Initial repo created
[timetracker.git] / WEB-INF / lib / pear / PEAR / PackageFile / v2 / Validator.php
1 <?php
2 /**
3  * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
4  *
5  * PHP versions 4 and 5
6  *
7  * @category   pear
8  * @package    PEAR
9  * @author     Greg Beaver <cellog@php.net>
10  * @copyright  1997-2009 The Authors
11  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12  * @version    CVS: $Id: Validator.php 313023 2011-07-06 19:17:11Z dufuz $
13  * @link       http://pear.php.net/package/PEAR
14  * @since      File available since Release 1.4.0a8
15  */
16 /**
17  * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
18  * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
19  * @category   pear
20  * @package    PEAR
21  * @author     Greg Beaver <cellog@php.net>
22  * @copyright  1997-2009 The Authors
23  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
24  * @version    Release: 1.9.4
25  * @link       http://pear.php.net/package/PEAR
26  * @since      Class available since Release 1.4.0a8
27  * @access private
28  */
29 class PEAR_PackageFile_v2_Validator
30 {
31     /**
32      * @var array
33      */
34     var $_packageInfo;
35     /**
36      * @var PEAR_PackageFile_v2
37      */
38     var $_pf;
39     /**
40      * @var PEAR_ErrorStack
41      */
42     var $_stack;
43     /**
44      * @var int
45      */
46     var $_isValid = 0;
47     /**
48      * @var int
49      */
50     var $_filesValid = 0;
51     /**
52      * @var int
53      */
54     var $_curState = 0;
55     /**
56      * @param PEAR_PackageFile_v2
57      * @param int
58      */
59     function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
60     {
61         $this->_pf = &$pf;
62         $this->_curState = $state;
63         $this->_packageInfo = $this->_pf->getArray();
64         $this->_isValid = $this->_pf->_isValid;
65         $this->_filesValid = $this->_pf->_filesValid;
66         $this->_stack = &$pf->_stack;
67         $this->_stack->getErrors(true);
68         if (($this->_isValid & $state) == $state) {
69             return true;
70         }
71         if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
72             return false;
73         }
74         if (!isset($this->_packageInfo['attribs']['version']) ||
75               ($this->_packageInfo['attribs']['version'] != '2.0' &&
76                $this->_packageInfo['attribs']['version'] != '2.1')
77         ) {
78             $this->_noPackageVersion();
79         }
80         $structure =
81         array(
82             'name',
83             'channel|uri',
84             '*extends', // can't be multiple, but this works fine
85             'summary',
86             'description',
87             '+lead', // these all need content checks
88             '*developer',
89             '*contributor',
90             '*helper',
91             'date',
92             '*time',
93             'version',
94             'stability',
95             'license->?uri->?filesource',
96             'notes',
97             'contents', //special validation needed
98             '*compatible',
99             'dependencies', //special validation needed
100             '*usesrole',
101             '*usestask', // reserve these for 1.4.0a1 to implement
102                          // this will allow a package.xml to gracefully say it
103                          // needs a certain package installed in order to implement a role or task
104             '*providesextension',
105             '*srcpackage|*srcuri',
106             '+phprelease|+extsrcrelease|+extbinrelease|' .
107                 '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
108             '*changelog',
109         );
110         $test = $this->_packageInfo;
111         if (isset($test['dependencies']) &&
112               isset($test['dependencies']['required']) &&
113               isset($test['dependencies']['required']['pearinstaller']) &&
114               isset($test['dependencies']['required']['pearinstaller']['min']) &&
115               version_compare('1.9.4',
116                 $test['dependencies']['required']['pearinstaller']['min'], '<')
117         ) {
118             $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
119             return false;
120         }
121         // ignore post-installation array fields
122         if (array_key_exists('filelist', $test)) {
123             unset($test['filelist']);
124         }
125         if (array_key_exists('_lastmodified', $test)) {
126             unset($test['_lastmodified']);
127         }
128         if (array_key_exists('#binarypackage', $test)) {
129             unset($test['#binarypackage']);
130         }
131         if (array_key_exists('old', $test)) {
132             unset($test['old']);
133         }
134         if (array_key_exists('_lastversion', $test)) {
135             unset($test['_lastversion']);
136         }
137         if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
138             return false;
139         }
140         if (empty($this->_packageInfo['name'])) {
141             $this->_tagCannotBeEmpty('name');
142         }
143         $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
144         if (empty($this->_packageInfo[$test])) {
145             $this->_tagCannotBeEmpty($test);
146         }
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');
153         }
154         if (empty($this->_packageInfo['summary'])) {
155             $this->_tagCannotBeEmpty('summary');
156         }
157         if (empty($this->_packageInfo['description'])) {
158             $this->_tagCannotBeEmpty('description');
159         }
160         if (empty($this->_packageInfo['date'])) {
161             $this->_tagCannotBeEmpty('date');
162         }
163         if (empty($this->_packageInfo['notes'])) {
164             $this->_tagCannotBeEmpty('notes');
165         }
166         if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
167             $this->_tagCannotBeEmpty('time');
168         }
169         if (isset($this->_packageInfo['dependencies'])) {
170             $this->_validateDependencies();
171         }
172         if (isset($this->_packageInfo['compatible'])) {
173             $this->_validateCompatible();
174         }
175         if (!isset($this->_packageInfo['bundle'])) {
176             if (empty($this->_packageInfo['contents'])) {
177                 $this->_tagCannotBeEmpty('contents');
178             }
179             if (!isset($this->_packageInfo['contents']['dir'])) {
180                 $this->_filelistMustContainDir('contents');
181                 return false;
182             }
183             if (isset($this->_packageInfo['contents']['file'])) {
184                 $this->_filelistCannotContainFile('contents');
185                 return false;
186             }
187         }
188         $this->_validateMaintainers();
189         $this->_validateStabilityVersion();
190         $fail = false;
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);
195             }
196             foreach ($roles as $role) {
197                 if (!isset($role['role'])) {
198                     $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
199                     $fail = true;
200                 } else {
201                     if (!isset($role['channel'])) {
202                         if (!isset($role['uri'])) {
203                             $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
204                             $fail = true;
205                         }
206                     } elseif (!isset($role['package'])) {
207                         $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
208                         $fail = true;
209                     }
210                 }
211             }
212         }
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);
217             }
218             foreach ($roles as $role) {
219                 if (!isset($role['task'])) {
220                     $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
221                     $fail = true;
222                 } else {
223                     if (!isset($role['channel'])) {
224                         if (!isset($role['uri'])) {
225                             $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
226                             $fail = true;
227                         }
228                     } elseif (!isset($role['package'])) {
229                         $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
230                         $fail = true;
231                     }
232                 }
233             }
234         }
235
236         if ($fail) {
237             return false;
238         }
239
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;
244         }
245
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());
252             } else {
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());
257                 if (!$validator) {
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;
266                 }
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%');
273                 }
274                 foreach ($failures['warnings'] as $warning) {
275                     $this->_stack->push(__FUNCTION__, 'warning', $warning,
276                         'Channel validator warning: field "%field%" - %reason%');
277                 }
278             }
279         }
280
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;
286                 } else {
287                     $this->_pf->_isValid = $this->_isValid = 0;
288                 }
289             } else {
290                 if (!$this->_analyzePhpFiles()) {
291                     $this->_pf->_isValid = $this->_isValid = 0;
292                 } else {
293                     $this->_filesValid = $this->_pf->_filesValid = true;
294                 }
295             }
296         }
297
298         if ($this->_isValid) {
299             return $this->_pf->_isValid = $this->_isValid = $state;
300         }
301
302         return $this->_pf->_isValid = $this->_isValid = 0;
303     }
304
305     function _stupidSchemaValidate($structure, $xml, $root)
306     {
307         if (!is_array($xml)) {
308             $xml = array();
309         }
310         $keys = array_keys($xml);
311         reset($keys);
312         $key = current($keys);
313         while ($key == 'attribs' || $key == '_contents') {
314             $key = next($keys);
315         }
316         $unfoundtags = $optionaltags = array();
317         $ret = true;
318         $mismatch = false;
319         foreach ($structure as $struc) {
320             if ($key) {
321                 $tag = $xml[$key];
322             }
323             $test = $this->_processStructure($struc);
324             if (isset($test['choices'])) {
325                 $loose = true;
326                 foreach ($test['choices'] as $choice) {
327                     if ($key == $choice['tag']) {
328                         $key = next($keys);
329                         while ($key == 'attribs' || $key == '_contents') {
330                             $key = next($keys);
331                         }
332                         $unfoundtags = $optionaltags = array();
333                         $mismatch = false;
334                         if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
335                             $unfoundtags[] = $choice['tag'];
336                             $optionaltags[] = $choice['tag'];
337                             if ($key) {
338                                 $mismatch = true;
339                             }
340                         }
341                         $ret &= $this->_processAttribs($choice, $tag, $root);
342                         continue 2;
343                     } else {
344                         $unfoundtags[] = $choice['tag'];
345                         $mismatch = true;
346                     }
347                     if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
348                         $loose = false;
349                     } else {
350                         $optionaltags[] = $choice['tag'];
351                     }
352                 }
353                 if (!$loose) {
354                     $this->_invalidTagOrder($unfoundtags, $key, $root);
355                     return false;
356                 }
357             } else {
358                 if ($key != $test['tag']) {
359                     if (isset($test['multiple']) && $test['multiple'] != '*') {
360                         $unfoundtags[] = $test['tag'];
361                         $this->_invalidTagOrder($unfoundtags, $key, $root);
362                         return false;
363                     } else {
364                         if ($key) {
365                             $mismatch = true;
366                         }
367                         $unfoundtags[] = $test['tag'];
368                         $optionaltags[] = $test['tag'];
369                     }
370                     if (!isset($test['multiple'])) {
371                         $this->_invalidTagOrder($unfoundtags, $key, $root);
372                         return false;
373                     }
374                     continue;
375                 } else {
376                     $unfoundtags = $optionaltags = array();
377                     $mismatch = false;
378                 }
379                 $key = next($keys);
380                 while ($key == 'attribs' || $key == '_contents') {
381                     $key = next($keys);
382                 }
383                 if ($key && $key != $test['tag'] && isset($test['multiple'])) {
384                     $unfoundtags[] = $test['tag'];
385                     $optionaltags[] = $test['tag'];
386                     $mismatch = true;
387                 }
388                 $ret &= $this->_processAttribs($test, $tag, $root);
389                 continue;
390             }
391         }
392         if (!$mismatch && count($optionaltags)) {
393             // don't error out on any optional tags
394             $unfoundtags = array_diff($unfoundtags, $optionaltags);
395         }
396         if (count($unfoundtags)) {
397             $this->_invalidTagOrder($unfoundtags, $key, $root);
398         } elseif ($key) {
399             // unknown tags
400             $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
401             while ($key = next($keys)) {
402                 $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
403             }
404         }
405         return $ret;
406     }
407
408     function _processAttribs($choice, $tag, $context)
409     {
410         if (isset($choice['attribs'])) {
411             if (!is_array($tag)) {
412                 $tag = array($tag);
413             }
414             $tags = $tag;
415             if (!isset($tags[0])) {
416                 $tags = array($tags);
417             }
418             $ret = true;
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'],
424                                 $context);
425                             continue 2;
426                         }
427                     }
428                 }
429                 foreach ($choice['attribs'] as $attrib) {
430                     if ($attrib{0} != '?') {
431                         if (!isset($tag['attribs'][$attrib])) {
432                             $ret &= $this->_tagMissingAttribute($choice['tag'],
433                                 $attrib, $context);
434                         }
435                     }
436                 }
437             }
438             return $ret;
439         }
440         return true;
441     }
442
443     function _processStructure($key)
444     {
445         $ret = array();
446         if (count($pieces = explode('|', $key)) > 1) {
447             $ret['choices'] = array();
448             foreach ($pieces as $piece) {
449                 $ret['choices'][] = $this->_processStructure($piece);
450             }
451             return $ret;
452         }
453         $multi = $key{0};
454         if ($multi == '+' || $multi == '*') {
455             $ret['multiple'] = $key{0};
456             $key = substr($key, 1);
457         }
458         if (count($attrs = explode('->', $key)) > 1) {
459             $ret['tag'] = array_shift($attrs);
460             $ret['attribs'] = $attrs;
461         } else {
462             $ret['tag'] = $key;
463         }
464         return $ret;
465     }
466
467     function _validateStabilityVersion()
468     {
469         $structure = array('release', 'api');
470         $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
471         $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
472         if ($a) {
473             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
474                   $this->_packageInfo['version']['release'])) {
475                 $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
476             }
477             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
478                   $this->_packageInfo['version']['api'])) {
479                 $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
480             }
481             if (!in_array($this->_packageInfo['stability']['release'],
482                   array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
483                 $this->_invalidState('release', $this->_packageInfo['stability']['release']);
484             }
485             if (!in_array($this->_packageInfo['stability']['api'],
486                   array('devel', 'alpha', 'beta', 'stable'))) {
487                 $this->_invalidState('api', $this->_packageInfo['stability']['api']);
488             }
489         }
490     }
491
492     function _validateMaintainers()
493     {
494         $structure =
495             array(
496                 'name',
497                 'user',
498                 'email',
499                 'active',
500             );
501         foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
502             if (!isset($this->_packageInfo[$type])) {
503                 continue;
504             }
505             if (isset($this->_packageInfo[$type][0])) {
506                 foreach ($this->_packageInfo[$type] as $lead) {
507                     $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
508                 }
509             } else {
510                 $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
511                     '<' . $type . '>');
512             }
513         }
514     }
515
516     function _validatePhpDep($dep, $installcondition = false)
517     {
518         $structure = array(
519             'min',
520             '*max',
521             '*exclude',
522         );
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/',
527                   $dep['min'])) {
528                 $this->_invalidVersion($type . '<min>', $dep['min']);
529             }
530         }
531         if (isset($dep['max'])) {
532             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
533                   $dep['max'])) {
534                 $this->_invalidVersion($type . '<max>', $dep['max']);
535             }
536         }
537         if (isset($dep['exclude'])) {
538             if (!is_array($dep['exclude'])) {
539                 $dep['exclude'] = array($dep['exclude']);
540             }
541             foreach ($dep['exclude'] as $exclude) {
542                 if (!preg_match(
543                      '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
544                      $exclude)) {
545                     $this->_invalidVersion($type . '<exclude>', $exclude);
546                 }
547             }
548         }
549     }
550
551     function _validatePearinstallerDep($dep)
552     {
553         $structure = array(
554             'min',
555             '*max',
556             '*recommended',
557             '*exclude',
558         );
559         $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
560         if (isset($dep['min'])) {
561             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
562                   $dep['min'])) {
563                 $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
564                     $dep['min']);
565             }
566         }
567         if (isset($dep['max'])) {
568             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
569                   $dep['max'])) {
570                 $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
571                     $dep['max']);
572             }
573         }
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']);
579             }
580         }
581         if (isset($dep['exclude'])) {
582             if (!is_array($dep['exclude'])) {
583                 $dep['exclude'] = array($dep['exclude']);
584             }
585             foreach ($dep['exclude'] as $exclude) {
586                 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
587                       $exclude)) {
588                     $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
589                         $exclude);
590                 }
591             }
592         }
593     }
594
595     function _validatePackageDep($dep, $group, $type = '<package>')
596     {
597         if (isset($dep['uri'])) {
598             if (isset($dep['conflicts'])) {
599                 $structure = array(
600                     'name',
601                     'uri',
602                     'conflicts',
603                     '*providesextension',
604                 );
605             } else {
606                 $structure = array(
607                     'name',
608                     'uri',
609                     '*providesextension',
610                 );
611             }
612         } else {
613             if (isset($dep['conflicts'])) {
614                 $structure = array(
615                     'name',
616                     'channel',
617                     '*min',
618                     '*max',
619                     '*exclude',
620                     'conflicts',
621                     '*providesextension',
622                 );
623             } else {
624                 $structure = array(
625                     'name',
626                     'channel',
627                     '*min',
628                     '*max',
629                     '*recommended',
630                     '*exclude',
631                     '*nodefault',
632                     '*providesextension',
633                 );
634             }
635         }
636         if (isset($dep['name'])) {
637             $type .= '<name>' . $dep['name'] . '</name>';
638         }
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);
643         }
644         if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
645             $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
646         }
647         if (isset($dep['min'])) {
648             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
649                   $dep['min'])) {
650                 $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
651             }
652         }
653         if (isset($dep['max'])) {
654             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
655                   $dep['max'])) {
656                 $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
657             }
658         }
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']);
664             }
665         }
666         if (isset($dep['exclude'])) {
667             if (!is_array($dep['exclude'])) {
668                 $dep['exclude'] = array($dep['exclude']);
669             }
670             foreach ($dep['exclude'] as $exclude) {
671                 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
672                       $exclude)) {
673                     $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
674                         $exclude);
675                 }
676             }
677         }
678     }
679
680     function _validateSubpackageDep($dep, $group)
681     {
682         $this->_validatePackageDep($dep, $group, '<subpackage>');
683         if (isset($dep['providesextension'])) {
684             $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
685         }
686         if (isset($dep['conflicts'])) {
687             $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
688         }
689     }
690
691     function _validateExtensionDep($dep, $group = false, $installcondition = false)
692     {
693         if (isset($dep['conflicts'])) {
694             $structure = array(
695                 'name',
696                 '*min',
697                 '*max',
698                 '*exclude',
699                 'conflicts',
700             );
701         } else {
702             $structure = array(
703                 'name',
704                 '*min',
705                 '*max',
706                 '*recommended',
707                 '*exclude',
708             );
709         }
710         if ($installcondition) {
711             $type = '<installcondition><extension>';
712         } else {
713             $type = '<dependencies>' . $group . '<extension>';
714         }
715         if (isset($dep['name'])) {
716             $type .= '<name>' . $dep['name'] . '</name>';
717         }
718         $this->_stupidSchemaValidate($structure, $dep, $type);
719         if (isset($dep['min'])) {
720             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
721                   $dep['min'])) {
722                 $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
723             }
724         }
725         if (isset($dep['max'])) {
726             if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
727                   $dep['max'])) {
728                 $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
729             }
730         }
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']);
735             }
736         }
737         if (isset($dep['exclude'])) {
738             if (!is_array($dep['exclude'])) {
739                 $dep['exclude'] = array($dep['exclude']);
740             }
741             foreach ($dep['exclude'] as $exclude) {
742                 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
743                       $exclude)) {
744                     $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
745                 }
746             }
747         }
748     }
749
750     function _validateOsDep($dep, $installcondition = false)
751     {
752         $structure = array(
753             'name',
754             '*conflicts',
755         );
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);
761                 }
762             }
763         }
764     }
765
766     function _validateArchDep($dep, $installcondition = false)
767     {
768         $structure = array(
769             'pattern',
770             '*conflicts',
771         );
772         $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
773         $this->_stupidSchemaValidate($structure, $dep, $type);
774     }
775
776     function _validateInstallConditions($cond, $release)
777     {
778         $structure = array(
779             '*php',
780             '*extension',
781             '*os',
782             '*arch',
783         );
784         if (!$this->_stupidSchemaValidate($structure,
785               $cond, $release)) {
786             return false;
787         }
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);
793                 }
794                 foreach ($iter as $package) {
795                     if ($type == 'extension') {
796                         $this->{"_validate{$type}Dep"}($package, false, true);
797                     } else {
798                         $this->{"_validate{$type}Dep"}($package, true);
799                     }
800                 }
801             }
802         }
803     }
804
805     function _validateDependencies()
806     {
807         $structure = array(
808             'required',
809             '*optional',
810             '*group->name->hint'
811         );
812         if (!$this->_stupidSchemaValidate($structure,
813               $this->_packageInfo['dependencies'], '<dependencies>')) {
814             return false;
815         }
816         foreach (array('required', 'optional') as $simpledep) {
817             if (isset($this->_packageInfo['dependencies'][$simpledep])) {
818                 if ($simpledep == 'optional') {
819                     $structure = array(
820                         '*package',
821                         '*subpackage',
822                         '*extension',
823                     );
824                 } else {
825                     $structure = array(
826                         'php',
827                         'pearinstaller',
828                         '*package',
829                         '*subpackage',
830                         '*extension',
831                         '*os',
832                         '*arch',
833                     );
834                 }
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);
843                             }
844                             foreach ($iter as $package) {
845                                 if ($type != 'extension') {
846                                     if (isset($package['uri'])) {
847                                         if (isset($package['channel'])) {
848                                             $this->_UrlOrChannel($type,
849                                                 $package['name']);
850                                         }
851                                     } else {
852                                         if (!isset($package['channel'])) {
853                                             $this->_NoChannel($type, $package['name']);
854                                         }
855                                     }
856                                 }
857                                 $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
858                             }
859                         }
860                     }
861                     if ($simpledep == 'optional') {
862                         continue;
863                     }
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);
869                             }
870                             foreach ($iter as $package) {
871                                 $this->{"_validate{$type}Dep"}($package);
872                             }
873                         }
874                     }
875                 }
876             }
877         }
878         if (isset($this->_packageInfo['dependencies']['group'])) {
879             $groups = $this->_packageInfo['dependencies']['group'];
880             if (!isset($groups[0])) {
881                 $groups = array($groups);
882             }
883             $structure = array(
884                 '*package',
885                 '*subpackage',
886                 '*extension',
887             );
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']);
892                     }
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);
898                             }
899                             foreach ($iter as $package) {
900                                 if ($type != 'extension') {
901                                     if (isset($package['uri'])) {
902                                         if (isset($package['channel'])) {
903                                             $this->_UrlOrChannelGroup($type,
904                                                 $package['name'],
905                                                 $group['name']);
906                                         }
907                                     } else {
908                                         if (!isset($package['channel'])) {
909                                             $this->_NoChannelGroup($type,
910                                                 $package['name'],
911                                                 $group['name']);
912                                         }
913                                     }
914                                 }
915                                 $this->{"_validate{$type}Dep"}($package, '<group name="' .
916                                     $group['attribs']['name'] . '">');
917                             }
918                         }
919                     }
920                 }
921             }
922         }
923     }
924
925     function _validateCompatible()
926     {
927         $compat = $this->_packageInfo['compatible'];
928         if (!isset($compat[0])) {
929             $compat = array($compat);
930         }
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>';
936             }
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/',
940                       $package['min'])) {
941                     $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
942                 }
943             }
944             if (is_array($package) && array_key_exists('max', $package)) {
945                 if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
946                       $package['max'])) {
947                     $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
948                 }
949             }
950             if (is_array($package) && array_key_exists('exclude', $package)) {
951                 if (!is_array($package['exclude'])) {
952                     $package['exclude'] = array($package['exclude']);
953                 }
954                 foreach ($package['exclude'] as $exclude) {
955                     if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
956                           $exclude)) {
957                         $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
958                     }
959                 }
960             }
961         }
962     }
963
964     function _validateBundle($list)
965     {
966         if (!is_array($list) || !isset($list['bundledpackage'])) {
967             return $this->_NoBundledPackages();
968         }
969         if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
970             return $this->_AtLeast2BundledPackages();
971         }
972         foreach ($list['bundledpackage'] as $package) {
973             if (!is_string($package)) {
974                 $this->_bundledPackagesMustBeFilename();
975             }
976         }
977     }
978
979     function _validateFilelist($list = false, $allowignore = false, $dirs = '')
980     {
981         $iscontents = false;
982         if (!$list) {
983             $iscontents = true;
984             $list = $this->_packageInfo['contents'];
985             if (isset($this->_packageInfo['bundle'])) {
986                 return $this->_validateBundle($list);
987             }
988         }
989         if ($allowignore) {
990             $struc = array(
991                 '*install->name->as',
992                 '*ignore->name'
993             );
994         } else {
995             $struc = array(
996                 '*dir->name->?baseinstalldir',
997                 '*file->name->role->?baseinstalldir->?md5sum'
998             );
999             if (isset($list['dir']) && isset($list['file'])) {
1000                 // stave off validation errors without requiring a set order.
1001                 $_old = $list;
1002                 if (isset($list['attribs'])) {
1003                     $list = array('attribs' => $_old['attribs']);
1004                 }
1005                 $list['dir'] = $_old['dir'];
1006                 $list['file'] = $_old['file'];
1007             }
1008         }
1009         if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
1010             $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
1011             $dirname = $iscontents ? '<contents>' : $unknown;
1012         } else {
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']);
1018             }
1019         }
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']);
1028             }
1029             foreach ($fcontents['dir']['file'] as $file) {
1030                 $filelist[$file['attribs']['name']] = true;
1031             }
1032             if (isset($list['install'])) {
1033                 if (!isset($list['install'][0])) {
1034                     $list['install'] = array($list['install']);
1035                 }
1036                 foreach ($list['install'] as $file) {
1037                     if (!isset($filelist[$file['attribs']['name']])) {
1038                         $this->_notInContents($file['attribs']['name'], 'install');
1039                         continue;
1040                     }
1041                     if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1042                         $this->_multipleInstallAs($file['attribs']['name']);
1043                     }
1044                     if (!isset($ignored_or_installed[$file['attribs']['name']])) {
1045                         $ignored_or_installed[$file['attribs']['name']] = array();
1046                     }
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']);
1053                     }
1054                 }
1055             }
1056             if (isset($list['ignore'])) {
1057                 if (!isset($list['ignore'][0])) {
1058                     $list['ignore'] = array($list['ignore']);
1059                 }
1060                 foreach ($list['ignore'] as $file) {
1061                     if (!isset($filelist[$file['attribs']['name']])) {
1062                         $this->_notInContents($file['attribs']['name'], 'ignore');
1063                         continue;
1064                     }
1065                     if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1066                         $this->_ignoreAndInstallAs($file['attribs']['name']);
1067                     }
1068                 }
1069             }
1070         }
1071         if (!$allowignore && isset($list['file'])) {
1072             if (is_string($list['file'])) {
1073                 $this->_oldStyleFileNotAllowed();
1074                 return false;
1075             }
1076             if (!isset($list['file'][0])) {
1077                 // single file
1078                 $list['file'] = array($list['file']);
1079             }
1080             foreach ($list['file'] as $i => $file)
1081             {
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);
1087                     }
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);
1092                     }
1093                 }
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);
1100                             }
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']);
1108                                     } else {
1109                                         $params = array('role' => $role['role'],
1110                                             'package' => $this->_pf->_registry->
1111                                             parsedPackageNameToString(array('package' =>
1112                                                 $role['package'], 'channel' => $role['channel']),
1113                                                 true));
1114                                     }
1115                                     $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
1116                                 }
1117                             }
1118                         }
1119                         $this->_invalidFileRole($file['attribs']['name'],
1120                             $dirname, $file['attribs']['role']);
1121                     }
1122                 }
1123                 if (!isset($file['attribs'])) {
1124                     continue;
1125                 }
1126                 $save = $file['attribs'];
1127                 if ($dirs) {
1128                     $save['name'] = $dirs . '/' . $save['name'];
1129                 }
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);
1136                             }
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'] : '');
1143                                 }
1144                             }
1145                         } else {
1146                             if (isset($this->_packageInfo['usestask'])) {
1147                                 $roles = $this->_packageInfo['usestask'];
1148                                 if (!isset($roles[0])) {
1149                                     $roles = array($roles);
1150                                 }
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']);
1158                                         } else {
1159                                             $params = array('task' => $role['task'],
1160                                                 'package' => $this->_pf->_registry->
1161                                                 parsedPackageNameToString(array('package' =>
1162                                                     $role['package'], 'channel' => $role['channel']),
1163                                                     true));
1164                                         }
1165                                         $this->_stack->push('_mustInstallTask', 'error',
1166                                             $params, $msg);
1167                                     }
1168                                 }
1169                             }
1170                             $this->_unknownTask($task, $save['name']);
1171                         }
1172                     }
1173                 }
1174             }
1175         }
1176         if (isset($list['ignore'])) {
1177             if (!$allowignore) {
1178                 $this->_ignoreNotAllowed('ignore');
1179             }
1180         }
1181         if (isset($list['install'])) {
1182             if (!$allowignore) {
1183                 $this->_ignoreNotAllowed('install');
1184             }
1185         }
1186         if (isset($list['file'])) {
1187             if ($allowignore) {
1188                 $this->_fileNotAllowed('file');
1189             }
1190         }
1191         if (isset($list['dir'])) {
1192             if ($allowignore) {
1193                 $this->_fileNotAllowed('dir');
1194             } else {
1195                 if (!isset($list['dir'][0])) {
1196                     $list['dir'] = array($list['dir']);
1197                 }
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
1203                             $newdirs = '';
1204                         } elseif ($dirs == '') {
1205                             $newdirs = $dir['attribs']['name'];
1206                         } else {
1207                             $newdirs = $dirs . '/' . $dir['attribs']['name'];
1208                         }
1209                     } else {
1210                         $newdirs = $dirs;
1211                     }
1212                     $this->_validateFilelist($dir, $allowignore, $newdirs);
1213                 }
1214             }
1215         }
1216     }
1217
1218     function _validateRelease()
1219     {
1220         if (isset($this->_packageInfo['phprelease'])) {
1221             $release = 'phprelease';
1222             if (isset($this->_packageInfo['providesextension'])) {
1223                 $this->_cannotProvideExtension($release);
1224             }
1225             if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1226                 $this->_cannotHaveSrcpackage($release);
1227             }
1228             $releases = $this->_packageInfo['phprelease'];
1229             if (!is_array($releases)) {
1230                 return true;
1231             }
1232             if (!isset($releases[0])) {
1233                 $releases = array($releases);
1234             }
1235             foreach ($releases as $rel) {
1236                 $this->_stupidSchemaValidate(array(
1237                     '*installconditions',
1238                     '*filelist',
1239                 ), $rel, '<phprelease>');
1240             }
1241         }
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);
1248                 }
1249                 if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1250                     $this->_cannotHaveSrcpackage($release);
1251                 }
1252                 $releases = $this->_packageInfo[$releasetype];
1253                 if (!is_array($releases)) {
1254                     return true;
1255                 }
1256                 if (!isset($releases[0])) {
1257                     $releases = array($releases);
1258                 }
1259                 foreach ($releases as $rel) {
1260                     $this->_stupidSchemaValidate(array(
1261                         '*installconditions',
1262                         '*configureoption->name->prompt->?default',
1263                         '*binarypackage',
1264                         '*filelist',
1265                     ), $rel, '<' . $releasetype . '>');
1266                     if (isset($rel['binarypackage'])) {
1267                         if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
1268                             $rel['binarypackage'] = array($rel['binarypackage']);
1269                         }
1270                         foreach ($rel['binarypackage'] as $bin) {
1271                             if (!is_string($bin)) {
1272                                 $this->_binaryPackageMustBePackagename();
1273                             }
1274                         }
1275                     }
1276                 }
1277             }
1278             $releasetype = 'extbinrelease';
1279             if (isset($this->_packageInfo[$releasetype])) {
1280                 $release = $releasetype;
1281                 if (!isset($this->_packageInfo['providesextension'])) {
1282                     $this->_mustProvideExtension($release);
1283                 }
1284                 if (isset($this->_packageInfo['channel']) &&
1285                       !isset($this->_packageInfo['srcpackage'])) {
1286                     $this->_mustSrcPackage($release);
1287                 }
1288                 if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
1289                     $this->_mustSrcuri($release);
1290                 }
1291                 $releases = $this->_packageInfo[$releasetype];
1292                 if (!is_array($releases)) {
1293                     return true;
1294                 }
1295                 if (!isset($releases[0])) {
1296                     $releases = array($releases);
1297                 }
1298                 foreach ($releases as $rel) {
1299                     $this->_stupidSchemaValidate(array(
1300                         '*installconditions',
1301                         '*filelist',
1302                     ), $rel, '<' . $releasetype . '>');
1303                 }
1304             }
1305         }
1306         if (isset($this->_packageInfo['bundle'])) {
1307             $release = 'bundle';
1308             if (isset($this->_packageInfo['providesextension'])) {
1309                 $this->_cannotProvideExtension($release);
1310             }
1311             if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1312                 $this->_cannotHaveSrcpackage($release);
1313             }
1314             $releases = $this->_packageInfo['bundle'];
1315             if (!is_array($releases) || !isset($releases[0])) {
1316                 $releases = array($releases);
1317             }
1318             foreach ($releases as $rel) {
1319                 $this->_stupidSchemaValidate(array(
1320                     '*installconditions',
1321                     '*filelist',
1322                 ), $rel, '<bundle>');
1323             }
1324         }
1325         foreach ($releases as $rel) {
1326             if (is_array($rel) && array_key_exists('installconditions', $rel)) {
1327                 $this->_validateInstallConditions($rel['installconditions'],
1328                     "<$release><installconditions>");
1329             }
1330             if (is_array($rel) && array_key_exists('filelist', $rel)) {
1331                 if ($rel['filelist']) {
1332
1333                     $this->_validateFilelist($rel['filelist'], true);
1334                 }
1335             }
1336         }
1337     }
1338
1339     /**
1340      * This is here to allow role extension through plugins
1341      * @param string
1342      */
1343     function _validateRole($role)
1344     {
1345         return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
1346     }
1347
1348     function _pearVersionTooLow($version)
1349     {
1350         $this->_stack->push(__FUNCTION__, 'error',
1351             array('version' => $version),
1352             'This package.xml requires PEAR version %version% to parse properly, we are ' .
1353             'version 1.9.4');
1354     }
1355
1356     function _invalidTagOrder($oktags, $actual, $root)
1357     {
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%"');
1361     }
1362
1363     function _ignoreNotAllowed($type)
1364     {
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');
1368     }
1369
1370     function _fileNotAllowed($type)
1371     {
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');
1375     }
1376
1377     function _oldStyleFileNotAllowed()
1378     {
1379         $this->_stack->push(__FUNCTION__, 'error', array(),
1380             'Old-style <file>name</file> is not allowed.  Use' .
1381             '<file name="name" role="role"/>');
1382     }
1383
1384     function _tagMissingAttribute($tag, $attr, $context)
1385     {
1386         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1387             'attribute' => $attr, 'context' => $context),
1388             'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
1389     }
1390
1391     function _tagHasNoAttribs($tag, $context)
1392     {
1393         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1394             'context' => $context),
1395             'tag <%tag%> has no attributes in context "%context%"');
1396     }
1397
1398     function _invalidInternalStructure()
1399     {
1400         $this->_stack->push(__FUNCTION__, 'exception', array(),
1401             'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
1402     }
1403
1404     function _invalidFileRole($file, $dir, $role)
1405     {
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%');
1410     }
1411
1412     function _invalidFileName($file, $dir)
1413     {
1414         $this->_stack->push(__FUNCTION__, 'error', array(
1415             'file' => $file),
1416             'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
1417     }
1418
1419     function _invalidFileInstallAs($file, $as)
1420     {
1421         $this->_stack->push(__FUNCTION__, 'error', array(
1422             'file' => $file, 'as' => $as),
1423             'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
1424     }
1425
1426     function _invalidDirName($dir)
1427     {
1428         $this->_stack->push(__FUNCTION__, 'error', array(
1429             'dir' => $file),
1430             'Directory "%dir%" cannot begin with "./" or contain ".."');
1431     }
1432
1433     function _filelistCannotContainFile($filelist)
1434     {
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');
1438     }
1439
1440     function _filelistMustContainDir($filelist)
1441     {
1442         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
1443             '<%tag%> must contain <dir>.  Use <dir name="/"> as the ' .
1444             'first dir element');
1445     }
1446
1447     function _tagCannotBeEmpty($tag)
1448     {
1449         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1450             '<%tag%> cannot be empty (<%tag%/>)');
1451     }
1452
1453     function _UrlOrChannel($type, $name)
1454     {
1455         $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1456             'name' => $name),
1457             'Required dependency <%type%> "%name%" can have either url OR ' .
1458             'channel attributes, and not both');
1459     }
1460
1461     function _NoChannel($type, $name)
1462     {
1463         $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1464             'name' => $name),
1465             'Required dependency <%type%> "%name%" must have either url OR ' .
1466             'channel attributes');
1467     }
1468
1469     function _UrlOrChannelGroup($type, $name, $group)
1470     {
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');
1475     }
1476
1477     function _NoChannelGroup($type, $name, $group)
1478     {
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');
1483     }
1484
1485     function _unknownChannel($channel)
1486     {
1487         $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
1488             'Unknown channel "%channel%"');
1489     }
1490
1491     function _noPackageVersion()
1492     {
1493         $this->_stack->push(__FUNCTION__, 'error', array(),
1494             'package.xml <package> tag has no version attribute, or version is not 2.0');
1495     }
1496
1497     function _NoBundledPackages()
1498     {
1499         $this->_stack->push(__FUNCTION__, 'error', array(),
1500             'No <bundledpackage> tag was found in <contents>, required for bundle packages');
1501     }
1502
1503     function _AtLeast2BundledPackages()
1504     {
1505         $this->_stack->push(__FUNCTION__, 'error', array(),
1506             'At least 2 packages must be bundled in a bundle package');
1507     }
1508
1509     function _ChannelOrUri($name)
1510     {
1511         $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1512             'Bundled package "%name%" can have either a uri or a channel, not both');
1513     }
1514
1515     function _noChildTag($child, $tag)
1516     {
1517         $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
1518             'Tag <%tag%> is missing child tag <%child%>');
1519     }
1520
1521     function _invalidVersion($type, $value)
1522     {
1523         $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
1524             'Version type <%type%> is not a valid version (%value%)');
1525     }
1526
1527     function _invalidState($type, $value)
1528     {
1529         $states = array('stable', 'beta', 'alpha', 'devel');
1530         if ($type != 'api') {
1531             $states[] = 'snapshot';
1532         }
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');
1537         }
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 ' .
1541             '%types%');
1542     }
1543
1544     function _invalidTask($task, $ret, $file)
1545     {
1546         switch ($ret[0]) {
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%';
1550             break;
1551             case PEAR_TASK_ERROR_NOATTRIBS :
1552                 $info = array('task' => $task, 'file' => $file);
1553                 $msg = 'task <%task%> has no attributes in file %file%';
1554             break;
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%"';
1560             break;
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%"';
1564             break;
1565         }
1566         $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
1567     }
1568
1569     function _unknownTask($task, $file)
1570     {
1571         $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
1572             'Unknown task "%task%" passed in file <file name="%file%">');
1573     }
1574
1575     function _subpackageCannotProvideExtension($name)
1576     {
1577         $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1578             'Subpackage dependency "%name%" cannot use <providesextension>, ' .
1579             'only package dependencies can use this tag');
1580     }
1581
1582     function _subpackagesCannotConflict($name)
1583     {
1584         $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1585             'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
1586             'only package dependencies can use this tag');
1587     }
1588
1589     function _cannotProvideExtension($release)
1590     {
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');
1593     }
1594
1595     function _mustProvideExtension($release)
1596     {
1597         $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1598             '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
1599     }
1600
1601     function _cannotHaveSrcpackage($release)
1602     {
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');
1605     }
1606
1607     function _mustSrcPackage($release)
1608     {
1609         $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1610             '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
1611     }
1612
1613     function _mustSrcuri($release)
1614     {
1615         $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1616             '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
1617     }
1618
1619     function _uriDepsCannotHaveVersioning($type)
1620     {
1621         $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1622             '%type%: dependencies with a <uri> tag cannot have any versioning information');
1623     }
1624
1625     function _conflictingDepsCannotHaveVersioning($type)
1626     {
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');
1630     }
1631
1632     function _DepchannelCannotBeUri($type)
1633     {
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');
1637     }
1638
1639     function _bundledPackagesMustBeFilename()
1640     {
1641         $this->_stack->push(__FUNCTION__, 'error', array(),
1642             '<bundledpackage> tags must contain only the filename of a package release ' .
1643             'in the bundle');
1644     }
1645
1646     function _binaryPackageMustBePackagename()
1647     {
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');
1651     }
1652
1653     function _fileNotFound($file)
1654     {
1655         $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1656             'File "%file%" in package.xml does not exist');
1657     }
1658
1659     function _notInContents($file, $tag)
1660     {
1661         $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
1662             '<%tag% name="%file%"> is invalid, file is not in <contents>');
1663     }
1664
1665     function _cannotValidateNoPathSet()
1666     {
1667         $this->_stack->push(__FUNCTION__, 'error', array(),
1668             'Cannot validate files, no path to package file is set (use setPackageFile())');
1669     }
1670
1671     function _usesroletaskMustHaveChannelOrUri($role, $tag)
1672     {
1673         $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1674             '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
1675     }
1676
1677     function _usesroletaskMustHavePackage($role, $tag)
1678     {
1679         $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1680             '<%tag%> for role "%role%" must contain <package>');
1681     }
1682
1683     function _usesroletaskMustHaveRoleTask($tag, $type)
1684     {
1685         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
1686             '<%tag%> must contain <%type%> defining the %type% to be used');
1687     }
1688
1689     function _cannotConflictWithAllOs($type)
1690     {
1691         $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1692             '%tag% cannot conflict with all OSes');
1693     }
1694
1695     function _invalidDepGroupName($name)
1696     {
1697         $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1698             'Invalid dependency group name "%name%"');
1699     }
1700
1701     function _multipleToplevelDirNotAllowed()
1702     {
1703         $this->_stack->push(__FUNCTION__, 'error', array(),
1704             'Multiple top-level <dir> tags are not allowed.  Enclose them ' .
1705                 'in a <dir name="/">');
1706     }
1707
1708     function _multipleInstallAs($file)
1709     {
1710         $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1711             'Only one <install> tag is allowed for file "%file%"');
1712     }
1713
1714     function _ignoreAndInstallAs($file)
1715     {
1716         $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1717             'Cannot have both <ignore> and <install> tags for file "%file%"');
1718     }
1719
1720     function _analyzeBundledPackages()
1721     {
1722         if (!$this->_isValid) {
1723             return false;
1724         }
1725         if (!$this->_pf->getPackageType() == 'bundle') {
1726             return false;
1727         }
1728         if (!isset($this->_pf->_packageFile)) {
1729             return false;
1730         }
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);
1739         }
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;
1745                 continue;
1746             }
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 " .
1754                     'package'));
1755                 $inf = $ret->getUserInfo();
1756                 if (is_array($inf)) {
1757                     foreach ($inf as $err) {
1758                         call_user_func_array($log, array(1, $err['message']));
1759                     }
1760                 }
1761                 return false;
1762             }
1763         }
1764         return true;
1765     }
1766
1767     function _analyzePhpFiles()
1768     {
1769         if (!$this->_isValid) {
1770             return false;
1771         }
1772         if (!isset($this->_pf->_packageFile)) {
1773             $this->_cannotValidateNoPathSet();
1774             return false;
1775         }
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');
1783             return false;
1784         }
1785         $info = $info['dir']['file'];
1786         if (isset($info['attribs'])) {
1787             $info = array($info);
1788         }
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;
1796                 continue;
1797             }
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);
1801                 if ($srcinfo) {
1802                     $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
1803                 }
1804             }
1805         }
1806         $this->_packageName = $pn = $this->_pf->getPackage();
1807         $pnl = strlen($pn);
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
1812                 continue;
1813             }
1814             extract($what);
1815             if ($type == 'class') {
1816                 if (!strncasecmp($name, $pn, $pnl)) {
1817                     continue;
1818                 }
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)) {
1824                     continue;
1825                 }
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%"');
1829             }
1830         }
1831         return $this->_isValid;
1832     }
1833
1834     /**
1835      * Analyze the source code of the given PHP file
1836      *
1837      * @param  string Filename of the PHP file
1838      * @param  boolean whether to analyze $file as the file contents
1839      * @return mixed
1840      */
1841     function analyzeSourceCode($file, $string = false)
1842     {
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');
1846             return false;
1847         }
1848
1849         if (!defined('T_DOC_COMMENT')) {
1850             define('T_DOC_COMMENT', T_COMMENT);
1851         }
1852
1853         if (!defined('T_INTERFACE')) {
1854             define('T_INTERFACE', -1);
1855         }
1856
1857         if (!defined('T_IMPLEMENTS')) {
1858             define('T_IMPLEMENTS', -1);
1859         }
1860
1861         if ($string) {
1862             $contents = $file;
1863         } else {
1864             if (!$fp = @fopen($file, "r")) {
1865                 return false;
1866             }
1867             fclose($fp);
1868             $contents = file_get_contents($file);
1869         }
1870
1871         // Silence this function so we can catch PHP Warnings and show our own custom message
1872         $tokens = @token_get_all($contents);
1873         if (isset($php_errormsg)) {
1874             if (isset($this->_stack)) {
1875                 $pn = $this->_pf->getPackage();
1876                 $this->_stack->push(__FUNCTION__, 'warning',
1877                         array('file' => $file, 'package' => $pn),
1878                         'in %file%: Could not process file for unkown reasons,' .
1879                         ' possibly a PHP parse error in %file% from %package%');
1880             }
1881         }
1882 /*
1883         for ($i = 0; $i < sizeof($tokens); $i++) {
1884             @list($token, $data) = $tokens[$i];
1885             if (is_string($token)) {
1886                 var_dump($token);
1887             } else {
1888                 print token_name($token) . ' ';
1889                 var_dump(rtrim($data));
1890             }
1891         }
1892 */
1893         $look_for = 0;
1894         $paren_level = 0;
1895         $bracket_level = 0;
1896         $brace_level = 0;
1897         $lastphpdoc = '';
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();
1909         $extends = array();
1910         $implements = array();
1911         $nodeps = array();
1912         $inquote = false;
1913         $interface = false;
1914         for ($i = 0; $i < sizeof($tokens); $i++) {
1915             if (is_array($tokens[$i])) {
1916                 list($token, $data) = $tokens[$i];
1917             } else {
1918                 $token = $tokens[$i];
1919                 $data = '';
1920             }
1921
1922             if ($inquote) {
1923                 if ($token != '"' && $token != T_END_HEREDOC) {
1924                     continue;
1925                 } else {
1926                     $inquote = false;
1927                     continue;
1928                 }
1929             }
1930
1931             switch ($token) {
1932                 case T_WHITESPACE :
1933                     continue;
1934                 case ';':
1935                     if ($interface) {
1936                         $current_function = '';
1937                         $current_function_level = -1;
1938                     }
1939                     break;
1940                 case '"':
1941                 case T_START_HEREDOC:
1942                     $inquote = true;
1943                     break;
1944                 case T_CURLY_OPEN:
1945                 case T_DOLLAR_OPEN_CURLY_BRACES:
1946                 case '{': $brace_level++; continue 2;
1947                 case '}':
1948                     $brace_level--;
1949                     if ($current_class_level == $brace_level) {
1950                         $current_class = '';
1951                         $current_class_level = -1;
1952                     }
1953                     if ($current_function_level == $brace_level) {
1954                         $current_function = '';
1955                         $current_function_level = -1;
1956                     }
1957                     continue 2;
1958                 case '[': $bracket_level++; continue 2;
1959                 case ']': $bracket_level--; continue 2;
1960                 case '(': $paren_level++;   continue 2;
1961                 case ')': $paren_level--;   continue 2;
1962                 case T_INTERFACE:
1963                     $interface = true;
1964                 case T_CLASS:
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%"');
1969                         } else {
1970                             PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
1971                                 PEAR_COMMON_ERROR_INVALIDPHP);
1972                         }
1973
1974                         return false;
1975                     }
1976                 case T_FUNCTION:
1977                 case T_NEW:
1978                 case T_EXTENDS:
1979                 case T_IMPLEMENTS:
1980                     $look_for = $token;
1981                     continue 2;
1982                 case T_STRING:
1983                     if (version_compare(zend_version(), '2.0', '<')) {
1984                         if (in_array(strtolower($data),
1985                             array('public', 'private', 'protected', 'abstract',
1986                                   'interface', 'implements', 'throw')
1987                                  )
1988                         ) {
1989                             if (isset($this->_stack)) {
1990                                 $this->_stack->push(__FUNCTION__, 'warning', array(
1991                                     'file' => $file),
1992                                     'Error, PHP5 token encountered in %file%,' .
1993                                     ' analysis should be in PHP5');
1994                             } else {
1995                                 PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
1996                                     'packaging should be done in PHP 5');
1997                                 return false;
1998                             }
1999                         }
2000                     }
2001
2002                     if ($look_for == T_CLASS) {
2003                         $current_class = $data;
2004                         $current_class_level = $brace_level;
2005                         $declared_classes[] = $current_class;
2006                     } elseif ($look_for == T_INTERFACE) {
2007                         $current_interface = $data;
2008                         $current_class_level = $brace_level;
2009                         $declared_interfaces[] = $current_interface;
2010                     } elseif ($look_for == T_IMPLEMENTS) {
2011                         $implements[$current_class] = $data;
2012                     } elseif ($look_for == T_EXTENDS) {
2013                         $extends[$current_class] = $data;
2014                     } elseif ($look_for == T_FUNCTION) {
2015                         if ($current_class) {
2016                             $current_function = "$current_class::$data";
2017                             $declared_methods[$current_class][] = $data;
2018                         } elseif ($current_interface) {
2019                             $current_function = "$current_interface::$data";
2020                             $declared_methods[$current_interface][] = $data;
2021                         } else {
2022                             $current_function = $data;
2023                             $declared_functions[] = $current_function;
2024                         }
2025
2026                         $current_function_level = $brace_level;
2027                         $m = array();
2028                     } elseif ($look_for == T_NEW) {
2029                         $used_classes[$data] = true;
2030                     }
2031
2032                     $look_for = 0;
2033                     continue 2;
2034                 case T_VARIABLE:
2035                     $look_for = 0;
2036                     continue 2;
2037                 case T_DOC_COMMENT:
2038                 case T_COMMENT:
2039                     if (preg_match('!^/\*\*\s!', $data)) {
2040                         $lastphpdoc = $data;
2041                         if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
2042                             $nodeps = array_merge($nodeps, $m[1]);
2043                         }
2044                     }
2045                     continue 2;
2046                 case T_DOUBLE_COLON:
2047                     $token = $tokens[$i - 1][0];
2048                     if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
2049                         if (isset($this->_stack)) {
2050                             $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
2051                                 'Parser error: invalid PHP found in file "%file%"');
2052                         } else {
2053                             PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
2054                                 PEAR_COMMON_ERROR_INVALIDPHP);
2055                         }
2056
2057                         return false;
2058                     }
2059
2060                     $class = $tokens[$i - 1][1];
2061                     if (strtolower($class) != 'parent') {
2062                         $used_classes[$class] = true;
2063                     }
2064
2065                     continue 2;
2066             }
2067         }
2068
2069         return array(
2070             "source_file" => $file,
2071             "declared_classes" => $declared_classes,
2072             "declared_interfaces" => $declared_interfaces,
2073             "declared_methods" => $declared_methods,
2074             "declared_functions" => $declared_functions,
2075             "used_classes" => array_diff(array_keys($used_classes), $nodeps),
2076             "inheritance" => $extends,
2077             "implements" => $implements,
2078         );
2079     }
2080
2081     /**
2082      * Build a "provides" array from data returned by
2083      * analyzeSourceCode().  The format of the built array is like
2084      * this:
2085      *
2086      *  array(
2087      *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
2088      *    ...
2089      *  )
2090      *
2091      *
2092      * @param array $srcinfo array with information about a source file
2093      * as returned by the analyzeSourceCode() method.
2094      *
2095      * @return void
2096      *
2097      * @access private
2098      *
2099      */
2100     function _buildProvidesArray($srcinfo)
2101     {
2102         if (!$this->_isValid) {
2103             return array();
2104         }
2105
2106         $providesret = array();
2107         $file        = basename($srcinfo['source_file']);
2108         $pn          = isset($this->_pf) ? $this->_pf->getPackage() : '';
2109         $pnl         = strlen($pn);
2110         foreach ($srcinfo['declared_classes'] as $class) {
2111             $key = "class;$class";
2112             if (isset($providesret[$key])) {
2113                 continue;
2114             }
2115
2116             $providesret[$key] =
2117                 array('file'=> $file, 'type' => 'class', 'name' => $class);
2118             if (isset($srcinfo['inheritance'][$class])) {
2119                 $providesret[$key]['extends'] =
2120                     $srcinfo['inheritance'][$class];
2121             }
2122         }
2123
2124         foreach ($srcinfo['declared_methods'] as $class => $methods) {
2125             foreach ($methods as $method) {
2126                 $function = "$class::$method";
2127                 $key = "function;$function";
2128                 if ($method{0} == '_' || !strcasecmp($method, $class) ||
2129                     isset($providesret[$key])) {
2130                     continue;
2131                 }
2132
2133                 $providesret[$key] =
2134                     array('file'=> $file, 'type' => 'function', 'name' => $function);
2135             }
2136         }
2137
2138         foreach ($srcinfo['declared_functions'] as $function) {
2139             $key = "function;$function";
2140             if ($function{0} == '_' || isset($providesret[$key])) {
2141                 continue;
2142             }
2143
2144             if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
2145                 $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
2146             }
2147
2148             $providesret[$key] =
2149                 array('file'=> $file, 'type' => 'function', 'name' => $function);
2150         }
2151
2152         return $providesret;
2153     }
2154 }