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