3 * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
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
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
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
29 class PEAR_PackageFile_v2_Validator
36 * @var PEAR_PackageFile_v2
40 * @var PEAR_ErrorStack
56 * @param PEAR_PackageFile_v2
59 function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
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) {
71 if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
74 if (!isset($this->_packageInfo['attribs']['version']) ||
75 ($this->_packageInfo['attribs']['version'] != '2.0' &&
76 $this->_packageInfo['attribs']['version'] != '2.1')
78 $this->_noPackageVersion();
84 '*extends', // can't be multiple, but this works fine
87 '+lead', // these all need content checks
95 'license->?uri->?filesource',
97 'contents', //special validation needed
99 'dependencies', //special validation needed
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
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'], '<')
118 $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
121 // ignore post-installation array fields
122 if (array_key_exists('filelist', $test)) {
123 unset($test['filelist']);
125 if (array_key_exists('_lastmodified', $test)) {
126 unset($test['_lastmodified']);
128 if (array_key_exists('#binarypackage', $test)) {
129 unset($test['#binarypackage']);
131 if (array_key_exists('old', $test)) {
134 if (array_key_exists('_lastversion', $test)) {
135 unset($test['_lastversion']);
137 if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
140 if (empty($this->_packageInfo['name'])) {
141 $this->_tagCannotBeEmpty('name');
143 $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
144 if (empty($this->_packageInfo[$test])) {
145 $this->_tagCannotBeEmpty($test);
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');
154 if (empty($this->_packageInfo['summary'])) {
155 $this->_tagCannotBeEmpty('summary');
157 if (empty($this->_packageInfo['description'])) {
158 $this->_tagCannotBeEmpty('description');
160 if (empty($this->_packageInfo['date'])) {
161 $this->_tagCannotBeEmpty('date');
163 if (empty($this->_packageInfo['notes'])) {
164 $this->_tagCannotBeEmpty('notes');
166 if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
167 $this->_tagCannotBeEmpty('time');
169 if (isset($this->_packageInfo['dependencies'])) {
170 $this->_validateDependencies();
172 if (isset($this->_packageInfo['compatible'])) {
173 $this->_validateCompatible();
175 if (!isset($this->_packageInfo['bundle'])) {
176 if (empty($this->_packageInfo['contents'])) {
177 $this->_tagCannotBeEmpty('contents');
179 if (!isset($this->_packageInfo['contents']['dir'])) {
180 $this->_filelistMustContainDir('contents');
183 if (isset($this->_packageInfo['contents']['file'])) {
184 $this->_filelistCannotContainFile('contents');
188 $this->_validateMaintainers();
189 $this->_validateStabilityVersion();
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);
196 foreach ($roles as $role) {
197 if (!isset($role['role'])) {
198 $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
201 if (!isset($role['channel'])) {
202 if (!isset($role['uri'])) {
203 $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
206 } elseif (!isset($role['package'])) {
207 $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
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);
218 foreach ($roles as $role) {
219 if (!isset($role['task'])) {
220 $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
223 if (!isset($role['channel'])) {
224 if (!isset($role['uri'])) {
225 $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
228 } elseif (!isset($role['package'])) {
229 $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
240 $list = $this->_packageInfo['contents'];
241 if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
242 $this->_multipleToplevelDirNotAllowed();
243 return $this->_isValid = 0;
246 $this->_validateFilelist();
247 $this->_validateRelease();
248 if (!$this->_stack->hasErrors()) {
249 $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
250 if (PEAR::isError($chan)) {
251 $this->_unknownChannel($this->_pf->getChannel());
253 $valpack = $chan->getValidationPackage();
254 // for channel validator packages, always use the default PEAR validator.
255 // otherwise, they can't be installed or packaged
256 $validator = $chan->getValidationObject($this->_pf->getPackage());
258 $this->_stack->push(__FUNCTION__, 'error',
259 array('channel' => $chan->getName(),
260 'package' => $this->_pf->getPackage(),
261 'name' => $valpack['_content'],
262 'version' => $valpack['attribs']['version']),
263 'package "%channel%/%package%" cannot be properly validated without ' .
264 'validation package "%channel%/%name%-%version%"');
265 return $this->_isValid = 0;
267 $validator->setPackageFile($this->_pf);
268 $validator->validate($state);
269 $failures = $validator->getFailures();
270 foreach ($failures['errors'] as $error) {
271 $this->_stack->push(__FUNCTION__, 'error', $error,
272 'Channel validator error: field "%field%" - %reason%');
274 foreach ($failures['warnings'] as $warning) {
275 $this->_stack->push(__FUNCTION__, 'warning', $warning,
276 'Channel validator warning: field "%field%" - %reason%');
281 $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
282 if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
283 if ($this->_pf->getPackageType() == 'bundle') {
284 if ($this->_analyzeBundledPackages()) {
285 $this->_filesValid = $this->_pf->_filesValid = true;
287 $this->_pf->_isValid = $this->_isValid = 0;
290 if (!$this->_analyzePhpFiles()) {
291 $this->_pf->_isValid = $this->_isValid = 0;
293 $this->_filesValid = $this->_pf->_filesValid = true;
298 if ($this->_isValid) {
299 return $this->_pf->_isValid = $this->_isValid = $state;
302 return $this->_pf->_isValid = $this->_isValid = 0;
305 function _stupidSchemaValidate($structure, $xml, $root)
307 if (!is_array($xml)) {
310 $keys = array_keys($xml);
312 $key = current($keys);
313 while ($key == 'attribs' || $key == '_contents') {
316 $unfoundtags = $optionaltags = array();
319 foreach ($structure as $struc) {
323 $test = $this->_processStructure($struc);
324 if (isset($test['choices'])) {
326 foreach ($test['choices'] as $choice) {
327 if ($key == $choice['tag']) {
329 while ($key == 'attribs' || $key == '_contents') {
332 $unfoundtags = $optionaltags = array();
334 if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
335 $unfoundtags[] = $choice['tag'];
336 $optionaltags[] = $choice['tag'];
341 $ret &= $this->_processAttribs($choice, $tag, $root);
344 $unfoundtags[] = $choice['tag'];
347 if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
350 $optionaltags[] = $choice['tag'];
354 $this->_invalidTagOrder($unfoundtags, $key, $root);
358 if ($key != $test['tag']) {
359 if (isset($test['multiple']) && $test['multiple'] != '*') {
360 $unfoundtags[] = $test['tag'];
361 $this->_invalidTagOrder($unfoundtags, $key, $root);
367 $unfoundtags[] = $test['tag'];
368 $optionaltags[] = $test['tag'];
370 if (!isset($test['multiple'])) {
371 $this->_invalidTagOrder($unfoundtags, $key, $root);
376 $unfoundtags = $optionaltags = array();
380 while ($key == 'attribs' || $key == '_contents') {
383 if ($key && $key != $test['tag'] && isset($test['multiple'])) {
384 $unfoundtags[] = $test['tag'];
385 $optionaltags[] = $test['tag'];
388 $ret &= $this->_processAttribs($test, $tag, $root);
392 if (!$mismatch && count($optionaltags)) {
393 // don't error out on any optional tags
394 $unfoundtags = array_diff($unfoundtags, $optionaltags);
396 if (count($unfoundtags)) {
397 $this->_invalidTagOrder($unfoundtags, $key, $root);
400 $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
401 while ($key = next($keys)) {
402 $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
408 function _processAttribs($choice, $tag, $context)
410 if (isset($choice['attribs'])) {
411 if (!is_array($tag)) {
415 if (!isset($tags[0])) {
416 $tags = array($tags);
419 foreach ($tags as $i => $tag) {
420 if (!is_array($tag) || !isset($tag['attribs'])) {
421 foreach ($choice['attribs'] as $attrib) {
422 if ($attrib{0} != '?') {
423 $ret &= $this->_tagHasNoAttribs($choice['tag'],
429 foreach ($choice['attribs'] as $attrib) {
430 if ($attrib{0} != '?') {
431 if (!isset($tag['attribs'][$attrib])) {
432 $ret &= $this->_tagMissingAttribute($choice['tag'],
443 function _processStructure($key)
446 if (count($pieces = explode('|', $key)) > 1) {
447 $ret['choices'] = array();
448 foreach ($pieces as $piece) {
449 $ret['choices'][] = $this->_processStructure($piece);
454 if ($multi == '+' || $multi == '*') {
455 $ret['multiple'] = $key{0};
456 $key = substr($key, 1);
458 if (count($attrs = explode('->', $key)) > 1) {
459 $ret['tag'] = array_shift($attrs);
460 $ret['attribs'] = $attrs;
467 function _validateStabilityVersion()
469 $structure = array('release', 'api');
470 $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
471 $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
473 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
474 $this->_packageInfo['version']['release'])) {
475 $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
477 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
478 $this->_packageInfo['version']['api'])) {
479 $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
481 if (!in_array($this->_packageInfo['stability']['release'],
482 array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
483 $this->_invalidState('release', $this->_packageInfo['stability']['release']);
485 if (!in_array($this->_packageInfo['stability']['api'],
486 array('devel', 'alpha', 'beta', 'stable'))) {
487 $this->_invalidState('api', $this->_packageInfo['stability']['api']);
492 function _validateMaintainers()
501 foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
502 if (!isset($this->_packageInfo[$type])) {
505 if (isset($this->_packageInfo[$type][0])) {
506 foreach ($this->_packageInfo[$type] as $lead) {
507 $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
510 $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
516 function _validatePhpDep($dep, $installcondition = false)
523 $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
524 $this->_stupidSchemaValidate($structure, $dep, $type);
525 if (isset($dep['min'])) {
526 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
528 $this->_invalidVersion($type . '<min>', $dep['min']);
531 if (isset($dep['max'])) {
532 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
534 $this->_invalidVersion($type . '<max>', $dep['max']);
537 if (isset($dep['exclude'])) {
538 if (!is_array($dep['exclude'])) {
539 $dep['exclude'] = array($dep['exclude']);
541 foreach ($dep['exclude'] as $exclude) {
543 '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
545 $this->_invalidVersion($type . '<exclude>', $exclude);
551 function _validatePearinstallerDep($dep)
559 $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
560 if (isset($dep['min'])) {
561 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
563 $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
567 if (isset($dep['max'])) {
568 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
570 $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
574 if (isset($dep['recommended'])) {
575 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
576 $dep['recommended'])) {
577 $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
578 $dep['recommended']);
581 if (isset($dep['exclude'])) {
582 if (!is_array($dep['exclude'])) {
583 $dep['exclude'] = array($dep['exclude']);
585 foreach ($dep['exclude'] as $exclude) {
586 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
588 $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
595 function _validatePackageDep($dep, $group, $type = '<package>')
597 if (isset($dep['uri'])) {
598 if (isset($dep['conflicts'])) {
603 '*providesextension',
609 '*providesextension',
613 if (isset($dep['conflicts'])) {
621 '*providesextension',
632 '*providesextension',
636 if (isset($dep['name'])) {
637 $type .= '<name>' . $dep['name'] . '</name>';
639 $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
640 if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
641 isset($dep['recommended']) || isset($dep['exclude']))) {
642 $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
644 if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
645 $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
647 if (isset($dep['min'])) {
648 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
650 $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
653 if (isset($dep['max'])) {
654 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
656 $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
659 if (isset($dep['recommended'])) {
660 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
661 $dep['recommended'])) {
662 $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
663 $dep['recommended']);
666 if (isset($dep['exclude'])) {
667 if (!is_array($dep['exclude'])) {
668 $dep['exclude'] = array($dep['exclude']);
670 foreach ($dep['exclude'] as $exclude) {
671 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
673 $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
680 function _validateSubpackageDep($dep, $group)
682 $this->_validatePackageDep($dep, $group, '<subpackage>');
683 if (isset($dep['providesextension'])) {
684 $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
686 if (isset($dep['conflicts'])) {
687 $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
691 function _validateExtensionDep($dep, $group = false, $installcondition = false)
693 if (isset($dep['conflicts'])) {
710 if ($installcondition) {
711 $type = '<installcondition><extension>';
713 $type = '<dependencies>' . $group . '<extension>';
715 if (isset($dep['name'])) {
716 $type .= '<name>' . $dep['name'] . '</name>';
718 $this->_stupidSchemaValidate($structure, $dep, $type);
719 if (isset($dep['min'])) {
720 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
722 $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
725 if (isset($dep['max'])) {
726 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
728 $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
731 if (isset($dep['recommended'])) {
732 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
733 $dep['recommended'])) {
734 $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
737 if (isset($dep['exclude'])) {
738 if (!is_array($dep['exclude'])) {
739 $dep['exclude'] = array($dep['exclude']);
741 foreach ($dep['exclude'] as $exclude) {
742 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
744 $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
750 function _validateOsDep($dep, $installcondition = false)
756 $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
757 if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
758 if ($dep['name'] == '*') {
759 if (array_key_exists('conflicts', $dep)) {
760 $this->_cannotConflictWithAllOs($type);
766 function _validateArchDep($dep, $installcondition = false)
772 $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
773 $this->_stupidSchemaValidate($structure, $dep, $type);
776 function _validateInstallConditions($cond, $release)
784 if (!$this->_stupidSchemaValidate($structure,
788 foreach (array('php', 'extension', 'os', 'arch') as $type) {
789 if (isset($cond[$type])) {
790 $iter = $cond[$type];
791 if (!is_array($iter) || !isset($iter[0])) {
792 $iter = array($iter);
794 foreach ($iter as $package) {
795 if ($type == 'extension') {
796 $this->{"_validate{$type}Dep"}($package, false, true);
798 $this->{"_validate{$type}Dep"}($package, true);
805 function _validateDependencies()
812 if (!$this->_stupidSchemaValidate($structure,
813 $this->_packageInfo['dependencies'], '<dependencies>')) {
816 foreach (array('required', 'optional') as $simpledep) {
817 if (isset($this->_packageInfo['dependencies'][$simpledep])) {
818 if ($simpledep == 'optional') {
835 if ($this->_stupidSchemaValidate($structure,
836 $this->_packageInfo['dependencies'][$simpledep],
837 "<dependencies><$simpledep>")) {
838 foreach (array('package', 'subpackage', 'extension') as $type) {
839 if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
840 $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
841 if (!isset($iter[0])) {
842 $iter = array($iter);
844 foreach ($iter as $package) {
845 if ($type != 'extension') {
846 if (isset($package['uri'])) {
847 if (isset($package['channel'])) {
848 $this->_UrlOrChannel($type,
852 if (!isset($package['channel'])) {
853 $this->_NoChannel($type, $package['name']);
857 $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
861 if ($simpledep == 'optional') {
864 foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
865 if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
866 $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
867 if (!isset($iter[0])) {
868 $iter = array($iter);
870 foreach ($iter as $package) {
871 $this->{"_validate{$type}Dep"}($package);
878 if (isset($this->_packageInfo['dependencies']['group'])) {
879 $groups = $this->_packageInfo['dependencies']['group'];
880 if (!isset($groups[0])) {
881 $groups = array($groups);
888 foreach ($groups as $group) {
889 if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
890 if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
891 $this->_invalidDepGroupName($group['attribs']['name']);
893 foreach (array('package', 'subpackage', 'extension') as $type) {
894 if (isset($group[$type])) {
895 $iter = $group[$type];
896 if (!isset($iter[0])) {
897 $iter = array($iter);
899 foreach ($iter as $package) {
900 if ($type != 'extension') {
901 if (isset($package['uri'])) {
902 if (isset($package['channel'])) {
903 $this->_UrlOrChannelGroup($type,
908 if (!isset($package['channel'])) {
909 $this->_NoChannelGroup($type,
915 $this->{"_validate{$type}Dep"}($package, '<group name="' .
916 $group['attribs']['name'] . '">');
925 function _validateCompatible()
927 $compat = $this->_packageInfo['compatible'];
928 if (!isset($compat[0])) {
929 $compat = array($compat);
931 $required = array('name', 'channel', 'min', 'max', '*exclude');
932 foreach ($compat as $package) {
933 $type = '<compatible>';
934 if (is_array($package) && array_key_exists('name', $package)) {
935 $type .= '<name>' . $package['name'] . '</name>';
937 $this->_stupidSchemaValidate($required, $package, $type);
938 if (is_array($package) && array_key_exists('min', $package)) {
939 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
941 $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
944 if (is_array($package) && array_key_exists('max', $package)) {
945 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
947 $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
950 if (is_array($package) && array_key_exists('exclude', $package)) {
951 if (!is_array($package['exclude'])) {
952 $package['exclude'] = array($package['exclude']);
954 foreach ($package['exclude'] as $exclude) {
955 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
957 $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
964 function _validateBundle($list)
966 if (!is_array($list) || !isset($list['bundledpackage'])) {
967 return $this->_NoBundledPackages();
969 if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
970 return $this->_AtLeast2BundledPackages();
972 foreach ($list['bundledpackage'] as $package) {
973 if (!is_string($package)) {
974 $this->_bundledPackagesMustBeFilename();
979 function _validateFilelist($list = false, $allowignore = false, $dirs = '')
984 $list = $this->_packageInfo['contents'];
985 if (isset($this->_packageInfo['bundle'])) {
986 return $this->_validateBundle($list);
991 '*install->name->as',
996 '*dir->name->?baseinstalldir',
997 '*file->name->role->?baseinstalldir->?md5sum'
999 if (isset($list['dir']) && isset($list['file'])) {
1000 // stave off validation errors without requiring a set order.
1002 if (isset($list['attribs'])) {
1003 $list = array('attribs' => $_old['attribs']);
1005 $list['dir'] = $_old['dir'];
1006 $list['file'] = $_old['file'];
1009 if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
1010 $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
1011 $dirname = $iscontents ? '<contents>' : $unknown;
1013 $dirname = '<dir name="' . $list['attribs']['name'] . '">';
1014 if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
1015 str_replace('\\', '/', $list['attribs']['name']))) {
1016 // file contains .. parent directory or . cur directory
1017 $this->_invalidDirName($list['attribs']['name']);
1020 $res = $this->_stupidSchemaValidate($struc, $list, $dirname);
1021 if ($allowignore && $res) {
1022 $ignored_or_installed = array();
1023 $this->_pf->getFilelist();
1024 $fcontents = $this->_pf->getContents();
1025 $filelist = array();
1026 if (!isset($fcontents['dir']['file'][0])) {
1027 $fcontents['dir']['file'] = array($fcontents['dir']['file']);
1029 foreach ($fcontents['dir']['file'] as $file) {
1030 $filelist[$file['attribs']['name']] = true;
1032 if (isset($list['install'])) {
1033 if (!isset($list['install'][0])) {
1034 $list['install'] = array($list['install']);
1036 foreach ($list['install'] as $file) {
1037 if (!isset($filelist[$file['attribs']['name']])) {
1038 $this->_notInContents($file['attribs']['name'], 'install');
1041 if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1042 $this->_multipleInstallAs($file['attribs']['name']);
1044 if (!isset($ignored_or_installed[$file['attribs']['name']])) {
1045 $ignored_or_installed[$file['attribs']['name']] = array();
1047 $ignored_or_installed[$file['attribs']['name']][] = 1;
1048 if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
1049 str_replace('\\', '/', $file['attribs']['as']))) {
1050 // file contains .. parent directory or . cur directory references
1051 $this->_invalidFileInstallAs($file['attribs']['name'],
1052 $file['attribs']['as']);
1056 if (isset($list['ignore'])) {
1057 if (!isset($list['ignore'][0])) {
1058 $list['ignore'] = array($list['ignore']);
1060 foreach ($list['ignore'] as $file) {
1061 if (!isset($filelist[$file['attribs']['name']])) {
1062 $this->_notInContents($file['attribs']['name'], 'ignore');
1065 if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1066 $this->_ignoreAndInstallAs($file['attribs']['name']);
1071 if (!$allowignore && isset($list['file'])) {
1072 if (is_string($list['file'])) {
1073 $this->_oldStyleFileNotAllowed();
1076 if (!isset($list['file'][0])) {
1078 $list['file'] = array($list['file']);
1080 foreach ($list['file'] as $i => $file)
1082 if (isset($file['attribs']) && isset($file['attribs']['name'])) {
1083 if ($file['attribs']['name']{0} == '.' &&
1084 $file['attribs']['name']{1} == '/') {
1085 // name is something like "./doc/whatever.txt"
1086 $this->_invalidFileName($file['attribs']['name'], $dirname);
1088 if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
1089 str_replace('\\', '/', $file['attribs']['name']))) {
1090 // file contains .. parent directory or . cur directory
1091 $this->_invalidFileName($file['attribs']['name'], $dirname);
1094 if (isset($file['attribs']) && isset($file['attribs']['role'])) {
1095 if (!$this->_validateRole($file['attribs']['role'])) {
1096 if (isset($this->_packageInfo['usesrole'])) {
1097 $roles = $this->_packageInfo['usesrole'];
1098 if (!isset($roles[0])) {
1099 $roles = array($roles);
1101 foreach ($roles as $role) {
1102 if ($role['role'] = $file['attribs']['role']) {
1103 $msg = 'This package contains role "%role%" and requires ' .
1104 'package "%package%" to be used';
1105 if (isset($role['uri'])) {
1106 $params = array('role' => $role['role'],
1107 'package' => $role['uri']);
1109 $params = array('role' => $role['role'],
1110 'package' => $this->_pf->_registry->
1111 parsedPackageNameToString(array('package' =>
1112 $role['package'], 'channel' => $role['channel']),
1115 $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
1119 $this->_invalidFileRole($file['attribs']['name'],
1120 $dirname, $file['attribs']['role']);
1123 if (!isset($file['attribs'])) {
1126 $save = $file['attribs'];
1128 $save['name'] = $dirs . '/' . $save['name'];
1130 unset($file['attribs']);
1131 if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
1132 foreach ($file as $task => $value) {
1133 if ($tagClass = $this->_pf->getTask($task)) {
1134 if (!is_array($value) || !isset($value[0])) {
1135 $value = array($value);
1137 foreach ($value as $v) {
1138 $ret = call_user_func(array($tagClass, 'validateXml'),
1139 $this->_pf, $v, $this->_pf->_config, $save);
1140 if (is_array($ret)) {
1141 $this->_invalidTask($task, $ret, isset($save['name']) ?
1142 $save['name'] : '');
1146 if (isset($this->_packageInfo['usestask'])) {
1147 $roles = $this->_packageInfo['usestask'];
1148 if (!isset($roles[0])) {
1149 $roles = array($roles);
1151 foreach ($roles as $role) {
1152 if ($role['task'] = $task) {
1153 $msg = 'This package contains task "%task%" and requires ' .
1154 'package "%package%" to be used';
1155 if (isset($role['uri'])) {
1156 $params = array('task' => $role['task'],
1157 'package' => $role['uri']);
1159 $params = array('task' => $role['task'],
1160 'package' => $this->_pf->_registry->
1161 parsedPackageNameToString(array('package' =>
1162 $role['package'], 'channel' => $role['channel']),
1165 $this->_stack->push('_mustInstallTask', 'error',
1170 $this->_unknownTask($task, $save['name']);
1176 if (isset($list['ignore'])) {
1177 if (!$allowignore) {
1178 $this->_ignoreNotAllowed('ignore');
1181 if (isset($list['install'])) {
1182 if (!$allowignore) {
1183 $this->_ignoreNotAllowed('install');
1186 if (isset($list['file'])) {
1188 $this->_fileNotAllowed('file');
1191 if (isset($list['dir'])) {
1193 $this->_fileNotAllowed('dir');
1195 if (!isset($list['dir'][0])) {
1196 $list['dir'] = array($list['dir']);
1198 foreach ($list['dir'] as $dir) {
1199 if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
1200 if ($dir['attribs']['name'] == '/' ||
1201 !isset($this->_packageInfo['contents']['dir']['dir'])) {
1202 // always use nothing if the filelist has already been flattened
1204 } elseif ($dirs == '') {
1205 $newdirs = $dir['attribs']['name'];
1207 $newdirs = $dirs . '/' . $dir['attribs']['name'];
1212 $this->_validateFilelist($dir, $allowignore, $newdirs);
1218 function _validateRelease()
1220 if (isset($this->_packageInfo['phprelease'])) {
1221 $release = 'phprelease';
1222 if (isset($this->_packageInfo['providesextension'])) {
1223 $this->_cannotProvideExtension($release);
1225 if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1226 $this->_cannotHaveSrcpackage($release);
1228 $releases = $this->_packageInfo['phprelease'];
1229 if (!is_array($releases)) {
1232 if (!isset($releases[0])) {
1233 $releases = array($releases);
1235 foreach ($releases as $rel) {
1236 $this->_stupidSchemaValidate(array(
1237 '*installconditions',
1239 ), $rel, '<phprelease>');
1242 foreach (array('', 'zend') as $prefix) {
1243 $releasetype = $prefix . 'extsrcrelease';
1244 if (isset($this->_packageInfo[$releasetype])) {
1245 $release = $releasetype;
1246 if (!isset($this->_packageInfo['providesextension'])) {
1247 $this->_mustProvideExtension($release);
1249 if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1250 $this->_cannotHaveSrcpackage($release);
1252 $releases = $this->_packageInfo[$releasetype];
1253 if (!is_array($releases)) {
1256 if (!isset($releases[0])) {
1257 $releases = array($releases);
1259 foreach ($releases as $rel) {
1260 $this->_stupidSchemaValidate(array(
1261 '*installconditions',
1262 '*configureoption->name->prompt->?default',
1265 ), $rel, '<' . $releasetype . '>');
1266 if (isset($rel['binarypackage'])) {
1267 if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
1268 $rel['binarypackage'] = array($rel['binarypackage']);
1270 foreach ($rel['binarypackage'] as $bin) {
1271 if (!is_string($bin)) {
1272 $this->_binaryPackageMustBePackagename();
1278 $releasetype = 'extbinrelease';
1279 if (isset($this->_packageInfo[$releasetype])) {
1280 $release = $releasetype;
1281 if (!isset($this->_packageInfo['providesextension'])) {
1282 $this->_mustProvideExtension($release);
1284 if (isset($this->_packageInfo['channel']) &&
1285 !isset($this->_packageInfo['srcpackage'])) {
1286 $this->_mustSrcPackage($release);
1288 if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
1289 $this->_mustSrcuri($release);
1291 $releases = $this->_packageInfo[$releasetype];
1292 if (!is_array($releases)) {
1295 if (!isset($releases[0])) {
1296 $releases = array($releases);
1298 foreach ($releases as $rel) {
1299 $this->_stupidSchemaValidate(array(
1300 '*installconditions',
1302 ), $rel, '<' . $releasetype . '>');
1306 if (isset($this->_packageInfo['bundle'])) {
1307 $release = 'bundle';
1308 if (isset($this->_packageInfo['providesextension'])) {
1309 $this->_cannotProvideExtension($release);
1311 if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1312 $this->_cannotHaveSrcpackage($release);
1314 $releases = $this->_packageInfo['bundle'];
1315 if (!is_array($releases) || !isset($releases[0])) {
1316 $releases = array($releases);
1318 foreach ($releases as $rel) {
1319 $this->_stupidSchemaValidate(array(
1320 '*installconditions',
1322 ), $rel, '<bundle>');
1325 foreach ($releases as $rel) {
1326 if (is_array($rel) && array_key_exists('installconditions', $rel)) {
1327 $this->_validateInstallConditions($rel['installconditions'],
1328 "<$release><installconditions>");
1330 if (is_array($rel) && array_key_exists('filelist', $rel)) {
1331 if ($rel['filelist']) {
1333 $this->_validateFilelist($rel['filelist'], true);
1340 * This is here to allow role extension through plugins
1343 function _validateRole($role)
1345 return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
1348 function _pearVersionTooLow($version)
1350 $this->_stack->push(__FUNCTION__, 'error',
1351 array('version' => $version),
1352 'This package.xml requires PEAR version %version% to parse properly, we are ' .
1356 function _invalidTagOrder($oktags, $actual, $root)
1358 $this->_stack->push(__FUNCTION__, 'error',
1359 array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
1360 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
1363 function _ignoreNotAllowed($type)
1365 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1366 '<%type%> is not allowed inside global <contents>, only inside ' .
1367 '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
1370 function _fileNotAllowed($type)
1372 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1373 '<%type%> is not allowed inside release <filelist>, only inside ' .
1374 '<contents>, use <ignore> and <install> only');
1377 function _oldStyleFileNotAllowed()
1379 $this->_stack->push(__FUNCTION__, 'error', array(),
1380 'Old-style <file>name</file> is not allowed. Use' .
1381 '<file name="name" role="role"/>');
1384 function _tagMissingAttribute($tag, $attr, $context)
1386 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1387 'attribute' => $attr, 'context' => $context),
1388 'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
1391 function _tagHasNoAttribs($tag, $context)
1393 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1394 'context' => $context),
1395 'tag <%tag%> has no attributes in context "%context%"');
1398 function _invalidInternalStructure()
1400 $this->_stack->push(__FUNCTION__, 'exception', array(),
1401 'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
1404 function _invalidFileRole($file, $dir, $role)
1406 $this->_stack->push(__FUNCTION__, 'error', array(
1407 'file' => $file, 'dir' => $dir, 'role' => $role,
1408 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
1409 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
1412 function _invalidFileName($file, $dir)
1414 $this->_stack->push(__FUNCTION__, 'error', array(
1416 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
1419 function _invalidFileInstallAs($file, $as)
1421 $this->_stack->push(__FUNCTION__, 'error', array(
1422 'file' => $file, 'as' => $as),
1423 'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
1426 function _invalidDirName($dir)
1428 $this->_stack->push(__FUNCTION__, 'error', array(
1430 'Directory "%dir%" cannot begin with "./" or contain ".."');
1433 function _filelistCannotContainFile($filelist)
1435 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
1436 '<%tag%> can only contain <dir>, contains <file>. Use ' .
1437 '<dir name="/"> as the first dir element');
1440 function _filelistMustContainDir($filelist)
1442 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
1443 '<%tag%> must contain <dir>. Use <dir name="/"> as the ' .
1444 'first dir element');
1447 function _tagCannotBeEmpty($tag)
1449 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1450 '<%tag%> cannot be empty (<%tag%/>)');
1453 function _UrlOrChannel($type, $name)
1455 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1457 'Required dependency <%type%> "%name%" can have either url OR ' .
1458 'channel attributes, and not both');
1461 function _NoChannel($type, $name)
1463 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1465 'Required dependency <%type%> "%name%" must have either url OR ' .
1466 'channel attributes');
1469 function _UrlOrChannelGroup($type, $name, $group)
1471 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1472 'name' => $name, 'group' => $group),
1473 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
1474 'channel attributes, and not both');
1477 function _NoChannelGroup($type, $name, $group)
1479 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1480 'name' => $name, 'group' => $group),
1481 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
1482 'channel attributes');
1485 function _unknownChannel($channel)
1487 $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
1488 'Unknown channel "%channel%"');
1491 function _noPackageVersion()
1493 $this->_stack->push(__FUNCTION__, 'error', array(),
1494 'package.xml <package> tag has no version attribute, or version is not 2.0');
1497 function _NoBundledPackages()
1499 $this->_stack->push(__FUNCTION__, 'error', array(),
1500 'No <bundledpackage> tag was found in <contents>, required for bundle packages');
1503 function _AtLeast2BundledPackages()
1505 $this->_stack->push(__FUNCTION__, 'error', array(),
1506 'At least 2 packages must be bundled in a bundle package');
1509 function _ChannelOrUri($name)
1511 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1512 'Bundled package "%name%" can have either a uri or a channel, not both');
1515 function _noChildTag($child, $tag)
1517 $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
1518 'Tag <%tag%> is missing child tag <%child%>');
1521 function _invalidVersion($type, $value)
1523 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
1524 'Version type <%type%> is not a valid version (%value%)');
1527 function _invalidState($type, $value)
1529 $states = array('stable', 'beta', 'alpha', 'devel');
1530 if ($type != 'api') {
1531 $states[] = 'snapshot';
1533 if (strtolower($value) == 'rc') {
1534 $this->_stack->push(__FUNCTION__, 'error',
1535 array('version' => $this->_packageInfo['version']['release']),
1536 'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
1538 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
1539 'types' => $states),
1540 'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
1544 function _invalidTask($task, $ret, $file)
1547 case PEAR_TASK_ERROR_MISSING_ATTRIB :
1548 $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
1549 $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
1551 case PEAR_TASK_ERROR_NOATTRIBS :
1552 $info = array('task' => $task, 'file' => $file);
1553 $msg = 'task <%task%> has no attributes in file %file%';
1555 case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
1556 $info = array('attrib' => $ret[1], 'values' => $ret[3],
1557 'was' => $ret[2], 'task' => $task, 'file' => $file);
1558 $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
1559 'in file %file%, expecting one of "%values%"';
1561 case PEAR_TASK_ERROR_INVALID :
1562 $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
1563 $msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
1566 $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
1569 function _unknownTask($task, $file)
1571 $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
1572 'Unknown task "%task%" passed in file <file name="%file%">');
1575 function _subpackageCannotProvideExtension($name)
1577 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1578 'Subpackage dependency "%name%" cannot use <providesextension>, ' .
1579 'only package dependencies can use this tag');
1582 function _subpackagesCannotConflict($name)
1584 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1585 'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
1586 'only package dependencies can use this tag');
1589 function _cannotProvideExtension($release)
1591 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1592 '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
1595 function _mustProvideExtension($release)
1597 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1598 '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
1601 function _cannotHaveSrcpackage($release)
1603 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1604 '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
1607 function _mustSrcPackage($release)
1609 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1610 '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
1613 function _mustSrcuri($release)
1615 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1616 '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
1619 function _uriDepsCannotHaveVersioning($type)
1621 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1622 '%type%: dependencies with a <uri> tag cannot have any versioning information');
1625 function _conflictingDepsCannotHaveVersioning($type)
1627 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1628 '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
1629 'exclude specific versions of a dependency');
1632 function _DepchannelCannotBeUri($type)
1634 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1635 '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
1636 'dependencies only');
1639 function _bundledPackagesMustBeFilename()
1641 $this->_stack->push(__FUNCTION__, 'error', array(),
1642 '<bundledpackage> tags must contain only the filename of a package release ' .
1646 function _binaryPackageMustBePackagename()
1648 $this->_stack->push(__FUNCTION__, 'error', array(),
1649 '<binarypackage> tags must contain the name of a package that is ' .
1650 'a compiled version of this extsrc/zendextsrc package');
1653 function _fileNotFound($file)
1655 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1656 'File "%file%" in package.xml does not exist');
1659 function _notInContents($file, $tag)
1661 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
1662 '<%tag% name="%file%"> is invalid, file is not in <contents>');
1665 function _cannotValidateNoPathSet()
1667 $this->_stack->push(__FUNCTION__, 'error', array(),
1668 'Cannot validate files, no path to package file is set (use setPackageFile())');
1671 function _usesroletaskMustHaveChannelOrUri($role, $tag)
1673 $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1674 '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
1677 function _usesroletaskMustHavePackage($role, $tag)
1679 $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1680 '<%tag%> for role "%role%" must contain <package>');
1683 function _usesroletaskMustHaveRoleTask($tag, $type)
1685 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
1686 '<%tag%> must contain <%type%> defining the %type% to be used');
1689 function _cannotConflictWithAllOs($type)
1691 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1692 '%tag% cannot conflict with all OSes');
1695 function _invalidDepGroupName($name)
1697 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1698 'Invalid dependency group name "%name%"');
1701 function _multipleToplevelDirNotAllowed()
1703 $this->_stack->push(__FUNCTION__, 'error', array(),
1704 'Multiple top-level <dir> tags are not allowed. Enclose them ' .
1705 'in a <dir name="/">');
1708 function _multipleInstallAs($file)
1710 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1711 'Only one <install> tag is allowed for file "%file%"');
1714 function _ignoreAndInstallAs($file)
1716 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1717 'Cannot have both <ignore> and <install> tags for file "%file%"');
1720 function _analyzeBundledPackages()
1722 if (!$this->_isValid) {
1725 if (!$this->_pf->getPackageType() == 'bundle') {
1728 if (!isset($this->_pf->_packageFile)) {
1731 $dir_prefix = dirname($this->_pf->_packageFile);
1732 $common = new PEAR_Common;
1733 $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
1734 array($common, 'log');
1735 $info = $this->_pf->getContents();
1736 $info = $info['bundledpackage'];
1737 if (!is_array($info)) {
1738 $info = array($info);
1740 $pkg = &new PEAR_PackageFile($this->_pf->_config);
1741 foreach ($info as $package) {
1742 if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
1743 $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
1744 $this->_isValid = 0;
1747 call_user_func_array($log, array(1, "Analyzing bundled package $package"));
1748 PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1749 $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
1750 PEAR_VALIDATE_NORMAL);
1751 PEAR::popErrorHandling();
1752 if (PEAR::isError($ret)) {
1753 call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
1755 $inf = $ret->getUserInfo();
1756 if (is_array($inf)) {
1757 foreach ($inf as $err) {
1758 call_user_func_array($log, array(1, $err['message']));
1767 function _analyzePhpFiles()
1769 if (!$this->_isValid) {
1772 if (!isset($this->_pf->_packageFile)) {
1773 $this->_cannotValidateNoPathSet();
1776 $dir_prefix = dirname($this->_pf->_packageFile);
1777 $common = new PEAR_Common;
1778 $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
1779 array(&$common, 'log');
1780 $info = $this->_pf->getContents();
1781 if (!$info || !isset($info['dir']['file'])) {
1782 $this->_tagCannotBeEmpty('contents><dir');
1785 $info = $info['dir']['file'];
1786 if (isset($info['attribs'])) {
1787 $info = array($info);
1789 $provides = array();
1790 foreach ($info as $fa) {
1791 $fa = $fa['attribs'];
1792 $file = $fa['name'];
1793 if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
1794 $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
1795 $this->_isValid = 0;
1798 if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
1799 call_user_func_array($log, array(1, "Analyzing $file"));
1800 $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
1802 $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
1806 $this->_packageName = $pn = $this->_pf->getPackage();
1808 foreach ($provides as $key => $what) {
1809 if (isset($what['explicit']) || !$what) {
1810 // skip conformance checks if the provides entry is
1811 // specified in the package.xml file
1815 if ($type == 'class') {
1816 if (!strncasecmp($name, $pn, $pnl)) {
1819 $this->_stack->push(__FUNCTION__, 'warning',
1820 array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
1821 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
1822 } elseif ($type == 'function') {
1823 if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
1826 $this->_stack->push(__FUNCTION__, 'warning',
1827 array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
1828 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
1831 return $this->_isValid;
1835 * Analyze the source code of the given PHP file
1837 * @param string Filename of the PHP file
1838 * @param boolean whether to analyze $file as the file contents
1841 function analyzeSourceCode($file, $string = false)
1843 if (!function_exists("token_get_all")) {
1844 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1845 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
1849 if (!defined('T_DOC_COMMENT')) {
1850 define('T_DOC_COMMENT', T_COMMENT);
1853 if (!defined('T_INTERFACE')) {
1854 define('T_INTERFACE', -1);
1857 if (!defined('T_IMPLEMENTS')) {
1858 define('T_IMPLEMENTS', -1);
1864 if (!$fp = @fopen($file, "r")) {
1868 $contents = file_get_contents($file);
1871 // Silence this function so we can catch PHP Warnings and show our own custom message
1872 $tokens = @token_get_all($contents);
1873 if (isset($php_errormsg)) {
1874 if (isset($this->_stack)) {
1875 $pn = $this->_pf->getPackage();
1876 $this->_stack->push(__FUNCTION__, 'warning',
1877 array('file' => $file, 'package' => $pn),
1878 'in %file%: Could not process file for unkown reasons,' .
1879 ' possibly a PHP parse error in %file% from %package%');
1883 for ($i = 0; $i < sizeof($tokens); $i++) {
1884 @list($token, $data) = $tokens[$i];
1885 if (is_string($token)) {
1888 print token_name($token) . ' ';
1889 var_dump(rtrim($data));
1898 $current_class = '';
1899 $current_interface = '';
1900 $current_class_level = -1;
1901 $current_function = '';
1902 $current_function_level = -1;
1903 $declared_classes = array();
1904 $declared_interfaces = array();
1905 $declared_functions = array();
1906 $declared_methods = array();
1907 $used_classes = array();
1908 $used_functions = array();
1910 $implements = array();
1914 for ($i = 0; $i < sizeof($tokens); $i++) {
1915 if (is_array($tokens[$i])) {
1916 list($token, $data) = $tokens[$i];
1918 $token = $tokens[$i];
1923 if ($token != '"' && $token != T_END_HEREDOC) {
1936 $current_function = '';
1937 $current_function_level = -1;
1941 case T_START_HEREDOC:
1945 case T_DOLLAR_OPEN_CURLY_BRACES:
1946 case '{': $brace_level++; continue 2;
1949 if ($current_class_level == $brace_level) {
1950 $current_class = '';
1951 $current_class_level = -1;
1953 if ($current_function_level == $brace_level) {
1954 $current_function = '';
1955 $current_function_level = -1;
1958 case '[': $bracket_level++; continue 2;
1959 case ']': $bracket_level--; continue 2;
1960 case '(': $paren_level++; continue 2;
1961 case ')': $paren_level--; continue 2;
1965 if (($current_class_level != -1) || ($current_function_level != -1)) {
1966 if (isset($this->_stack)) {
1967 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1968 'Parser error: invalid PHP found in file "%file%"');
1970 PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
1971 PEAR_COMMON_ERROR_INVALIDPHP);
1983 if (version_compare(zend_version(), '2.0', '<')) {
1984 if (in_array(strtolower($data),
1985 array('public', 'private', 'protected', 'abstract',
1986 'interface', 'implements', 'throw')
1989 if (isset($this->_stack)) {
1990 $this->_stack->push(__FUNCTION__, 'warning', array(
1992 'Error, PHP5 token encountered in %file%,' .
1993 ' analysis should be in PHP5');
1995 PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
1996 'packaging should be done in PHP 5');
2002 if ($look_for == T_CLASS) {
2003 $current_class = $data;
2004 $current_class_level = $brace_level;
2005 $declared_classes[] = $current_class;
2006 } elseif ($look_for == T_INTERFACE) {
2007 $current_interface = $data;
2008 $current_class_level = $brace_level;
2009 $declared_interfaces[] = $current_interface;
2010 } elseif ($look_for == T_IMPLEMENTS) {
2011 $implements[$current_class] = $data;
2012 } elseif ($look_for == T_EXTENDS) {
2013 $extends[$current_class] = $data;
2014 } elseif ($look_for == T_FUNCTION) {
2015 if ($current_class) {
2016 $current_function = "$current_class::$data";
2017 $declared_methods[$current_class][] = $data;
2018 } elseif ($current_interface) {
2019 $current_function = "$current_interface::$data";
2020 $declared_methods[$current_interface][] = $data;
2022 $current_function = $data;
2023 $declared_functions[] = $current_function;
2026 $current_function_level = $brace_level;
2028 } elseif ($look_for == T_NEW) {
2029 $used_classes[$data] = true;
2039 if (preg_match('!^/\*\*\s!', $data)) {
2040 $lastphpdoc = $data;
2041 if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
2042 $nodeps = array_merge($nodeps, $m[1]);
2046 case T_DOUBLE_COLON:
2047 $token = $tokens[$i - 1][0];
2048 if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
2049 if (isset($this->_stack)) {
2050 $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
2051 'Parser error: invalid PHP found in file "%file%"');
2053 PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
2054 PEAR_COMMON_ERROR_INVALIDPHP);
2060 $class = $tokens[$i - 1][1];
2061 if (strtolower($class) != 'parent') {
2062 $used_classes[$class] = true;
2070 "source_file" => $file,
2071 "declared_classes" => $declared_classes,
2072 "declared_interfaces" => $declared_interfaces,
2073 "declared_methods" => $declared_methods,
2074 "declared_functions" => $declared_functions,
2075 "used_classes" => array_diff(array_keys($used_classes), $nodeps),
2076 "inheritance" => $extends,
2077 "implements" => $implements,
2082 * Build a "provides" array from data returned by
2083 * analyzeSourceCode(). The format of the built array is like
2087 * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
2092 * @param array $srcinfo array with information about a source file
2093 * as returned by the analyzeSourceCode() method.
2100 function _buildProvidesArray($srcinfo)
2102 if (!$this->_isValid) {
2106 $providesret = array();
2107 $file = basename($srcinfo['source_file']);
2108 $pn = isset($this->_pf) ? $this->_pf->getPackage() : '';
2110 foreach ($srcinfo['declared_classes'] as $class) {
2111 $key = "class;$class";
2112 if (isset($providesret[$key])) {
2116 $providesret[$key] =
2117 array('file'=> $file, 'type' => 'class', 'name' => $class);
2118 if (isset($srcinfo['inheritance'][$class])) {
2119 $providesret[$key]['extends'] =
2120 $srcinfo['inheritance'][$class];
2124 foreach ($srcinfo['declared_methods'] as $class => $methods) {
2125 foreach ($methods as $method) {
2126 $function = "$class::$method";
2127 $key = "function;$function";
2128 if ($method{0} == '_' || !strcasecmp($method, $class) ||
2129 isset($providesret[$key])) {
2133 $providesret[$key] =
2134 array('file'=> $file, 'type' => 'function', 'name' => $function);
2138 foreach ($srcinfo['declared_functions'] as $function) {
2139 $key = "function;$function";
2140 if ($function{0} == '_' || isset($providesret[$key])) {
2144 if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
2145 $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
2148 $providesret[$key] =
2149 array('file'=> $file, 'type' => 'function', 'name' => $function);
2152 return $providesret;