* @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License * @version CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $ * @link http://pear.php.net/package/PEAR * @since File available since Release 1.4.0a8 */ /** * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller * @category pear * @package PEAR * @author Greg Beaver * @copyright 1997-2009 The Authors * @license http://opensource.org/licenses/bsd-license.php New BSD License * @version Release: 1.9.4 * @link http://pear.php.net/package/PEAR * @since Class available since Release 1.4.0a8 * @access private */ class PEAR_PackageFile_v2_Validator { /** * @var array */ var $_packageInfo; /** * @var PEAR_PackageFile_v2 */ var $_pf; /** * @var PEAR_ErrorStack */ var $_stack; /** * @var int */ var $_isValid = 0; /** * @var int */ var $_filesValid = 0; /** * @var int */ var $_curState = 0; /** * @param PEAR_PackageFile_v2 * @param int */ function validate(&$pf, $state = PEAR_VALIDATE_NORMAL) { $this->_pf = &$pf; $this->_curState = $state; $this->_packageInfo = $this->_pf->getArray(); $this->_isValid = $this->_pf->_isValid; $this->_filesValid = $this->_pf->_filesValid; $this->_stack = &$pf->_stack; $this->_stack->getErrors(true); if (($this->_isValid & $state) == $state) { return true; } if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) { return false; } if (!isset($this->_packageInfo['attribs']['version']) || ($this->_packageInfo['attribs']['version'] != '2.0' && $this->_packageInfo['attribs']['version'] != '2.1') ) { $this->_noPackageVersion(); } $structure = array( 'name', 'channel|uri', '*extends', // can't be multiple, but this works fine 'summary', 'description', '+lead', // these all need content checks '*developer', '*contributor', '*helper', 'date', '*time', 'version', 'stability', 'license->?uri->?filesource', 'notes', 'contents', //special validation needed '*compatible', 'dependencies', //special validation needed '*usesrole', '*usestask', // reserve these for 1.4.0a1 to implement // this will allow a package.xml to gracefully say it // needs a certain package installed in order to implement a role or task '*providesextension', '*srcpackage|*srcuri', '+phprelease|+extsrcrelease|+extbinrelease|' . '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed '*changelog', ); $test = $this->_packageInfo; if (isset($test['dependencies']) && isset($test['dependencies']['required']) && isset($test['dependencies']['required']['pearinstaller']) && isset($test['dependencies']['required']['pearinstaller']['min']) && version_compare('1.9.4', $test['dependencies']['required']['pearinstaller']['min'], '<') ) { $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']); return false; } // ignore post-installation array fields if (array_key_exists('filelist', $test)) { unset($test['filelist']); } if (array_key_exists('_lastmodified', $test)) { unset($test['_lastmodified']); } if (array_key_exists('#binarypackage', $test)) { unset($test['#binarypackage']); } if (array_key_exists('old', $test)) { unset($test['old']); } if (array_key_exists('_lastversion', $test)) { unset($test['_lastversion']); } if (!$this->_stupidSchemaValidate($structure, $test, '')) { return false; } if (empty($this->_packageInfo['name'])) { $this->_tagCannotBeEmpty('name'); } $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel'; if (empty($this->_packageInfo[$test])) { $this->_tagCannotBeEmpty($test); } if (is_array($this->_packageInfo['license']) && (!isset($this->_packageInfo['license']['_content']) || empty($this->_packageInfo['license']['_content']))) { $this->_tagCannotBeEmpty('license'); } elseif (empty($this->_packageInfo['license'])) { $this->_tagCannotBeEmpty('license'); } if (empty($this->_packageInfo['summary'])) { $this->_tagCannotBeEmpty('summary'); } if (empty($this->_packageInfo['description'])) { $this->_tagCannotBeEmpty('description'); } if (empty($this->_packageInfo['date'])) { $this->_tagCannotBeEmpty('date'); } if (empty($this->_packageInfo['notes'])) { $this->_tagCannotBeEmpty('notes'); } if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) { $this->_tagCannotBeEmpty('time'); } if (isset($this->_packageInfo['dependencies'])) { $this->_validateDependencies(); } if (isset($this->_packageInfo['compatible'])) { $this->_validateCompatible(); } if (!isset($this->_packageInfo['bundle'])) { if (empty($this->_packageInfo['contents'])) { $this->_tagCannotBeEmpty('contents'); } if (!isset($this->_packageInfo['contents']['dir'])) { $this->_filelistMustContainDir('contents'); return false; } if (isset($this->_packageInfo['contents']['file'])) { $this->_filelistCannotContainFile('contents'); return false; } } $this->_validateMaintainers(); $this->_validateStabilityVersion(); $fail = false; if (array_key_exists('usesrole', $this->_packageInfo)) { $roles = $this->_packageInfo['usesrole']; if (!is_array($roles) || !isset($roles[0])) { $roles = array($roles); } foreach ($roles as $role) { if (!isset($role['role'])) { $this->_usesroletaskMustHaveRoleTask('usesrole', 'role'); $fail = true; } else { if (!isset($role['channel'])) { if (!isset($role['uri'])) { $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole'); $fail = true; } } elseif (!isset($role['package'])) { $this->_usesroletaskMustHavePackage($role['role'], 'usesrole'); $fail = true; } } } } if (array_key_exists('usestask', $this->_packageInfo)) { $roles = $this->_packageInfo['usestask']; if (!is_array($roles) || !isset($roles[0])) { $roles = array($roles); } foreach ($roles as $role) { if (!isset($role['task'])) { $this->_usesroletaskMustHaveRoleTask('usestask', 'task'); $fail = true; } else { if (!isset($role['channel'])) { if (!isset($role['uri'])) { $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask'); $fail = true; } } elseif (!isset($role['package'])) { $this->_usesroletaskMustHavePackage($role['task'], 'usestask'); $fail = true; } } } } if ($fail) { return false; } $list = $this->_packageInfo['contents']; if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) { $this->_multipleToplevelDirNotAllowed(); return $this->_isValid = 0; } $this->_validateFilelist(); $this->_validateRelease(); if (!$this->_stack->hasErrors()) { $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true); if (PEAR::isError($chan)) { $this->_unknownChannel($this->_pf->getChannel()); } else { $valpack = $chan->getValidationPackage(); // for channel validator packages, always use the default PEAR validator. // otherwise, they can't be installed or packaged $validator = $chan->getValidationObject($this->_pf->getPackage()); if (!$validator) { $this->_stack->push(__FUNCTION__, 'error', array('channel' => $chan->getName(), 'package' => $this->_pf->getPackage(), 'name' => $valpack['_content'], 'version' => $valpack['attribs']['version']), 'package "%channel%/%package%" cannot be properly validated without ' . 'validation package "%channel%/%name%-%version%"'); return $this->_isValid = 0; } $validator->setPackageFile($this->_pf); $validator->validate($state); $failures = $validator->getFailures(); foreach ($failures['errors'] as $error) { $this->_stack->push(__FUNCTION__, 'error', $error, 'Channel validator error: field "%field%" - %reason%'); } foreach ($failures['warnings'] as $warning) { $this->_stack->push(__FUNCTION__, 'warning', $warning, 'Channel validator warning: field "%field%" - %reason%'); } } } $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error'); if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) { if ($this->_pf->getPackageType() == 'bundle') { if ($this->_analyzeBundledPackages()) { $this->_filesValid = $this->_pf->_filesValid = true; } else { $this->_pf->_isValid = $this->_isValid = 0; } } else { if (!$this->_analyzePhpFiles()) { $this->_pf->_isValid = $this->_isValid = 0; } else { $this->_filesValid = $this->_pf->_filesValid = true; } } } if ($this->_isValid) { return $this->_pf->_isValid = $this->_isValid = $state; } return $this->_pf->_isValid = $this->_isValid = 0; } function _stupidSchemaValidate($structure, $xml, $root) { if (!is_array($xml)) { $xml = array(); } $keys = array_keys($xml); reset($keys); $key = current($keys); while ($key == 'attribs' || $key == '_contents') { $key = next($keys); } $unfoundtags = $optionaltags = array(); $ret = true; $mismatch = false; foreach ($structure as $struc) { if ($key) { $tag = $xml[$key]; } $test = $this->_processStructure($struc); if (isset($test['choices'])) { $loose = true; foreach ($test['choices'] as $choice) { if ($key == $choice['tag']) { $key = next($keys); while ($key == 'attribs' || $key == '_contents') { $key = next($keys); } $unfoundtags = $optionaltags = array(); $mismatch = false; if ($key && $key != $choice['tag'] && isset($choice['multiple'])) { $unfoundtags[] = $choice['tag']; $optionaltags[] = $choice['tag']; if ($key) { $mismatch = true; } } $ret &= $this->_processAttribs($choice, $tag, $root); continue 2; } else { $unfoundtags[] = $choice['tag']; $mismatch = true; } if (!isset($choice['multiple']) || $choice['multiple'] != '*') { $loose = false; } else { $optionaltags[] = $choice['tag']; } } if (!$loose) { $this->_invalidTagOrder($unfoundtags, $key, $root); return false; } } else { if ($key != $test['tag']) { if (isset($test['multiple']) && $test['multiple'] != '*') { $unfoundtags[] = $test['tag']; $this->_invalidTagOrder($unfoundtags, $key, $root); return false; } else { if ($key) { $mismatch = true; } $unfoundtags[] = $test['tag']; $optionaltags[] = $test['tag']; } if (!isset($test['multiple'])) { $this->_invalidTagOrder($unfoundtags, $key, $root); return false; } continue; } else { $unfoundtags = $optionaltags = array(); $mismatch = false; } $key = next($keys); while ($key == 'attribs' || $key == '_contents') { $key = next($keys); } if ($key && $key != $test['tag'] && isset($test['multiple'])) { $unfoundtags[] = $test['tag']; $optionaltags[] = $test['tag']; $mismatch = true; } $ret &= $this->_processAttribs($test, $tag, $root); continue; } } if (!$mismatch && count($optionaltags)) { // don't error out on any optional tags $unfoundtags = array_diff($unfoundtags, $optionaltags); } if (count($unfoundtags)) { $this->_invalidTagOrder($unfoundtags, $key, $root); } elseif ($key) { // unknown tags $this->_invalidTagOrder('*no tags allowed here*', $key, $root); while ($key = next($keys)) { $this->_invalidTagOrder('*no tags allowed here*', $key, $root); } } return $ret; } function _processAttribs($choice, $tag, $context) { if (isset($choice['attribs'])) { if (!is_array($tag)) { $tag = array($tag); } $tags = $tag; if (!isset($tags[0])) { $tags = array($tags); } $ret = true; foreach ($tags as $i => $tag) { if (!is_array($tag) || !isset($tag['attribs'])) { foreach ($choice['attribs'] as $attrib) { if ($attrib{0} != '?') { $ret &= $this->_tagHasNoAttribs($choice['tag'], $context); continue 2; } } } foreach ($choice['attribs'] as $attrib) { if ($attrib{0} != '?') { if (!isset($tag['attribs'][$attrib])) { $ret &= $this->_tagMissingAttribute($choice['tag'], $attrib, $context); } } } } return $ret; } return true; } function _processStructure($key) { $ret = array(); if (count($pieces = explode('|', $key)) > 1) { $ret['choices'] = array(); foreach ($pieces as $piece) { $ret['choices'][] = $this->_processStructure($piece); } return $ret; } $multi = $key{0}; if ($multi == '+' || $multi == '*') { $ret['multiple'] = $key{0}; $key = substr($key, 1); } if (count($attrs = explode('->', $key)) > 1) { $ret['tag'] = array_shift($attrs); $ret['attribs'] = $attrs; } else { $ret['tag'] = $key; } return $ret; } function _validateStabilityVersion() { $structure = array('release', 'api'); $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], ''); $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], ''); if ($a) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $this->_packageInfo['version']['release'])) { $this->_invalidVersion('release', $this->_packageInfo['version']['release']); } if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $this->_packageInfo['version']['api'])) { $this->_invalidVersion('api', $this->_packageInfo['version']['api']); } if (!in_array($this->_packageInfo['stability']['release'], array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) { $this->_invalidState('release', $this->_packageInfo['stability']['release']); } if (!in_array($this->_packageInfo['stability']['api'], array('devel', 'alpha', 'beta', 'stable'))) { $this->_invalidState('api', $this->_packageInfo['stability']['api']); } } } function _validateMaintainers() { $structure = array( 'name', 'user', 'email', 'active', ); foreach (array('lead', 'developer', 'contributor', 'helper') as $type) { if (!isset($this->_packageInfo[$type])) { continue; } if (isset($this->_packageInfo[$type][0])) { foreach ($this->_packageInfo[$type] as $lead) { $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>'); } } else { $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type], '<' . $type . '>'); } } } function _validatePhpDep($dep, $installcondition = false) { $structure = array( 'min', '*max', '*exclude', ); $type = $installcondition ? '' : ''; $this->_stupidSchemaValidate($structure, $dep, $type); if (isset($dep['min'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', $dep['min'])) { $this->_invalidVersion($type . '', $dep['min']); } } if (isset($dep['max'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', $dep['max'])) { $this->_invalidVersion($type . '', $dep['max']); } } if (isset($dep['exclude'])) { if (!is_array($dep['exclude'])) { $dep['exclude'] = array($dep['exclude']); } foreach ($dep['exclude'] as $exclude) { if (!preg_match( '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/', $exclude)) { $this->_invalidVersion($type . '', $exclude); } } } } function _validatePearinstallerDep($dep) { $structure = array( 'min', '*max', '*recommended', '*exclude', ); $this->_stupidSchemaValidate($structure, $dep, ''); if (isset($dep['min'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['min'])) { $this->_invalidVersion('', $dep['min']); } } if (isset($dep['max'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['max'])) { $this->_invalidVersion('', $dep['max']); } } if (isset($dep['recommended'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['recommended'])) { $this->_invalidVersion('', $dep['recommended']); } } if (isset($dep['exclude'])) { if (!is_array($dep['exclude'])) { $dep['exclude'] = array($dep['exclude']); } foreach ($dep['exclude'] as $exclude) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $exclude)) { $this->_invalidVersion('', $exclude); } } } } function _validatePackageDep($dep, $group, $type = '') { if (isset($dep['uri'])) { if (isset($dep['conflicts'])) { $structure = array( 'name', 'uri', 'conflicts', '*providesextension', ); } else { $structure = array( 'name', 'uri', '*providesextension', ); } } else { if (isset($dep['conflicts'])) { $structure = array( 'name', 'channel', '*min', '*max', '*exclude', 'conflicts', '*providesextension', ); } else { $structure = array( 'name', 'channel', '*min', '*max', '*recommended', '*exclude', '*nodefault', '*providesextension', ); } } if (isset($dep['name'])) { $type .= '' . $dep['name'] . ''; } $this->_stupidSchemaValidate($structure, $dep, '' . $group . $type); if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) || isset($dep['recommended']) || isset($dep['exclude']))) { $this->_uriDepsCannotHaveVersioning('' . $group . $type); } if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') { $this->_DepchannelCannotBeUri('' . $group . $type); } if (isset($dep['min'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['min'])) { $this->_invalidVersion('' . $group . $type . '', $dep['min']); } } if (isset($dep['max'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['max'])) { $this->_invalidVersion('' . $group . $type . '', $dep['max']); } } if (isset($dep['recommended'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['recommended'])) { $this->_invalidVersion('' . $group . $type . '', $dep['recommended']); } } if (isset($dep['exclude'])) { if (!is_array($dep['exclude'])) { $dep['exclude'] = array($dep['exclude']); } foreach ($dep['exclude'] as $exclude) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $exclude)) { $this->_invalidVersion('' . $group . $type . '', $exclude); } } } } function _validateSubpackageDep($dep, $group) { $this->_validatePackageDep($dep, $group, ''); if (isset($dep['providesextension'])) { $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : ''); } if (isset($dep['conflicts'])) { $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : ''); } } function _validateExtensionDep($dep, $group = false, $installcondition = false) { if (isset($dep['conflicts'])) { $structure = array( 'name', '*min', '*max', '*exclude', 'conflicts', ); } else { $structure = array( 'name', '*min', '*max', '*recommended', '*exclude', ); } if ($installcondition) { $type = ''; } else { $type = '' . $group . ''; } if (isset($dep['name'])) { $type .= '' . $dep['name'] . ''; } $this->_stupidSchemaValidate($structure, $dep, $type); if (isset($dep['min'])) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $dep['min'])) { $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '' : ''; if ($this->_stupidSchemaValidate($structure, $dep, $type)) { if ($dep['name'] == '*') { if (array_key_exists('conflicts', $dep)) { $this->_cannotConflictWithAllOs($type); } } } } function _validateArchDep($dep, $installcondition = false) { $structure = array( 'pattern', '*conflicts', ); $type = $installcondition ? '' : ''; $this->_stupidSchemaValidate($structure, $dep, $type); } function _validateInstallConditions($cond, $release) { $structure = array( '*php', '*extension', '*os', '*arch', ); if (!$this->_stupidSchemaValidate($structure, $cond, $release)) { return false; } foreach (array('php', 'extension', 'os', 'arch') as $type) { if (isset($cond[$type])) { $iter = $cond[$type]; if (!is_array($iter) || !isset($iter[0])) { $iter = array($iter); } foreach ($iter as $package) { if ($type == 'extension') { $this->{"_validate{$type}Dep"}($package, false, true); } else { $this->{"_validate{$type}Dep"}($package, true); } } } } } function _validateDependencies() { $structure = array( 'required', '*optional', '*group->name->hint' ); if (!$this->_stupidSchemaValidate($structure, $this->_packageInfo['dependencies'], '')) { return false; } foreach (array('required', 'optional') as $simpledep) { if (isset($this->_packageInfo['dependencies'][$simpledep])) { if ($simpledep == 'optional') { $structure = array( '*package', '*subpackage', '*extension', ); } else { $structure = array( 'php', 'pearinstaller', '*package', '*subpackage', '*extension', '*os', '*arch', ); } if ($this->_stupidSchemaValidate($structure, $this->_packageInfo['dependencies'][$simpledep], "<$simpledep>")) { foreach (array('package', 'subpackage', 'extension') as $type) { if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; if (!isset($iter[0])) { $iter = array($iter); } foreach ($iter as $package) { if ($type != 'extension') { if (isset($package['uri'])) { if (isset($package['channel'])) { $this->_UrlOrChannel($type, $package['name']); } } else { if (!isset($package['channel'])) { $this->_NoChannel($type, $package['name']); } } } $this->{"_validate{$type}Dep"}($package, "<$simpledep>"); } } } if ($simpledep == 'optional') { continue; } foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) { if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) { $iter = $this->_packageInfo['dependencies'][$simpledep][$type]; if (!isset($iter[0])) { $iter = array($iter); } foreach ($iter as $package) { $this->{"_validate{$type}Dep"}($package); } } } } } } if (isset($this->_packageInfo['dependencies']['group'])) { $groups = $this->_packageInfo['dependencies']['group']; if (!isset($groups[0])) { $groups = array($groups); } $structure = array( '*package', '*subpackage', '*extension', ); foreach ($groups as $group) { if ($this->_stupidSchemaValidate($structure, $group, '')) { if (!PEAR_Validate::validGroupName($group['attribs']['name'])) { $this->_invalidDepGroupName($group['attribs']['name']); } foreach (array('package', 'subpackage', 'extension') as $type) { if (isset($group[$type])) { $iter = $group[$type]; if (!isset($iter[0])) { $iter = array($iter); } foreach ($iter as $package) { if ($type != 'extension') { if (isset($package['uri'])) { if (isset($package['channel'])) { $this->_UrlOrChannelGroup($type, $package['name'], $group['name']); } } else { if (!isset($package['channel'])) { $this->_NoChannelGroup($type, $package['name'], $group['name']); } } } $this->{"_validate{$type}Dep"}($package, ''); } } } } } } } function _validateCompatible() { $compat = $this->_packageInfo['compatible']; if (!isset($compat[0])) { $compat = array($compat); } $required = array('name', 'channel', 'min', 'max', '*exclude'); foreach ($compat as $package) { $type = ''; if (is_array($package) && array_key_exists('name', $package)) { $type .= '' . $package['name'] . ''; } $this->_stupidSchemaValidate($required, $package, $type); if (is_array($package) && array_key_exists('min', $package)) { if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/', $package['min'])) { $this->_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_invalidVersion(substr($type, 1) . '_NoBundledPackages(); } if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) { return $this->_AtLeast2BundledPackages(); } foreach ($list['bundledpackage'] as $package) { if (!is_string($package)) { $this->_bundledPackagesMustBeFilename(); } } } function _validateFilelist($list = false, $allowignore = false, $dirs = '') { $iscontents = false; if (!$list) { $iscontents = true; $list = $this->_packageInfo['contents']; if (isset($this->_packageInfo['bundle'])) { return $this->_validateBundle($list); } } if ($allowignore) { $struc = array( '*install->name->as', '*ignore->name' ); } else { $struc = array( '*dir->name->?baseinstalldir', '*file->name->role->?baseinstalldir->?md5sum' ); if (isset($list['dir']) && isset($list['file'])) { // stave off validation errors without requiring a set order. $_old = $list; if (isset($list['attribs'])) { $list = array('attribs' => $_old['attribs']); } $list['dir'] = $_old['dir']; $list['file'] = $_old['file']; } } if (!isset($list['attribs']) || !isset($list['attribs']['name'])) { $unknown = $allowignore ? '' : ''; $dirname = $iscontents ? '' : $unknown; } else { $dirname = ''; if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $list['attribs']['name']))) { // file contains .. parent directory or . cur directory $this->_invalidDirName($list['attribs']['name']); } } $res = $this->_stupidSchemaValidate($struc, $list, $dirname); if ($allowignore && $res) { $ignored_or_installed = array(); $this->_pf->getFilelist(); $fcontents = $this->_pf->getContents(); $filelist = array(); if (!isset($fcontents['dir']['file'][0])) { $fcontents['dir']['file'] = array($fcontents['dir']['file']); } foreach ($fcontents['dir']['file'] as $file) { $filelist[$file['attribs']['name']] = true; } if (isset($list['install'])) { if (!isset($list['install'][0])) { $list['install'] = array($list['install']); } foreach ($list['install'] as $file) { if (!isset($filelist[$file['attribs']['name']])) { $this->_notInContents($file['attribs']['name'], 'install'); continue; } if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { $this->_multipleInstallAs($file['attribs']['name']); } if (!isset($ignored_or_installed[$file['attribs']['name']])) { $ignored_or_installed[$file['attribs']['name']] = array(); } $ignored_or_installed[$file['attribs']['name']][] = 1; if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file['attribs']['as']))) { // file contains .. parent directory or . cur directory references $this->_invalidFileInstallAs($file['attribs']['name'], $file['attribs']['as']); } } } if (isset($list['ignore'])) { if (!isset($list['ignore'][0])) { $list['ignore'] = array($list['ignore']); } foreach ($list['ignore'] as $file) { if (!isset($filelist[$file['attribs']['name']])) { $this->_notInContents($file['attribs']['name'], 'ignore'); continue; } if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) { $this->_ignoreAndInstallAs($file['attribs']['name']); } } } } if (!$allowignore && isset($list['file'])) { if (is_string($list['file'])) { $this->_oldStyleFileNotAllowed(); return false; } if (!isset($list['file'][0])) { // single file $list['file'] = array($list['file']); } foreach ($list['file'] as $i => $file) { if (isset($file['attribs']) && isset($file['attribs']['name'])) { if ($file['attribs']['name']{0} == '.' && $file['attribs']['name']{1} == '/') { // name is something like "./doc/whatever.txt" $this->_invalidFileName($file['attribs']['name'], $dirname); } if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~', str_replace('\\', '/', $file['attribs']['name']))) { // file contains .. parent directory or . cur directory $this->_invalidFileName($file['attribs']['name'], $dirname); } } if (isset($file['attribs']) && isset($file['attribs']['role'])) { if (!$this->_validateRole($file['attribs']['role'])) { if (isset($this->_packageInfo['usesrole'])) { $roles = $this->_packageInfo['usesrole']; if (!isset($roles[0])) { $roles = array($roles); } foreach ($roles as $role) { if ($role['role'] = $file['attribs']['role']) { $msg = 'This package contains role "%role%" and requires ' . 'package "%package%" to be used'; if (isset($role['uri'])) { $params = array('role' => $role['role'], 'package' => $role['uri']); } else { $params = array('role' => $role['role'], 'package' => $this->_pf->_registry-> parsedPackageNameToString(array('package' => $role['package'], 'channel' => $role['channel']), true)); } $this->_stack->push('_mustInstallRole', 'error', $params, $msg); } } } $this->_invalidFileRole($file['attribs']['name'], $dirname, $file['attribs']['role']); } } if (!isset($file['attribs'])) { continue; } $save = $file['attribs']; if ($dirs) { $save['name'] = $dirs . '/' . $save['name']; } unset($file['attribs']); if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks foreach ($file as $task => $value) { if ($tagClass = $this->_pf->getTask($task)) { if (!is_array($value) || !isset($value[0])) { $value = array($value); } foreach ($value as $v) { $ret = call_user_func(array($tagClass, 'validateXml'), $this->_pf, $v, $this->_pf->_config, $save); if (is_array($ret)) { $this->_invalidTask($task, $ret, isset($save['name']) ? $save['name'] : ''); } } } else { if (isset($this->_packageInfo['usestask'])) { $roles = $this->_packageInfo['usestask']; if (!isset($roles[0])) { $roles = array($roles); } foreach ($roles as $role) { if ($role['task'] = $task) { $msg = 'This package contains task "%task%" and requires ' . 'package "%package%" to be used'; if (isset($role['uri'])) { $params = array('task' => $role['task'], 'package' => $role['uri']); } else { $params = array('task' => $role['task'], 'package' => $this->_pf->_registry-> parsedPackageNameToString(array('package' => $role['package'], 'channel' => $role['channel']), true)); } $this->_stack->push('_mustInstallTask', 'error', $params, $msg); } } } $this->_unknownTask($task, $save['name']); } } } } } if (isset($list['ignore'])) { if (!$allowignore) { $this->_ignoreNotAllowed('ignore'); } } if (isset($list['install'])) { if (!$allowignore) { $this->_ignoreNotAllowed('install'); } } if (isset($list['file'])) { if ($allowignore) { $this->_fileNotAllowed('file'); } } if (isset($list['dir'])) { if ($allowignore) { $this->_fileNotAllowed('dir'); } else { if (!isset($list['dir'][0])) { $list['dir'] = array($list['dir']); } foreach ($list['dir'] as $dir) { if (isset($dir['attribs']) && isset($dir['attribs']['name'])) { if ($dir['attribs']['name'] == '/' || !isset($this->_packageInfo['contents']['dir']['dir'])) { // always use nothing if the filelist has already been flattened $newdirs = ''; } elseif ($dirs == '') { $newdirs = $dir['attribs']['name']; } else { $newdirs = $dirs . '/' . $dir['attribs']['name']; } } else { $newdirs = $dirs; } $this->_validateFilelist($dir, $allowignore, $newdirs); } } } } function _validateRelease() { if (isset($this->_packageInfo['phprelease'])) { $release = 'phprelease'; if (isset($this->_packageInfo['providesextension'])) { $this->_cannotProvideExtension($release); } if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { $this->_cannotHaveSrcpackage($release); } $releases = $this->_packageInfo['phprelease']; if (!is_array($releases)) { return true; } if (!isset($releases[0])) { $releases = array($releases); } foreach ($releases as $rel) { $this->_stupidSchemaValidate(array( '*installconditions', '*filelist', ), $rel, ''); } } foreach (array('', 'zend') as $prefix) { $releasetype = $prefix . 'extsrcrelease'; if (isset($this->_packageInfo[$releasetype])) { $release = $releasetype; if (!isset($this->_packageInfo['providesextension'])) { $this->_mustProvideExtension($release); } if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { $this->_cannotHaveSrcpackage($release); } $releases = $this->_packageInfo[$releasetype]; if (!is_array($releases)) { return true; } if (!isset($releases[0])) { $releases = array($releases); } foreach ($releases as $rel) { $this->_stupidSchemaValidate(array( '*installconditions', '*configureoption->name->prompt->?default', '*binarypackage', '*filelist', ), $rel, '<' . $releasetype . '>'); if (isset($rel['binarypackage'])) { if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) { $rel['binarypackage'] = array($rel['binarypackage']); } foreach ($rel['binarypackage'] as $bin) { if (!is_string($bin)) { $this->_binaryPackageMustBePackagename(); } } } } } $releasetype = 'extbinrelease'; if (isset($this->_packageInfo[$releasetype])) { $release = $releasetype; if (!isset($this->_packageInfo['providesextension'])) { $this->_mustProvideExtension($release); } if (isset($this->_packageInfo['channel']) && !isset($this->_packageInfo['srcpackage'])) { $this->_mustSrcPackage($release); } if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) { $this->_mustSrcuri($release); } $releases = $this->_packageInfo[$releasetype]; if (!is_array($releases)) { return true; } if (!isset($releases[0])) { $releases = array($releases); } foreach ($releases as $rel) { $this->_stupidSchemaValidate(array( '*installconditions', '*filelist', ), $rel, '<' . $releasetype . '>'); } } } if (isset($this->_packageInfo['bundle'])) { $release = 'bundle'; if (isset($this->_packageInfo['providesextension'])) { $this->_cannotProvideExtension($release); } if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) { $this->_cannotHaveSrcpackage($release); } $releases = $this->_packageInfo['bundle']; if (!is_array($releases) || !isset($releases[0])) { $releases = array($releases); } foreach ($releases as $rel) { $this->_stupidSchemaValidate(array( '*installconditions', '*filelist', ), $rel, ''); } } foreach ($releases as $rel) { if (is_array($rel) && array_key_exists('installconditions', $rel)) { $this->_validateInstallConditions($rel['installconditions'], "<$release>"); } if (is_array($rel) && array_key_exists('filelist', $rel)) { if ($rel['filelist']) { $this->_validateFilelist($rel['filelist'], true); } } } } /** * This is here to allow role extension through plugins * @param string */ function _validateRole($role) { return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())); } function _pearVersionTooLow($version) { $this->_stack->push(__FUNCTION__, 'error', array('version' => $version), 'This package.xml requires PEAR version %version% to parse properly, we are ' . 'version 1.9.4'); } function _invalidTagOrder($oktags, $actual, $root) { $this->_stack->push(__FUNCTION__, 'error', array('oktags' => $oktags, 'actual' => $actual, 'root' => $root), 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"'); } function _ignoreNotAllowed($type) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), '<%type%> is not allowed inside global , only inside ' . '//, use and only'); } function _fileNotAllowed($type) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), '<%type%> is not allowed inside release , only inside ' . ', use and only'); } function _oldStyleFileNotAllowed() { $this->_stack->push(__FUNCTION__, 'error', array(), 'Old-style name is not allowed. Use' . ''); } function _tagMissingAttribute($tag, $attr, $context) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'attribute' => $attr, 'context' => $context), 'tag <%tag%> in context "%context%" has no attribute "%attribute%"'); } function _tagHasNoAttribs($tag, $context) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'context' => $context), 'tag <%tag%> has no attributes in context "%context%"'); } function _invalidInternalStructure() { $this->_stack->push(__FUNCTION__, 'exception', array(), 'internal array was not generated by compatible parser, or extreme parser error, cannot continue'); } function _invalidFileRole($file, $dir, $role) { $this->_stack->push(__FUNCTION__, 'error', array( 'file' => $file, 'dir' => $dir, 'role' => $role, 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())), 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%'); } function _invalidFileName($file, $dir) { $this->_stack->push(__FUNCTION__, 'error', array( 'file' => $file), 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."'); } function _invalidFileInstallAs($file, $as) { $this->_stack->push(__FUNCTION__, 'error', array( 'file' => $file, 'as' => $as), 'File "%file%" cannot contain "./" or contain ".."'); } function _invalidDirName($dir) { $this->_stack->push(__FUNCTION__, 'error', array( 'dir' => $file), 'Directory "%dir%" cannot begin with "./" or contain ".."'); } function _filelistCannotContainFile($filelist) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), '<%tag%> can only contain , contains . Use ' . ' as the first dir element'); } function _filelistMustContainDir($filelist) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist), '<%tag%> must contain . Use as the ' . 'first dir element'); } function _tagCannotBeEmpty($tag) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), '<%tag%> cannot be empty (<%tag%/>)'); } function _UrlOrChannel($type, $name) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'name' => $name), 'Required dependency <%type%> "%name%" can have either url OR ' . 'channel attributes, and not both'); } function _NoChannel($type, $name) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'name' => $name), 'Required dependency <%type%> "%name%" must have either url OR ' . 'channel attributes'); } function _UrlOrChannelGroup($type, $name, $group) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'name' => $name, 'group' => $group), 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' . 'channel attributes, and not both'); } function _NoChannelGroup($type, $name, $group) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'name' => $name, 'group' => $group), 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' . 'channel attributes'); } function _unknownChannel($channel) { $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel), 'Unknown channel "%channel%"'); } function _noPackageVersion() { $this->_stack->push(__FUNCTION__, 'error', array(), 'package.xml tag has no version attribute, or version is not 2.0'); } function _NoBundledPackages() { $this->_stack->push(__FUNCTION__, 'error', array(), 'No tag was found in , required for bundle packages'); } function _AtLeast2BundledPackages() { $this->_stack->push(__FUNCTION__, 'error', array(), 'At least 2 packages must be bundled in a bundle package'); } function _ChannelOrUri($name) { $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), 'Bundled package "%name%" can have either a uri or a channel, not both'); } function _noChildTag($child, $tag) { $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag), 'Tag <%tag%> is missing child tag <%child%>'); } function _invalidVersion($type, $value) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value), 'Version type <%type%> is not a valid version (%value%)'); } function _invalidState($type, $value) { $states = array('stable', 'beta', 'alpha', 'devel'); if ($type != 'api') { $states[] = 'snapshot'; } if (strtolower($value) == 'rc') { $this->_stack->push(__FUNCTION__, 'error', array('version' => $this->_packageInfo['version']['release']), 'RC is not a state, it is a version postfix, try %version%RC1, stability beta'); } $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value, 'types' => $states), 'Stability type <%type%> is not a valid stability (%value%), must be one of ' . '%types%'); } function _invalidTask($task, $ret, $file) { switch ($ret[0]) { case PEAR_TASK_ERROR_MISSING_ATTRIB : $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file); $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%'; break; case PEAR_TASK_ERROR_NOATTRIBS : $info = array('task' => $task, 'file' => $file); $msg = 'task <%task%> has no attributes in file %file%'; break; case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE : $info = array('attrib' => $ret[1], 'values' => $ret[3], 'was' => $ret[2], 'task' => $task, 'file' => $file); $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '. 'in file %file%, expecting one of "%values%"'; break; case PEAR_TASK_ERROR_INVALID : $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file); $msg = 'task <%task%> in file %file% is invalid because of "%reason%"'; break; } $this->_stack->push(__FUNCTION__, 'error', $info, $msg); } function _unknownTask($task, $file) { $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file), 'Unknown task "%task%" passed in file '); } function _subpackageCannotProvideExtension($name) { $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), 'Subpackage dependency "%name%" cannot use , ' . 'only package dependencies can use this tag'); } function _subpackagesCannotConflict($name) { $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), 'Subpackage dependency "%name%" cannot use , ' . 'only package dependencies can use this tag'); } function _cannotProvideExtension($release) { $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), '<%release%> packages cannot use , only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension'); } function _mustProvideExtension($release) { $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), '<%release%> packages must use to indicate which PHP extension is provided'); } function _cannotHaveSrcpackage($release) { $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), '<%release%> packages cannot specify a source code package, only extension binaries may use the tag'); } function _mustSrcPackage($release) { $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), '/ packages must specify a source code package with '); } function _mustSrcuri($release) { $this->_stack->push(__FUNCTION__, 'error', array('release' => $release), '/ packages must specify a source code package with '); } function _uriDepsCannotHaveVersioning($type) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), '%type%: dependencies with a tag cannot have any versioning information'); } function _conflictingDepsCannotHaveVersioning($type) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), '%type%: conflicting dependencies cannot have versioning info, use to ' . 'exclude specific versions of a dependency'); } function _DepchannelCannotBeUri($type) { $this->_stack->push(__FUNCTION__, 'error', array('type' => $type), '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' . 'dependencies only'); } function _bundledPackagesMustBeFilename() { $this->_stack->push(__FUNCTION__, 'error', array(), ' tags must contain only the filename of a package release ' . 'in the bundle'); } function _binaryPackageMustBePackagename() { $this->_stack->push(__FUNCTION__, 'error', array(), ' tags must contain the name of a package that is ' . 'a compiled version of this extsrc/zendextsrc package'); } function _fileNotFound($file) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), 'File "%file%" in package.xml does not exist'); } function _notInContents($file, $tag) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag), '<%tag% name="%file%"> is invalid, file is not in '); } function _cannotValidateNoPathSet() { $this->_stack->push(__FUNCTION__, 'error', array(), 'Cannot validate files, no path to package file is set (use setPackageFile())'); } function _usesroletaskMustHaveChannelOrUri($role, $tag) { $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), '<%tag%> for role "%role%" must contain either , or and '); } function _usesroletaskMustHavePackage($role, $tag) { $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag), '<%tag%> for role "%role%" must contain '); } function _usesroletaskMustHaveRoleTask($tag, $type) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type), '<%tag%> must contain <%type%> defining the %type% to be used'); } function _cannotConflictWithAllOs($type) { $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag), '%tag% cannot conflict with all OSes'); } function _invalidDepGroupName($name) { $this->_stack->push(__FUNCTION__, 'error', array('name' => $name), 'Invalid dependency group name "%name%"'); } function _multipleToplevelDirNotAllowed() { $this->_stack->push(__FUNCTION__, 'error', array(), 'Multiple top-level tags are not allowed. Enclose them ' . 'in a '); } function _multipleInstallAs($file) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), 'Only one tag is allowed for file "%file%"'); } function _ignoreAndInstallAs($file) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), 'Cannot have both and tags for file "%file%"'); } function _analyzeBundledPackages() { if (!$this->_isValid) { return false; } if (!$this->_pf->getPackageType() == 'bundle') { return false; } if (!isset($this->_pf->_packageFile)) { return false; } $dir_prefix = dirname($this->_pf->_packageFile); $common = new PEAR_Common; $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : array($common, 'log'); $info = $this->_pf->getContents(); $info = $info['bundledpackage']; if (!is_array($info)) { $info = array($info); } $pkg = &new PEAR_PackageFile($this->_pf->_config); foreach ($info as $package) { if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) { $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package); $this->_isValid = 0; continue; } call_user_func_array($log, array(1, "Analyzing bundled package $package")); PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package, PEAR_VALIDATE_NORMAL); PEAR::popErrorHandling(); if (PEAR::isError($ret)) { call_user_func_array($log, array(0, "ERROR: package $package is not a valid " . 'package')); $inf = $ret->getUserInfo(); if (is_array($inf)) { foreach ($inf as $err) { call_user_func_array($log, array(1, $err['message'])); } } return false; } } return true; } function _analyzePhpFiles() { if (!$this->_isValid) { return false; } if (!isset($this->_pf->_packageFile)) { $this->_cannotValidateNoPathSet(); return false; } $dir_prefix = dirname($this->_pf->_packageFile); $common = new PEAR_Common; $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') : array(&$common, 'log'); $info = $this->_pf->getContents(); if (!$info || !isset($info['dir']['file'])) { $this->_tagCannotBeEmpty('contents>_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file); $this->_isValid = 0; continue; } if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) { call_user_func_array($log, array(1, "Analyzing $file")); $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file); if ($srcinfo) { $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo)); } } } $this->_packageName = $pn = $this->_pf->getPackage(); $pnl = strlen($pn); foreach ($provides as $key => $what) { if (isset($what['explicit']) || !$what) { // skip conformance checks if the provides entry is // specified in the package.xml file continue; } extract($what); if ($type == 'class') { if (!strncasecmp($name, $pn, $pnl)) { continue; } $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); } elseif ($type == 'function') { if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) { continue; } $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn), 'in %file%: %type% "%name%" not prefixed with package name "%package%"'); } } return $this->_isValid; } /** * Analyze the source code of the given PHP file * * @param string Filename of the PHP file * @param boolean whether to analyze $file as the file contents * @return mixed */ function analyzeSourceCode($file, $string = false) { if (!function_exists("token_get_all")) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer'); return false; } if (!defined('T_DOC_COMMENT')) { define('T_DOC_COMMENT', T_COMMENT); } if (!defined('T_INTERFACE')) { define('T_INTERFACE', -1); } if (!defined('T_IMPLEMENTS')) { define('T_IMPLEMENTS', -1); } if ($string) { $contents = $file; } else { if (!$fp = @fopen($file, "r")) { return false; } fclose($fp); $contents = file_get_contents($file); } // Silence this function so we can catch PHP Warnings and show our own custom message $tokens = @token_get_all($contents); if (isset($php_errormsg)) { if (isset($this->_stack)) { $pn = $this->_pf->getPackage(); $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file, 'package' => $pn), 'in %file%: Could not process file for unkown reasons,' . ' possibly a PHP parse error in %file% from %package%'); } } /* for ($i = 0; $i < sizeof($tokens); ++$i) { @list($token, $data) = $tokens[$i]; if (is_string($token)) { var_dump($token); } else { print token_name($token) . ' '; var_dump(rtrim($data)); } } */ $look_for = 0; $paren_level = 0; $bracket_level = 0; $brace_level = 0; $lastphpdoc = ''; $current_class = ''; $current_interface = ''; $current_class_level = -1; $current_function = ''; $current_function_level = -1; $declared_classes = array(); $declared_interfaces = array(); $declared_functions = array(); $declared_methods = array(); $used_classes = array(); $used_functions = array(); $extends = array(); $implements = array(); $nodeps = array(); $inquote = false; $interface = false; for ($i = 0; $i < sizeof($tokens); ++$i) { if (is_array($tokens[$i])) { list($token, $data) = $tokens[$i]; } else { $token = $tokens[$i]; $data = ''; } if ($inquote) { if ($token != '"' && $token != T_END_HEREDOC) { continue; } else { $inquote = false; continue; } } switch ($token) { case T_WHITESPACE : continue; case ';': if ($interface) { $current_function = ''; $current_function_level = -1; } break; case '"': case T_START_HEREDOC: $inquote = true; break; case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: case '{': $brace_level++; continue 2; case '}': $brace_level--; if ($current_class_level == $brace_level) { $current_class = ''; $current_class_level = -1; } if ($current_function_level == $brace_level) { $current_function = ''; $current_function_level = -1; } continue 2; case '[': $bracket_level++; continue 2; case ']': $bracket_level--; continue 2; case '(': $paren_level++; continue 2; case ')': $paren_level--; continue 2; case T_INTERFACE: $interface = true; case T_CLASS: if (($current_class_level != -1) || ($current_function_level != -1)) { if (isset($this->_stack)) { $this->_stack->push(__FUNCTION__, 'error', array('file' => $file), 'Parser error: invalid PHP found in file "%file%"'); } else { PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", PEAR_COMMON_ERROR_INVALIDPHP); } return false; } case T_FUNCTION: case T_NEW: case T_EXTENDS: case T_IMPLEMENTS: $look_for = $token; continue 2; case T_STRING: if (version_compare(zend_version(), '2.0', '<')) { if (in_array(strtolower($data), array('public', 'private', 'protected', 'abstract', 'interface', 'implements', 'throw') ) ) { if (isset($this->_stack)) { $this->_stack->push(__FUNCTION__, 'warning', array( 'file' => $file), 'Error, PHP5 token encountered in %file%,' . ' analysis should be in PHP5'); } else { PEAR::raiseError('Error: PHP5 token encountered in ' . $file . 'packaging should be done in PHP 5'); return false; } } } if ($look_for == T_CLASS) { $current_class = $data; $current_class_level = $brace_level; $declared_classes[] = $current_class; } elseif ($look_for == T_INTERFACE) { $current_interface = $data; $current_class_level = $brace_level; $declared_interfaces[] = $current_interface; } elseif ($look_for == T_IMPLEMENTS) { $implements[$current_class] = $data; } elseif ($look_for == T_EXTENDS) { $extends[$current_class] = $data; } elseif ($look_for == T_FUNCTION) { if ($current_class) { $current_function = "$current_class::$data"; $declared_methods[$current_class][] = $data; } elseif ($current_interface) { $current_function = "$current_interface::$data"; $declared_methods[$current_interface][] = $data; } else { $current_function = $data; $declared_functions[] = $current_function; } $current_function_level = $brace_level; $m = array(); } elseif ($look_for == T_NEW) { $used_classes[$data] = true; } $look_for = 0; continue 2; case T_VARIABLE: $look_for = 0; continue 2; case T_DOC_COMMENT: case T_COMMENT: if (preg_match('!^/\*\*\s!', $data)) { $lastphpdoc = $data; if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) { $nodeps = array_merge($nodeps, $m[1]); } } continue 2; case T_DOUBLE_COLON: $token = $tokens[$i - 1][0]; if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) { if (isset($this->_stack)) { $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file), 'Parser error: invalid PHP found in file "%file%"'); } else { PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"", PEAR_COMMON_ERROR_INVALIDPHP); } return false; } $class = $tokens[$i - 1][1]; if (strtolower($class) != 'parent') { $used_classes[$class] = true; } continue 2; } } return array( "source_file" => $file, "declared_classes" => $declared_classes, "declared_interfaces" => $declared_interfaces, "declared_methods" => $declared_methods, "declared_functions" => $declared_functions, "used_classes" => array_diff(array_keys($used_classes), $nodeps), "inheritance" => $extends, "implements" => $implements, ); } /** * Build a "provides" array from data returned by * analyzeSourceCode(). The format of the built array is like * this: * * array( * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'), * ... * ) * * * @param array $srcinfo array with information about a source file * as returned by the analyzeSourceCode() method. * * @return void * * @access private * */ function _buildProvidesArray($srcinfo) { if (!$this->_isValid) { return array(); } $providesret = array(); $file = basename($srcinfo['source_file']); $pn = isset($this->_pf) ? $this->_pf->getPackage() : ''; $pnl = strlen($pn); foreach ($srcinfo['declared_classes'] as $class) { $key = "class;$class"; if (isset($providesret[$key])) { continue; } $providesret[$key] = array('file'=> $file, 'type' => 'class', 'name' => $class); if (isset($srcinfo['inheritance'][$class])) { $providesret[$key]['extends'] = $srcinfo['inheritance'][$class]; } } foreach ($srcinfo['declared_methods'] as $class => $methods) { foreach ($methods as $method) { $function = "$class::$method"; $key = "function;$function"; if ($method{0} == '_' || !strcasecmp($method, $class) || isset($providesret[$key])) { continue; } $providesret[$key] = array('file'=> $file, 'type' => 'function', 'name' => $function); } } foreach ($srcinfo['declared_functions'] as $function) { $key = "function;$function"; if ($function{0} == '_' || isset($providesret[$key])) { continue; } if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) { $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\""; } $providesret[$key] = array('file'=> $file, 'type' => 'function', 'name' => $function); } return $providesret; } }