text: <?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
   * @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 <cellog@php.net>
   * @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, '<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.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 <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 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;
      }
  }