source: trunk/library/PEAR/PEAR/PackageFile/v2/Validator.php @ 7655

Revision 7655, 84.1 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Melhorias de performance no codigo do Expresso.

Line 
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 */
29class 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}
Note: See TracBrowser for help on using the repository browser.