| Viewing file:  Validator.php (83.13 KB)      -rw-r--r-- Select action/file-type:
 
  (+) |  (+) |  (+) | Code (+) | Session (+) |  (+) | SDB (+) |  (+) |  (+) |  (+) |  (+) |  (+) | 
 
<?php/**
 * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
 *
 * PHP versions 4 and 5
 *
 * @category   pear
 * @package    PEAR
 * @author     Greg Beaver <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @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 <cellog@php.net>
 * @copyright  1997-2009 The Authors
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
 * @version    Release: 1.10.16
 * @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']) &&
 '1.10.16' != '@package' . '_version@' &&
 version_compare('1.10.16',
 $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, '<package>')) {
 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'], '<version>');
 $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<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 ? '<installcondition><php>' : '<dependencies><required><php>';
 $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 . '<min>', $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 . '<max>', $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>', $exclude);
 }
 }
 }
 }
 
 function _validatePearinstallerDep($dep)
 {
 $structure = array(
 'min',
 '*max',
 '*recommended',
 '*exclude',
 );
 $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
 if (isset($dep['min'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['min'])) {
 $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
 $dep['min']);
 }
 }
 if (isset($dep['max'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['max'])) {
 $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
 $dep['max']);
 }
 }
 if (isset($dep['recommended'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['recommended'])) {
 $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
 $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('<dependencies><required><pearinstaller><exclude>',
 $exclude);
 }
 }
 }
 }
 
 function _validatePackageDep($dep, $group, $type = '<package>')
 {
 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 .= '<name>' . $dep['name'] . '</name>';
 }
 $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
 if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
 isset($dep['recommended']) || isset($dep['exclude']))) {
 $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
 }
 if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
 $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
 }
 if (isset($dep['min'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['min'])) {
 $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
 }
 }
 if (isset($dep['max'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['max'])) {
 $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
 }
 }
 if (isset($dep['recommended'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['recommended'])) {
 $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
 $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('<dependencies>' . $group . $type . '<exclude>',
 $exclude);
 }
 }
 }
 }
 
 function _validateSubpackageDep($dep, $group)
 {
 $this->_validatePackageDep($dep, $group, '<subpackage>');
 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 = '<installcondition><extension>';
 } else {
 $type = '<dependencies>' . $group . '<extension>';
 }
 if (isset($dep['name'])) {
 $type .= '<name>' . $dep['name'] . '</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) . '<min', $dep['min']);
 }
 }
 if (isset($dep['max'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['max'])) {
 $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
 }
 }
 if (isset($dep['recommended'])) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $dep['recommended'])) {
 $this->_invalidVersion(substr($type, 1) . '<recommended', $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(substr($type, 1) . '<exclude', $exclude);
 }
 }
 }
 }
 
 function _validateOsDep($dep, $installcondition = false)
 {
 $structure = array(
 'name',
 '*conflicts',
 );
 $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
 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 ? '<installcondition><arch>' : '<dependencies><required><arch>';
 $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'], '<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],
 "<dependencies><$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, '<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, '<group name="' .
 $group['attribs']['name'] . '">');
 }
 }
 }
 }
 }
 }
 }
 
 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 = '<compatible>';
 if (is_array($package) && array_key_exists('name', $package)) {
 $type .= '<name>' . $package['name'] . '</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) . '<min', $package['min']);
 }
 }
 if (is_array($package) && array_key_exists('max', $package)) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $package['max'])) {
 $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
 }
 }
 if (is_array($package) && array_key_exists('exclude', $package)) {
 if (!is_array($package['exclude'])) {
 $package['exclude'] = array($package['exclude']);
 }
 foreach ($package['exclude'] as $exclude) {
 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
 $exclude)) {
 $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
 }
 }
 }
 }
 }
 
 function _validateBundle($list)
 {
 if (!is_array($list) || !isset($list['bundledpackage'])) {
 return $this->_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 ? '<filelist>' : '<dir name="*unknown*">';
 $dirname = $iscontents ? '<contents>' : $unknown;
 } else {
 $dirname = '<dir name="' . $list['attribs']['name'] . '">';
 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, '<phprelease>');
 }
 }
 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, '<bundle>');
 }
 }
 foreach ($releases as $rel) {
 if (is_array($rel) && array_key_exists('installconditions', $rel)) {
 $this->_validateInstallConditions($rel['installconditions'],
 "<$release><installconditions>");
 }
 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.10.16');
 }
 
 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 <contents>, only inside ' .
 '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
 }
 
 function _fileNotAllowed($type)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
 '<%type%> is not allowed inside release <filelist>, only inside ' .
 '<contents>, use <ignore> and <install> only');
 }
 
 function _oldStyleFileNotAllowed()
 {
 $this->_stack->push(__FUNCTION__, 'error', array(),
 'Old-style <file>name</file> is not allowed.  Use' .
 '<file name="name" role="role"/>');
 }
 
 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%" <install as="%as%"/> 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 <dir>, contains <file>.  Use ' .
 '<dir name="/"> as the first dir element');
 }
 
 function _filelistMustContainDir($filelist)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
 '<%tag%> must contain <dir>.  Use <dir name="/"> 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 <package> tag has no version attribute, or version is not 2.0');
 }
 
 function _NoBundledPackages()
 {
 $this->_stack->push(__FUNCTION__, 'error', array(),
 'No <bundledpackage> tag was found in <contents>, 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 <file name="%file%">');
 }
 
 function _subpackageCannotProvideExtension($name)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
 'Subpackage dependency "%name%" cannot use <providesextension>, ' .
 'only package dependencies can use this tag');
 }
 
 function _subpackagesCannotConflict($name)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
 'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
 'only package dependencies can use this tag');
 }
 
 function _cannotProvideExtension($release)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
 '<%release%> packages cannot use <providesextension>, 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 <providesextension> 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 <srcpackage> tag');
 }
 
 function _mustSrcPackage($release)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
 '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
 }
 
 function _mustSrcuri($release)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
 '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
 }
 
 function _uriDepsCannotHaveVersioning($type)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
 '%type%: dependencies with a <uri> 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 <exclude> 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(),
 '<bundledpackage> tags must contain only the filename of a package release ' .
 'in the bundle');
 }
 
 function _binaryPackageMustBePackagename()
 {
 $this->_stack->push(__FUNCTION__, 'error', array(),
 '<binarypackage> 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 <contents>');
 }
 
 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 <uri>, or <channel> and <package>');
 }
 
 function _usesroletaskMustHavePackage($role, $tag)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
 '<%tag%> for role "%role%" must contain <package>');
 }
 
 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 <dir> tags are not allowed.  Enclose them ' .
 'in a <dir name="/">');
 }
 
 function _multipleInstallAs($file)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
 'Only one <install> tag is allowed for file "%file%"');
 }
 
 function _ignoreAndInstallAs($file)
 {
 $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
 'Cannot have both <ignore> and <install> 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><dir');
 return false;
 }
 $info = $info['dir']['file'];
 if (isset($info['attribs'])) {
 $info = array($info);
 }
 $provides = array();
 foreach ($info as $fa) {
 $fa = $fa['attribs'];
 $file = $fa['name'];
 if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
 $this->_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 unknown 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 2;
 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 ($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 || $token == T_VARIABLE)) {
 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;
 }
 }
 
 |