Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
94 jpm 1
<?php
2
//
3
// +----------------------------------------------------------------------+
4
// | PHP Version 5                                                        |
5
// +----------------------------------------------------------------------+
6
// | Copyright (c) 1997-2004 The PHP Group                                |
7
// +----------------------------------------------------------------------+
8
// | This source file is subject to version 3.0 of the PHP license,       |
9
// | that is bundled with this package in the file LICENSE, and is        |
10
// | available through the world-wide-web at the following url:           |
11
// | http://www.php.net/license/3_0.txt.                                  |
12
// | If you did not receive a copy of the PHP license and are unable to   |
13
// | obtain it through the world-wide-web, please send a note to          |
14
// | license@php.net so we can mail you a copy immediately.               |
15
// +----------------------------------------------------------------------+
16
// | Author: Greg Beaver <cellog@php.net>                                 |
17
// |                                                                      |
18
// +----------------------------------------------------------------------+
19
//
20
// $Id: Validator.php,v 1.97 2007/02/10 05:56:18 cellog Exp $
21
/**
22
 * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
23
 * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
24
 * @author Greg Beaver <cellog@php.net>
25
 * @access private
26
 */
27
class PEAR_PackageFile_v2_Validator
28
{
29
    /**
30
     * @var array
31
     */
32
    var $_packageInfo;
33
    /**
34
     * @var PEAR_PackageFile_v2
35
     */
36
    var $_pf;
37
    /**
38
     * @var PEAR_ErrorStack
39
     */
40
    var $_stack;
41
    /**
42
     * @var int
43
     */
44
    var $_isValid = 0;
45
    /**
46
     * @var int
47
     */
48
    var $_filesValid = 0;
49
    /**
50
     * @var int
51
     */
52
    var $_curState = 0;
53
    /**
54
     * @param PEAR_PackageFile_v2
55
     * @param int
56
     */
57
    function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
58
    {
59
        $this->_pf = &$pf;
60
        $this->_curState = $state;
61
        $this->_packageInfo = $this->_pf->getArray();
62
        $this->_isValid = $this->_pf->_isValid;
63
        $this->_filesValid = $this->_pf->_filesValid;
64
        $this->_stack = &$pf->_stack;
65
        $this->_stack->getErrors(true);
66
        if (($this->_isValid & $state) == $state) {
67
            return true;
68
        }
69
        if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
70
            return false;
71
        }
72
        if (!isset($this->_packageInfo['attribs']['version']) ||
73
              ($this->_packageInfo['attribs']['version'] != '2.0' &&
74
               $this->_packageInfo['attribs']['version'] != '2.1')) {
75
            $this->_noPackageVersion();
76
        }
77
        $structure =
78
        array(
79
            'name',
80
            'channel|uri',
81
            '*extends', // can't be multiple, but this works fine
82
            'summary',
83
            'description',
84
            '+lead', // these all need content checks
85
            '*developer',
86
            '*contributor',
87
            '*helper',
88
            'date',
89
            '*time',
90
            'version',
91
            'stability',
92
            'license->?uri->?filesource',
93
            'notes',
94
            'contents', //special validation needed
95
            '*compatible',
96
            'dependencies', //special validation needed
97
            '*usesrole',
98
            '*usestask', // reserve these for 1.4.0a1 to implement
99
                         // this will allow a package.xml to gracefully say it
100
                         // needs a certain package installed in order to implement a role or task
101
            '*providesextension',
102
            '*srcpackage|*srcuri',
103
            '+phprelease|+extsrcrelease|+extbinrelease|' .
104
                '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
105
            '*changelog',
106
        );
107
        $test = $this->_packageInfo;
108
        if (isset($test['dependencies']) &&
109
              isset($test['dependencies']['required']) &&
110
              isset($test['dependencies']['required']['pearinstaller']) &&
111
              isset($test['dependencies']['required']['pearinstaller']['min']) &&
112
              version_compare('1.5.1',
113
                $test['dependencies']['required']['pearinstaller']['min'], '<')) {
114
            $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
115
            return false;
116
        }
117
        // ignore post-installation array fields
118
        if (array_key_exists('filelist', $test)) {
119
            unset($test['filelist']);
120
        }
121
        if (array_key_exists('_lastmodified', $test)) {
122
            unset($test['_lastmodified']);
123
        }
124
        if (array_key_exists('#binarypackage', $test)) {
125
            unset($test['#binarypackage']);
126
        }
127
        if (array_key_exists('old', $test)) {
128
            unset($test['old']);
129
        }
130
        if (array_key_exists('_lastversion', $test)) {
131
            unset($test['_lastversion']);
132
        }
133
        if (!$this->_stupidSchemaValidate($structure,
134
                                          $test, '<package>')) {
135
            return false;
136
        }
137
        if (empty($this->_packageInfo['name'])) {
138
            $this->_tagCannotBeEmpty('name');
139
        }
140
        if (isset($this->_packageInfo['uri'])) {
141
            $test = 'uri';
142
        } else {
143
            $test = 'channel';
144
        }
145
        if (empty($this->_packageInfo[$test])) {
146
            $this->_tagCannotBeEmpty($test);
147
        }
148
        if (is_array($this->_packageInfo['license']) &&
149
              (!isset($this->_packageInfo['license']['_content']) ||
150
              empty($this->_packageInfo['license']['_content']))) {
151
            $this->_tagCannotBeEmpty('license');
152
        } elseif (empty($this->_packageInfo['license'])) {
153
            $this->_tagCannotBeEmpty('license');
154
        }
155
        if (empty($this->_packageInfo['summary'])) {
156
            $this->_tagCannotBeEmpty('summary');
157
        }
158
        if (empty($this->_packageInfo['description'])) {
159
            $this->_tagCannotBeEmpty('description');
160
        }
161
        if (empty($this->_packageInfo['date'])) {
162
            $this->_tagCannotBeEmpty('date');
163
        }
164
        if (empty($this->_packageInfo['notes'])) {
165
            $this->_tagCannotBeEmpty('notes');
166
        }
167
        if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
168
            $this->_tagCannotBeEmpty('time');
169
        }
170
        if (isset($this->_packageInfo['dependencies'])) {
171
            $this->_validateDependencies();
172
        }
173
        if (isset($this->_packageInfo['compatible'])) {
174
            $this->_validateCompatible();
175
        }
176
        if (!isset($this->_packageInfo['bundle'])) {
177
            if (empty($this->_packageInfo['contents'])) {
178
                $this->_tagCannotBeEmpty('contents');
179
            }
180
            if (!isset($this->_packageInfo['contents']['dir'])) {
181
                $this->_filelistMustContainDir('contents');
182
                return false;
183
            }
184
            if (isset($this->_packageInfo['contents']['file'])) {
185
                $this->_filelistCannotContainFile('contents');
186
                return false;
187
            }
188
        }
189
        $this->_validateMaintainers();
190
        $this->_validateStabilityVersion();
191
        $fail = false;
192
        if (array_key_exists('usesrole', $this->_packageInfo)) {
193
            $roles = $this->_packageInfo['usesrole'];
194
            if (!is_array($roles) || !isset($roles[0])) {
195
                $roles = array($roles);
196
            }
197
            foreach ($roles as $role) {
198
                if (!isset($role['role'])) {
199
                    $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
200
                    $fail = true;
201
                } else {
202
                    if (!isset($role['channel'])) {
203
                        if (!isset($role['uri'])) {
204
                            $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
205
                            $fail = true;
206
                        }
207
                    } elseif (!isset($role['package'])) {
208
                        $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
209
                        $fail = true;
210
                    }
211
                }
212
            }
213
        }
214
        if (array_key_exists('usestask', $this->_packageInfo)) {
215
            $roles = $this->_packageInfo['usestask'];
216
            if (!is_array($roles) || !isset($roles[0])) {
217
                $roles = array($roles);
218
            }
219
            foreach ($roles as $role) {
220
                if (!isset($role['task'])) {
221
                    $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
222
                    $fail = true;
223
                } else {
224
                    if (!isset($role['channel'])) {
225
                        if (!isset($role['uri'])) {
226
                            $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
227
                            $fail = true;
228
                        }
229
                    } elseif (!isset($role['package'])) {
230
                        $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
231
                        $fail = true;
232
                    }
233
                }
234
            }
235
        }
236
        if ($fail) {
237
            return false;
238
        }
239
        $list = $this->_packageInfo['contents'];
240
        if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
241
            $this->_multipleToplevelDirNotAllowed();
242
            return $this->_isValid = 0;
243
        }
244
        $this->_validateFilelist();
245
        $this->_validateRelease();
246
        if (!$this->_stack->hasErrors()) {
247
            $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
248
            if (PEAR::isError($chan)) {
249
                $this->_unknownChannel($this->_pf->getChannel());
250
            } else {
251
                $valpack = $chan->getValidationPackage();
252
                // for channel validator packages, always use the default PEAR validator.
253
                // otherwise, they can't be installed or packaged
254
                $validator = $chan->getValidationObject($this->_pf->getPackage());
255
                if (!$validator) {
256
                    $this->_stack->push(__FUNCTION__, 'error',
257
                        array_merge(
258
                            array('channel' => $chan->getName(),
259
                                  'package' => $this->_pf->getPackage()),
260
                              $valpack
261
                            ),
262
                        'package "%channel%/%package%" cannot be properly validated without ' .
263
                        'validation package "%channel%/%name%-%version%"');
264
                    return $this->_isValid = 0;
265
                }
266
                $validator->setPackageFile($this->_pf);
267
                $validator->validate($state);
268
                $failures = $validator->getFailures();
269
                foreach ($failures['errors'] as $error) {
270
                    $this->_stack->push(__FUNCTION__, 'error', $error,
271
                        'Channel validator error: field "%field%" - %reason%');
272
                }
273
                foreach ($failures['warnings'] as $warning) {
274
                    $this->_stack->push(__FUNCTION__, 'warning', $warning,
275
                        'Channel validator warning: field "%field%" - %reason%');
276
                }
277
            }
278
        }
279
        $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
280
        if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
281
            if ($this->_pf->getPackageType() == 'bundle') {
282
                if ($this->_analyzeBundledPackages()) {
283
                    $this->_filesValid = $this->_pf->_filesValid = true;
284
                } else {
285
                    $this->_pf->_isValid = $this->_isValid = 0;
286
                }
287
            } else {
288
                if (!$this->_analyzePhpFiles()) {
289
                    $this->_pf->_isValid = $this->_isValid = 0;
290
                } else {
291
                    $this->_filesValid = $this->_pf->_filesValid = true;
292
                }
293
            }
294
        }
295
        if ($this->_isValid) {
296
            return $this->_pf->_isValid = $this->_isValid = $state;
297
        }
298
        return $this->_pf->_isValid = $this->_isValid = 0;
299
    }
300
 
301
    function _stupidSchemaValidate($structure, $xml, $root)
302
    {
303
        if (!is_array($xml)) {
304
            $xml = array();
305
        }
306
        $keys = array_keys($xml);
307
        reset($keys);
308
        $key = current($keys);
309
        while ($key == 'attribs' || $key == '_contents') {
310
            $key = next($keys);
311
        }
312
        $unfoundtags = $optionaltags = array();
313
        $ret = true;
314
        $mismatch = false;
315
        foreach ($structure as $struc) {
316
            if ($key) {
317
                $tag = $xml[$key];
318
            }
319
            $test = $this->_processStructure($struc);
320
            if (isset($test['choices'])) {
321
                $loose = true;
322
                foreach ($test['choices'] as $choice) {
323
                    if ($key == $choice['tag']) {
324
                        $key = next($keys);
325
                        while ($key == 'attribs' || $key == '_contents') {
326
                            $key = next($keys);
327
                        }
328
                        $unfoundtags = $optionaltags = array();
329
                        $mismatch = false;
330
                        if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
331
                            $unfoundtags[] = $choice['tag'];
332
                            $optionaltags[] = $choice['tag'];
333
                            if ($key) {
334
                                $mismatch = true;
335
                            }
336
                        }
337
                        $ret &= $this->_processAttribs($choice, $tag, $root);
338
                        continue 2;
339
                    } else {
340
                        $unfoundtags[] = $choice['tag'];
341
                        $mismatch = true;
342
                    }
343
                    if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
344
                        $loose = false;
345
                    } else {
346
                        $optionaltags[] = $choice['tag'];
347
                    }
348
                }
349
                if (!$loose) {
350
                    $this->_invalidTagOrder($unfoundtags, $key, $root);
351
                    return false;
352
                }
353
            } else {
354
                if ($key != $test['tag']) {
355
                    if (isset($test['multiple']) && $test['multiple'] != '*') {
356
                        $unfoundtags[] = $test['tag'];
357
                        $this->_invalidTagOrder($unfoundtags, $key, $root);
358
                        return false;
359
                    } else {
360
                        if ($key) {
361
                            $mismatch = true;
362
                        }
363
                        $unfoundtags[] = $test['tag'];
364
                        $optionaltags[] = $test['tag'];
365
                    }
366
                    if (!isset($test['multiple'])) {
367
                        $this->_invalidTagOrder($unfoundtags, $key, $root);
368
                        return false;
369
                    }
370
                    continue;
371
                } else {
372
                    $unfoundtags = $optionaltags = array();
373
                    $mismatch = false;
374
                }
375
                $key = next($keys);
376
                while ($key == 'attribs' || $key == '_contents') {
377
                    $key = next($keys);
378
                }
379
                if ($key && $key != $test['tag'] && isset($test['multiple'])) {
380
                    $unfoundtags[] = $test['tag'];
381
                    $optionaltags[] = $test['tag'];
382
                    $mismatch = true;
383
                }
384
                $ret &= $this->_processAttribs($test, $tag, $root);
385
                continue;
386
            }
387
        }
388
        if (!$mismatch && count($optionaltags)) {
389
            // don't error out on any optional tags
390
            $unfoundtags = array_diff($unfoundtags, $optionaltags);
391
        }
392
        if (count($unfoundtags)) {
393
            $this->_invalidTagOrder($unfoundtags, $key, $root);
394
        } elseif ($key) {
395
            // unknown tags
396
            $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
397
            while ($key = next($keys)) {
398
                $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
399
            }
400
        }
401
        return $ret;
402
    }
403
 
404
    function _processAttribs($choice, $tag, $context)
405
    {
406
        if (isset($choice['attribs'])) {
407
            if (!is_array($tag)) {
408
                $tag = array($tag);
409
            }
410
            $tags = $tag;
411
            if (!isset($tags[0])) {
412
                $tags = array($tags);
413
            }
414
            $ret = true;
415
            foreach ($tags as $i => $tag) {
416
                if (!is_array($tag) || !isset($tag['attribs'])) {
417
                    foreach ($choice['attribs'] as $attrib) {
418
                        if ($attrib{0} != '?') {
419
                            $ret &= $this->_tagHasNoAttribs($choice['tag'],
420
                                $context);
421
                            continue 2;
422
                        }
423
                    }
424
                }
425
                foreach ($choice['attribs'] as $attrib) {
426
                    if ($attrib{0} != '?') {
427
                        if (!isset($tag['attribs'][$attrib])) {
428
                            $ret &= $this->_tagMissingAttribute($choice['tag'],
429
                                $attrib, $context);
430
                        }
431
                    }
432
                }
433
            }
434
            return $ret;
435
        }
436
        return true;
437
    }
438
 
439
    function _processStructure($key)
440
    {
441
        $ret = array();
442
        if (count($pieces = explode('|', $key)) > 1) {
443
            $ret['choices'] = array();
444
            foreach ($pieces as $piece) {
445
                $ret['choices'][] = $this->_processStructure($piece);
446
            }
447
            return $ret;
448
        }
449
        $multi = $key{0};
450
        if ($multi == '+' || $multi == '*') {
451
            $ret['multiple'] = $key{0};
452
            $key = substr($key, 1);
453
        }
454
        if (count($attrs = explode('->', $key)) > 1) {
455
            $ret['tag'] = array_shift($attrs);
456
            $ret['attribs'] = $attrs;
457
        } else {
458
            $ret['tag'] = $key;
459
        }
460
        return $ret;
461
    }
462
 
463
    function _validateStabilityVersion()
464
    {
465
        $structure = array('release', 'api');
466
        $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
467
        $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
468
        if ($a) {
469
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
470
                  $this->_packageInfo['version']['release'])) {
471
                $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
472
            }
473
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
474
                  $this->_packageInfo['version']['api'])) {
475
                $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
476
            }
477
            if (!in_array($this->_packageInfo['stability']['release'],
478
                  array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
479
                $this->_invalidState('release', $this->_packageinfo['stability']['release']);
480
            }
481
            if (!in_array($this->_packageInfo['stability']['api'],
482
                  array('devel', 'alpha', 'beta', 'stable'))) {
483
                $this->_invalidState('api', $this->_packageinfo['stability']['api']);
484
            }
485
        }
486
    }
487
 
488
    function _validateMaintainers()
489
    {
490
        $structure =
491
            array(
492
                'name',
493
                'user',
494
                'email',
495
                'active',
496
            );
497
        foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
498
            if (!isset($this->_packageInfo[$type])) {
499
                continue;
500
            }
501
            if (isset($this->_packageInfo[$type][0])) {
502
                foreach ($this->_packageInfo[$type] as $lead) {
503
                    $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
504
                }
505
            } else {
506
                $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
507
                    '<' . $type . '>');
508
            }
509
        }
510
    }
511
 
512
    function _validatePhpDep($dep, $installcondition = false)
513
    {
514
        $structure = array(
515
            'min',
516
            '*max',
517
            '*exclude',
518
        );
519
        $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
520
        $this->_stupidSchemaValidate($structure, $dep, $type);
521
        if (isset($dep['min'])) {
522
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?$/',
523
                  $dep['min'])) {
524
                $this->_invalidVersion($type . '<min>', $dep['min']);
525
            }
526
        }
527
        if (isset($dep['max'])) {
528
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?$/',
529
                  $dep['max'])) {
530
                $this->_invalidVersion($type . '<max>', $dep['max']);
531
            }
532
        }
533
        if (isset($dep['exclude'])) {
534
            if (!is_array($dep['exclude'])) {
535
                $dep['exclude'] = array($dep['exclude']);
536
            }
537
            foreach ($dep['exclude'] as $exclude) {
538
                if (!preg_match(
539
                     '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?$/',
540
                     $exclude)) {
541
                    $this->_invalidVersion($type . '<exclude>', $exclude);
542
                }
543
            }
544
        }
545
    }
546
 
547
    function _validatePearinstallerDep($dep)
548
    {
549
        $structure = array(
550
            'min',
551
            '*max',
552
            '*recommended',
553
            '*exclude',
554
        );
555
        $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
556
        if (isset($dep['min'])) {
557
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
558
                  $dep['min'])) {
559
                $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
560
                    $dep['min']);
561
            }
562
        }
563
        if (isset($dep['max'])) {
564
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
565
                  $dep['max'])) {
566
                $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
567
                    $dep['max']);
568
            }
569
        }
570
        if (isset($dep['recommended'])) {
571
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
572
                  $dep['recommended'])) {
573
                $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
574
                    $dep['recommended']);
575
            }
576
        }
577
        if (isset($dep['exclude'])) {
578
            if (!is_array($dep['exclude'])) {
579
                $dep['exclude'] = array($dep['exclude']);
580
            }
581
            foreach ($dep['exclude'] as $exclude) {
582
                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
583
                      $exclude)) {
584
                    $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
585
                        $exclude);
586
                }
587
            }
588
        }
589
    }
590
 
591
    function _validatePackageDep($dep, $group, $type = '<package>')
592
    {
593
        if (isset($dep['uri'])) {
594
            if (isset($dep['conflicts'])) {
595
                $structure = array(
596
                    'name',
597
                    'uri',
598
                    'conflicts',
599
                    '*providesextension',
600
                );
601
            } else {
602
                $structure = array(
603
                    'name',
604
                    'uri',
605
                    '*providesextension',
606
                );
607
            }
608
        } else {
609
            if (isset($dep['conflicts'])) {
610
                $structure = array(
611
                    'name',
612
                    'channel',
613
                    '*min',
614
                    '*max',
615
                    '*exclude',
616
                    'conflicts',
617
                    '*providesextension',
618
                );
619
            } else {
620
                $structure = array(
621
                    'name',
622
                    'channel',
623
                    '*min',
624
                    '*max',
625
                    '*recommended',
626
                    '*exclude',
627
                    '*nodefault',
628
                    '*providesextension',
629
                );
630
            }
631
        }
632
        if (isset($dep['name'])) {
633
            $type .= '<name>' . $dep['name'] . '</name>';
634
        }
635
        $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
636
        if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
637
              isset($dep['recommended']) || isset($dep['exclude']))) {
638
            $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
639
        }
640
        if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
641
            $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
642
        }
643
        if (isset($dep['min'])) {
644
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
645
                  $dep['min'])) {
646
                $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
647
            }
648
        }
649
        if (isset($dep['max'])) {
650
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
651
                  $dep['max'])) {
652
                $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
653
            }
654
        }
655
        if (isset($dep['recommended'])) {
656
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
657
                  $dep['recommended'])) {
658
                $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
659
                    $dep['recommended']);
660
            }
661
        }
662
        if (isset($dep['exclude'])) {
663
            if (!is_array($dep['exclude'])) {
664
                $dep['exclude'] = array($dep['exclude']);
665
            }
666
            foreach ($dep['exclude'] as $exclude) {
667
                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
668
                      $exclude)) {
669
                    $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
670
                        $exclude);
671
                }
672
            }
673
        }
674
    }
675
 
676
    function _validateSubpackageDep($dep, $group)
677
    {
678
        $this->_validatePackageDep($dep, $group, '<subpackage>');
679
        if (isset($dep['providesextension'])) {
680
            $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
681
        }
682
        if (isset($dep['conflicts'])) {
683
            $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
684
        }
685
    }
686
 
687
    function _validateExtensionDep($dep, $group = false, $installcondition = false)
688
    {
689
        if (isset($dep['conflicts'])) {
690
            $structure = array(
691
                'name',
692
                '*min',
693
                '*max',
694
                '*exclude',
695
                'conflicts',
696
            );
697
        } else {
698
            $structure = array(
699
                'name',
700
                '*min',
701
                '*max',
702
                '*recommended',
703
                '*exclude',
704
            );
705
        }
706
        if ($installcondition) {
707
            $type = '<installcondition><extension>';
708
        } else {
709
            $type = '<dependencies>' . $group . '<extension>';
710
        }
711
        if (isset($dep['name'])) {
712
            $type .= '<name>' . $dep['name'] . '</name>';
713
        }
714
        $this->_stupidSchemaValidate($structure, $dep, $type);
715
        if (isset($dep['min'])) {
716
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
717
                  $dep['min'])) {
718
                $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
719
            }
720
        }
721
        if (isset($dep['max'])) {
722
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
723
                  $dep['max'])) {
724
                $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
725
            }
726
        }
727
        if (isset($dep['recommended'])) {
728
            if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
729
                  $dep['recommended'])) {
730
                $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
731
            }
732
        }
733
        if (isset($dep['exclude'])) {
734
            if (!is_array($dep['exclude'])) {
735
                $dep['exclude'] = array($dep['exclude']);
736
            }
737
            foreach ($dep['exclude'] as $exclude) {
738
                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
739
                      $exclude)) {
740
                    $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
741
                }
742
            }
743
        }
744
    }
745
 
746
    function _validateOsDep($dep, $installcondition = false)
747
    {
748
        $structure = array(
749
            'name',
750
            '*conflicts',
751
        );
752
        $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
753
        if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
754
            if ($dep['name'] == '*') {
755
                if (array_key_exists('conflicts', $dep)) {
756
                    $this->_cannotConflictWithAllOs($type);
757
                }
758
            }
759
        }
760
    }
761
 
762
    function _validateArchDep($dep, $installcondition = false)
763
    {
764
        $structure = array(
765
            'pattern',
766
            '*conflicts',
767
        );
768
        $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
769
        $this->_stupidSchemaValidate($structure, $dep, $type);
770
    }
771
 
772
    function _validateInstallConditions($cond, $release)
773
    {
774
        $structure = array(
775
            '*php',
776
            '*extension',
777
            '*os',
778
            '*arch',
779
        );
780
        if (!$this->_stupidSchemaValidate($structure,
781
              $cond, $release)) {
782
            return false;
783
        }
784
        foreach (array('php', 'extension', 'os', 'arch') as $type) {
785
            if (isset($cond[$type])) {
786
                $iter = $cond[$type];
787
                if (!is_array($iter) || !isset($iter[0])) {
788
                    $iter = array($iter);
789
                }
790
                foreach ($iter as $package) {
791
                    if ($type == 'extension') {
792
                        $this->{"_validate{$type}Dep"}($package, false, true);
793
                    } else {
794
                        $this->{"_validate{$type}Dep"}($package, true);
795
                    }
796
                }
797
            }
798
        }
799
    }
800
 
801
    function _validateDependencies()
802
    {
803
        $structure = array(
804
            'required',
805
            '*optional',
806
            '*group->name->hint'
807
        );
808
        if (!$this->_stupidSchemaValidate($structure,
809
              $this->_packageInfo['dependencies'], '<dependencies>')) {
810
            return false;
811
        }
812
        foreach (array('required', 'optional') as $simpledep) {
813
            if (isset($this->_packageInfo['dependencies'][$simpledep])) {
814
                if ($simpledep == 'optional') {
815
                    $structure = array(
816
                        '*package',
817
                        '*subpackage',
818
                        '*extension',
819
                    );
820
                } else {
821
                    $structure = array(
822
                        'php',
823
                        'pearinstaller',
824
                        '*package',
825
                        '*subpackage',
826
                        '*extension',
827
                        '*os',
828
                        '*arch',
829
                    );
830
                }
831
                if ($this->_stupidSchemaValidate($structure,
832
                      $this->_packageInfo['dependencies'][$simpledep],
833
                      "<dependencies><$simpledep>")) {
834
                    foreach (array('package', 'subpackage', 'extension') as $type) {
835
                        if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
836
                            $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
837
                            if (!isset($iter[0])) {
838
                                $iter = array($iter);
839
                            }
840
                            foreach ($iter as $package) {
841
                                if ($type != 'extension') {
842
                                    if (isset($package['uri'])) {
843
                                        if (isset($package['channel'])) {
844
                                            $this->_UrlOrChannel($type,
845
                                                $package['name']);
846
                                        }
847
                                    } else {
848
                                        if (!isset($package['channel'])) {
849
                                            $this->_NoChannel($type, $package['name']);
850
                                        }
851
                                    }
852
                                }
853
                                $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
854
                            }
855
                        }
856
                    }
857
                    if ($simpledep == 'optional') {
858
                        continue;
859
                    }
860
                    foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
861
                        if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
862
                            $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
863
                            if (!isset($iter[0])) {
864
                                $iter = array($iter);
865
                            }
866
                            foreach ($iter as $package) {
867
                                $this->{"_validate{$type}Dep"}($package);
868
                            }
869
                        }
870
                    }
871
                }
872
            }
873
        }
874
        if (isset($this->_packageInfo['dependencies']['group'])) {
875
            $groups = $this->_packageInfo['dependencies']['group'];
876
            if (!isset($groups[0])) {
877
                $groups = array($groups);
878
            }
879
            $structure = array(
880
                '*package',
881
                '*subpackage',
882
                '*extension',
883
            );
884
            foreach ($groups as $group) {
885
                if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
886
                    if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
887
                        $this->_invalidDepGroupName($group['attribs']['name']);
888
                    }
889
                    foreach (array('package', 'subpackage', 'extension') as $type) {
890
                        if (isset($group[$type])) {
891
                            $iter = $group[$type];
892
                            if (!isset($iter[0])) {
893
                                $iter = array($iter);
894
                            }
895
                            foreach ($iter as $package) {
896
                                if ($type != 'extension') {
897
                                    if (isset($package['uri'])) {
898
                                        if (isset($package['channel'])) {
899
                                            $this->_UrlOrChannelGroup($type,
900
                                                $package['name'],
901
                                                $group['name']);
902
                                        }
903
                                    } else {
904
                                        if (!isset($package['channel'])) {
905
                                            $this->_NoChannelGroup($type,
906
                                                $package['name'],
907
                                                $group['name']);
908
                                        }
909
                                    }
910
                                }
911
                                $this->{"_validate{$type}Dep"}($package, '<group name="' .
912
                                    $group['attribs']['name'] . '">');
913
                            }
914
                        }
915
                    }
916
                }
917
            }
918
        }
919
    }
920
 
921
    function _validateCompatible()
922
    {
923
        $compat = $this->_packageInfo['compatible'];
924
        if (!isset($compat[0])) {
925
            $compat = array($compat);
926
        }
927
        $required = array('name', 'channel', 'min', 'max', '*exclude');
928
        foreach ($compat as $package) {
929
            $type = '<compatible>';
930
            if (is_array($package) && array_key_exists('name', $package)) {
931
                $type .= '<name>' . $package['name'] . '</name>';
932
            }
933
            $this->_stupidSchemaValidate($required, $package, $type);
934
            if (is_array($package) && array_key_exists('min', $package)) {
935
                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
936
                      $package['min'])) {
937
                    $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
938
                }
939
            }
940
            if (is_array($package) && array_key_exists('max', $package)) {
941
                if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
942
                      $package['max'])) {
943
                    $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
944
                }
945
            }
946
            if (is_array($package) && array_key_exists('exclude', $package)) {
947
                if (!is_array($package['exclude'])) {
948
                    $package['exclude'] = array($package['exclude']);
949
                }
950
                foreach ($package['exclude'] as $exclude) {
951
                    if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?$/',
952
                          $exclude)) {
953
                        $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
954
                    }
955
                }
956
            }
957
        }
958
    }
959
 
960
    function _validateBundle($list)
961
    {
962
        if (!is_array($list) || !isset($list['bundledpackage'])) {
963
            return $this->_NoBundledPackages();
964
        }
965
        if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
966
            return $this->_AtLeast2BundledPackages();
967
        }
968
        foreach ($list['bundledpackage'] as $package) {
969
            if (!is_string($package)) {
970
                $this->_bundledPackagesMustBeFilename();
971
            }
972
        }
973
    }
974
 
975
    function _validateFilelist($list = false, $allowignore = false, $dirs = '')
976
    {
977
        $iscontents = false;
978
        if (!$list) {
979
            $iscontents = true;
980
            $list = $this->_packageInfo['contents'];
981
            if (isset($this->_packageInfo['bundle'])) {
982
                return $this->_validateBundle($list);
983
            }
984
        }
985
        if ($allowignore) {
986
            $struc = array(
987
                '*install->name->as',
988
                '*ignore->name'
989
            );
990
        } else {
991
            $struc = array(
992
                '*dir->name->?baseinstalldir',
993
                '*file->name->role->?baseinstalldir->?md5sum'
994
            );
995
            if (isset($list['dir']) && isset($list['file'])) {
996
                // stave off validation errors without requiring a set order.
997
                $_old = $list;
998
                if (isset($list['attribs'])) {
999
                    $list = array('attribs' => $_old['attribs']);
1000
                }
1001
                $list['dir'] = $_old['dir'];
1002
                $list['file'] = $_old['file'];
1003
            }
1004
        }
1005
        if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
1006
            $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
1007
            $dirname = $iscontents ? '<contents>' : $unknown;
1008
        } else {
1009
            $dirname = '<dir name="' . $list['attribs']['name'] . '">';
1010
        }
1011
        $res = $this->_stupidSchemaValidate($struc, $list, $dirname);
1012
        if ($allowignore && $res) {
1013
            $ignored_or_installed = array();
1014
            $this->_pf->getFilelist();
1015
            $fcontents = $this->_pf->getContents();
1016
            $filelist = array();
1017
            if (!isset($fcontents['dir']['file'][0])) {
1018
                $fcontents['dir']['file'] = array($fcontents['dir']['file']);
1019
            }
1020
            foreach ($fcontents['dir']['file'] as $file) {
1021
                $filelist[$file['attribs']['name']] = true;
1022
            }
1023
            if (isset($list['install'])) {
1024
                if (!isset($list['install'][0])) {
1025
                    $list['install'] = array($list['install']);
1026
                }
1027
                foreach ($list['install'] as $file) {
1028
                    if (!isset($filelist[$file['attribs']['name']])) {
1029
                        $this->_notInContents($file['attribs']['name'], 'install');
1030
                        continue;
1031
                    }
1032
                    if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1033
                        $this->_multipleInstallAs($file['attribs']['name']);
1034
                    }
1035
                    if (!isset($ignored_or_installed[$file['attribs']['name']])) {
1036
                        $ignored_or_installed[$file['attribs']['name']] = array();
1037
                    }
1038
                    $ignored_or_installed[$file['attribs']['name']][] = 1;
1039
                }
1040
            }
1041
            if (isset($list['ignore'])) {
1042
                if (!isset($list['ignore'][0])) {
1043
                    $list['ignore'] = array($list['ignore']);
1044
                }
1045
                foreach ($list['ignore'] as $file) {
1046
                    if (!isset($filelist[$file['attribs']['name']])) {
1047
                        $this->_notInContents($file['attribs']['name'], 'ignore');
1048
                        continue;
1049
                    }
1050
                    if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
1051
                        $this->_ignoreAndInstallAs($file['attribs']['name']);
1052
                    }
1053
                }
1054
            }
1055
        }
1056
        if (!$allowignore && isset($list['file'])) {
1057
            if (!isset($list['file'][0])) {
1058
                // single file
1059
                $list['file'] = array($list['file']);
1060
            }
1061
            foreach ($list['file'] as $i => $file)
1062
            {
1063
                if (isset($file['attribs']) && isset($file['attribs']['name']) &&
1064
                      $file['attribs']['name']{0} == '.' &&
1065
                        $file['attribs']['name']{1} == '/') {
1066
                    // name is something like "./doc/whatever.txt"
1067
                    $this->_invalidFileName($file['attribs']['name']);
1068
                }
1069
                if (isset($file['attribs']) && isset($file['attribs']['role'])) {
1070
                    if (!$this->_validateRole($file['attribs']['role'])) {
1071
                        if (isset($this->_packageInfo['usesrole'])) {
1072
                            $roles = $this->_packageInfo['usesrole'];
1073
                            if (!isset($roles[0])) {
1074
                                $roles = array($roles);
1075
                            }
1076
                            foreach ($roles as $role) {
1077
                                if ($role['role'] = $file['attribs']['role']) {
1078
                                    $msg = 'This package contains role "%role%" and requires ' .
1079
                                        'package "%package%" to be used';
1080
                                    if (isset($role['uri'])) {
1081
                                        $params = array('role' => $role['role'],
1082
                                            'package' => $role['uri']);
1083
                                    } else {
1084
                                        $params = array('role' => $role['role'],
1085
                                            'package' => $this->_pf->_registry->
1086
                                            parsedPackageNameToString(array('package' =>
1087
                                                $role['package'], 'channel' => $role['channel']),
1088
                                                true));
1089
                                    }
1090
                                    $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
1091
                                }
1092
                            }
1093
                        }
1094
                        $this->_invalidFileRole($file['attribs']['name'],
1095
                            $dirname, $file['attribs']['role']);
1096
                    }
1097
                }
1098
                if (!isset($file['attribs'])) {
1099
                    continue;
1100
                }
1101
                $save = $file['attribs'];
1102
                if ($dirs) {
1103
                    $save['name'] = $dirs . '/' . $save['name'];
1104
                }
1105
                unset($file['attribs']);
1106
                if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
1107
                    foreach ($file as $task => $value) {
1108
                        if ($tagClass = $this->_pf->getTask($task)) {
1109
                            if (!is_array($value) || !isset($value[0])) {
1110
                                $value = array($value);
1111
                            }
1112
                            foreach ($value as $v) {
1113
                                $ret = call_user_func(array($tagClass, 'validateXml'),
1114
                                    $this->_pf, $v, $this->_pf->_config, $save);
1115
                                if (is_array($ret)) {
1116
                                    $this->_invalidTask($task, $ret, isset($save['name']) ?
1117
                                        $save['name'] : '');
1118
                                }
1119
                            }
1120
                        } else {
1121
                            if (isset($this->_packageInfo['usestask'])) {
1122
                                $roles = $this->_packageInfo['usestask'];
1123
                                if (!isset($roles[0])) {
1124
                                    $roles = array($roles);
1125
                                }
1126
                                foreach ($roles as $role) {
1127
                                    if ($role['task'] = $task) {
1128
                                        $msg = 'This package contains task "%task%" and requires ' .
1129
                                            'package "%package%" to be used';
1130
                                        if (isset($role['uri'])) {
1131
                                            $params = array('task' => $role['task'],
1132
                                                'package' => $role['uri']);
1133
                                        } else {
1134
                                            $params = array('task' => $role['task'],
1135
                                                'package' => $this->_pf->_registry->
1136
                                                parsedPackageNameToString(array('package' =>
1137
                                                    $role['package'], 'channel' => $role['channel']),
1138
                                                    true));
1139
                                        }
1140
                                        $this->_stack->push('_mustInstallTask', 'error',
1141
                                            $params, $msg);
1142
                                    }
1143
                                }
1144
                            }
1145
                            $this->_unknownTask($task, $save['name']);
1146
                        }
1147
                    }
1148
                }
1149
            }
1150
        }
1151
        if (isset($list['ignore'])) {
1152
            if (!$allowignore) {
1153
                $this->_ignoreNotAllowed('ignore');
1154
            }
1155
        }
1156
        if (isset($list['install'])) {
1157
            if (!$allowignore) {
1158
                $this->_ignoreNotAllowed('install');
1159
            }
1160
        }
1161
        if (isset($list['file'])) {
1162
            if ($allowignore) {
1163
                $this->_fileNotAllowed('file');
1164
            }
1165
        }
1166
        if (isset($list['dir'])) {
1167
            if ($allowignore) {
1168
                $this->_fileNotAllowed('dir');
1169
            } else {
1170
                if (!isset($list['dir'][0])) {
1171
                    $list['dir'] = array($list['dir']);
1172
                }
1173
                foreach ($list['dir'] as $dir) {
1174
                    if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
1175
                        if ($dir['attribs']['name'] == '/' ||
1176
                              !isset($this->_packageInfo['contents']['dir']['dir'])) {
1177
                            // always use nothing if the filelist has already been flattened
1178
                            $newdirs = '';
1179
                        } elseif ($dirs == '') {
1180
                            $newdirs = $dir['attribs']['name'];
1181
                        } else {
1182
                            $newdirs = $dirs . '/' . $dir['attribs']['name'];
1183
                        }
1184
                    } else {
1185
                        $newdirs = $dirs;
1186
                    }
1187
                    $this->_validateFilelist($dir, $allowignore, $newdirs);
1188
                }
1189
            }
1190
        }
1191
    }
1192
 
1193
    function _validateRelease()
1194
    {
1195
        if (isset($this->_packageInfo['phprelease'])) {
1196
            $release = 'phprelease';
1197
            if (isset($this->_packageInfo['providesextension'])) {
1198
                $this->_cannotProvideExtension($release);
1199
            }
1200
            if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1201
                $this->_cannotHaveSrcpackage($release);
1202
            }
1203
            $releases = $this->_packageInfo['phprelease'];
1204
            if (!is_array($releases)) {
1205
                return true;
1206
            }
1207
            if (!isset($releases[0])) {
1208
                $releases = array($releases);
1209
            }
1210
            foreach ($releases as $rel) {
1211
                $this->_stupidSchemaValidate(array(
1212
                    '*installconditions',
1213
                    '*filelist',
1214
                ), $rel, '<phprelease>');
1215
            }
1216
        }
1217
        foreach (array('', 'zend') as $prefix) {
1218
            $releasetype = $prefix . 'extsrcrelease';
1219
            if (isset($this->_packageInfo[$releasetype])) {
1220
                $release = $releasetype;
1221
                if (!isset($this->_packageInfo['providesextension'])) {
1222
                    $this->_mustProvideExtension($release);
1223
                }
1224
                if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1225
                    $this->_cannotHaveSrcpackage($release);
1226
                }
1227
                $releases = $this->_packageInfo[$releasetype];
1228
                if (!is_array($releases)) {
1229
                    return true;
1230
                }
1231
                if (!isset($releases[0])) {
1232
                    $releases = array($releases);
1233
                }
1234
                foreach ($releases as $rel) {
1235
                    $this->_stupidSchemaValidate(array(
1236
                        '*installconditions',
1237
                        '*configureoption->name->prompt->?default',
1238
                        '*binarypackage',
1239
                        '*filelist',
1240
                    ), $rel, '<' . $releasetype . '>');
1241
                    if (isset($rel['binarypackage'])) {
1242
                        if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
1243
                            $rel['binarypackage'] = array($rel['binarypackage']);
1244
                        }
1245
                        foreach ($rel['binarypackage'] as $bin) {
1246
                            if (!is_string($bin)) {
1247
                                $this->_binaryPackageMustBePackagename();
1248
                            }
1249
                        }
1250
                    }
1251
                }
1252
            }
1253
            $releasetype = 'extbinrelease';
1254
            if (isset($this->_packageInfo[$releasetype])) {
1255
                $release = $releasetype;
1256
                if (!isset($this->_packageInfo['providesextension'])) {
1257
                    $this->_mustProvideExtension($release);
1258
                }
1259
                if (isset($this->_packageInfo['channel']) &&
1260
                      !isset($this->_packageInfo['srcpackage'])) {
1261
                    $this->_mustSrcPackage($release);
1262
                }
1263
                if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
1264
                    $this->_mustSrcuri($release);
1265
                }
1266
                $releases = $this->_packageInfo[$releasetype];
1267
                if (!is_array($releases)) {
1268
                    return true;
1269
                }
1270
                if (!isset($releases[0])) {
1271
                    $releases = array($releases);
1272
                }
1273
                foreach ($releases as $rel) {
1274
                    $this->_stupidSchemaValidate(array(
1275
                        '*installconditions',
1276
                        '*filelist',
1277
                    ), $rel, '<' . $releasetype . '>');
1278
                }
1279
            }
1280
        }
1281
        if (isset($this->_packageInfo['bundle'])) {
1282
            $release = 'bundle';
1283
            if (isset($this->_packageInfo['providesextension'])) {
1284
                $this->_cannotProvideExtension($release);
1285
            }
1286
            if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
1287
                $this->_cannotHaveSrcpackage($release);
1288
            }
1289
            $releases = $this->_packageInfo['bundle'];
1290
            if (!is_array($releases) || !isset($releases[0])) {
1291
                $releases = array($releases);
1292
            }
1293
            foreach ($releases as $rel) {
1294
                $this->_stupidSchemaValidate(array(
1295
                    '*installconditions',
1296
                    '*filelist',
1297
                ), $rel, '<bundle>');
1298
            }
1299
        }
1300
        foreach ($releases as $rel) {
1301
            if (is_array($rel) && array_key_exists('installconditions', $rel)) {
1302
                $this->_validateInstallConditions($rel['installconditions'],
1303
                    "<$release><installconditions>");
1304
            }
1305
            if (is_array($rel) && array_key_exists('filelist', $rel)) {
1306
                if ($rel['filelist']) {
1307
 
1308
                    $this->_validateFilelist($rel['filelist'], true);
1309
                }
1310
            }
1311
        }
1312
    }
1313
 
1314
    /**
1315
     * This is here to allow role extension through plugins
1316
     * @param string
1317
     */
1318
    function _validateRole($role)
1319
    {
1320
        return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
1321
    }
1322
 
1323
    function _pearVersionTooLow($version)
1324
    {
1325
        $this->_stack->push(__FUNCTION__, 'error',
1326
            array('version' => $version),
1327
            'This package.xml requires PEAR version %version% to parse properly, we are ' .
1328
            'version 1.5.1');
1329
    }
1330
 
1331
    function _invalidTagOrder($oktags, $actual, $root)
1332
    {
1333
        $this->_stack->push(__FUNCTION__, 'error',
1334
            array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
1335
            'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
1336
    }
1337
 
1338
    function _ignoreNotAllowed($type)
1339
    {
1340
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1341
            '<%type%> is not allowed inside global <contents>, only inside ' .
1342
            '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
1343
    }
1344
 
1345
    function _fileNotAllowed($type)
1346
    {
1347
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1348
            '<%type%> is not allowed inside release <filelist>, only inside ' .
1349
            '<contents>, use <ignore> and <install> only');
1350
    }
1351
 
1352
    function _tagMissingAttribute($tag, $attr, $context)
1353
    {
1354
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1355
            'attribute' => $attr, 'context' => $context),
1356
            'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
1357
    }
1358
 
1359
    function _tagHasNoAttribs($tag, $context)
1360
    {
1361
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
1362
            'context' => $context),
1363
            'tag <%tag%> has no attributes in context "%context%"');
1364
    }
1365
 
1366
    function _invalidInternalStructure()
1367
    {
1368
        $this->_stack->push(__FUNCTION__, 'exception', array(),
1369
            'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
1370
    }
1371
 
1372
    function _invalidFileRole($file, $dir, $role)
1373
    {
1374
        $this->_stack->push(__FUNCTION__, 'error', array(
1375
            'file' => $file, 'dir' => $dir, 'role' => $role,
1376
            'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
1377
            'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
1378
    }
1379
 
1380
    function _invalidFileName($file, $dir)
1381
    {
1382
        $this->_stack->push(__FUNCTION__, 'error', array(
1383
            'file' => $file),
1384
            'File "%file%" cannot begin with "."');
1385
    }
1386
 
1387
    function _filelistCannotContainFile($filelist)
1388
    {
1389
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
1390
            '<%tag%> can only contain <dir>, contains <file>.  Use ' .
1391
            '<dir name="/"> as the first dir element');
1392
    }
1393
 
1394
    function _filelistMustContainDir($filelist)
1395
    {
1396
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
1397
            '<%tag%> must contain <dir>.  Use <dir name="/"> as the ' .
1398
            'first dir element');
1399
    }
1400
 
1401
    function _tagCannotBeEmpty($tag)
1402
    {
1403
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1404
            '<%tag%> cannot be empty (<%tag%/>)');
1405
    }
1406
 
1407
    function _UrlOrChannel($type, $name)
1408
    {
1409
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1410
            'name' => $name),
1411
            'Required dependency <%type%> "%name%" can have either url OR ' .
1412
            'channel attributes, and not both');
1413
    }
1414
 
1415
    function _NoChannel($type, $name)
1416
    {
1417
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1418
            'name' => $name),
1419
            'Required dependency <%type%> "%name%" must have either url OR ' .
1420
            'channel attributes');
1421
    }
1422
 
1423
    function _UrlOrChannelGroup($type, $name, $group)
1424
    {
1425
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1426
            'name' => $name, 'group' => $group),
1427
            'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
1428
            'channel attributes, and not both');
1429
    }
1430
 
1431
    function _NoChannelGroup($type, $name, $group)
1432
    {
1433
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
1434
            'name' => $name, 'group' => $group),
1435
            'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
1436
            'channel attributes');
1437
    }
1438
 
1439
    function _unknownChannel($channel)
1440
    {
1441
        $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
1442
            'Unknown channel "%channel%"');
1443
    }
1444
 
1445
    function _noPackageVersion()
1446
    {
1447
        $this->_stack->push(__FUNCTION__, 'error', array(),
1448
            'package.xml <package> tag has no version attribute, or version is not 2.0');
1449
    }
1450
 
1451
    function _NoBundledPackages()
1452
    {
1453
        $this->_stack->push(__FUNCTION__, 'error', array(),
1454
            'No <bundledpackage> tag was found in <contents>, required for bundle packages');
1455
    }
1456
 
1457
    function _AtLeast2BundledPackages()
1458
    {
1459
        $this->_stack->push(__FUNCTION__, 'error', array(),
1460
            'At least 2 packages must be bundled in a bundle package');
1461
    }
1462
 
1463
    function _ChannelOrUri($name)
1464
    {
1465
        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1466
            'Bundled package "%name%" can have either a uri or a channel, not both');
1467
    }
1468
 
1469
    function _noChildTag($child, $tag)
1470
    {
1471
        $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
1472
            'Tag <%tag%> is missing child tag <%child%>');
1473
    }
1474
 
1475
    function _invalidVersion($type, $value)
1476
    {
1477
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
1478
            'Version type <%type%> is not a valid version (%value%)');
1479
    }
1480
 
1481
    function _invalidState($type, $value)
1482
    {
1483
        $states = array('stable', 'beta', 'alpha', 'devel');
1484
        if ($type != 'api') {
1485
            $states[] = 'snapshot';
1486
        }
1487
        if (strtolower($value) == 'rc') {
1488
            $this->_stack->push(__FUNCTION__, 'error',
1489
                array('version' => $this->_packageInfo['version']['release']),
1490
                'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
1491
        }
1492
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
1493
            'types' => $states),
1494
            'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
1495
            '%types%');
1496
    }
1497
 
1498
    function _invalidTask($task, $ret, $file)
1499
    {
1500
        switch ($ret[0]) {
1501
            case PEAR_TASK_ERROR_MISSING_ATTRIB :
1502
                $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
1503
                $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
1504
            break;
1505
            case PEAR_TASK_ERROR_NOATTRIBS :
1506
                $info = array('task' => $task, 'file' => $file);
1507
                $msg = 'task <%task%> has no attributes in file %file%';
1508
            break;
1509
            case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
1510
                $info = array('attrib' => $ret[1], 'values' => $ret[3],
1511
                    'was' => $ret[2], 'task' => $task, 'file' => $file);
1512
                $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
1513
                    'in file %file%, expecting one of "%values%"';
1514
            break;
1515
            case PEAR_TASK_ERROR_INVALID :
1516
                $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
1517
                $msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
1518
            break;
1519
        }
1520
        $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
1521
    }
1522
 
1523
    function _unknownTask($task, $file)
1524
    {
1525
        $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
1526
            'Unknown task "%task%" passed in file <file name="%file%">');
1527
    }
1528
 
1529
    function _subpackageCannotProvideExtension($name)
1530
    {
1531
        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1532
            'Subpackage dependency "%name%" cannot use <providesextension>, ' .
1533
            'only package dependencies can use this tag');
1534
    }
1535
 
1536
    function _subpackagesCannotConflict($name)
1537
    {
1538
        $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
1539
            'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
1540
            'only package dependencies can use this tag');
1541
    }
1542
 
1543
    function _cannotProvideExtension($release)
1544
    {
1545
        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1546
            '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
1547
    }
1548
 
1549
    function _mustProvideExtension($release)
1550
    {
1551
        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1552
            '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
1553
    }
1554
 
1555
    function _cannotHaveSrcpackage($release)
1556
    {
1557
        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1558
            '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
1559
    }
1560
 
1561
    function _mustSrcPackage($release)
1562
    {
1563
        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1564
            '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
1565
    }
1566
 
1567
    function _mustSrcuri($release)
1568
    {
1569
        $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
1570
            '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
1571
    }
1572
 
1573
    function _uriDepsCannotHaveVersioning($type)
1574
    {
1575
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1576
            '%type%: dependencies with a <uri> tag cannot have any versioning information');
1577
    }
1578
 
1579
    function _conflictingDepsCannotHaveVersioning($type)
1580
    {
1581
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1582
            '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
1583
            'exclude specific versions of a dependency');
1584
    }
1585
 
1586
    function _DepchannelCannotBeUri($type)
1587
    {
1588
        $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
1589
            '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
1590
            'dependencies only');
1591
    }
1592
 
1593
    function _bundledPackagesMustBeFilename()
1594
    {
1595
        $this->_stack->push(__FUNCTION__, 'error', array(),
1596
            '<bundledpackage> tags must contain only the filename of a package release ' .
1597
            'in the bundle');
1598
    }
1599
 
1600
    function _binaryPackageMustBePackagename()
1601
    {
1602
        $this->_stack->push(__FUNCTION__, 'error', array(),
1603
            '<binarypackage> tags must contain the name of a package that is ' .
1604
            'a compiled version of this extsrc/zendextsrc package');
1605
    }
1606
 
1607
    function _fileNotFound($file)
1608
    {
1609
        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1610
            'File "%file%" in package.xml does not exist');
1611
    }
1612
 
1613
    function _notInContents($file, $tag)
1614
    {
1615
        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
1616
            '<%tag% name="%file%"> is invalid, file is not in <contents>');
1617
    }
1618
 
1619
    function _cannotValidateNoPathSet()
1620
    {
1621
        $this->_stack->push(__FUNCTION__, 'error', array(),
1622
            'Cannot validate files, no path to package file is set (use setPackageFile())');
1623
    }
1624
 
1625
    function _usesroletaskMustHaveChannelOrUri($role, $tag)
1626
    {
1627
        $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1628
            '<%tag%> must contain either <uri>, or <channel> and <package>');
1629
    }
1630
 
1631
    function _usesroletaskMustHavePackage($role, $tag)
1632
    {
1633
        $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
1634
            '<%tag%> must contain <package>');
1635
    }
1636
 
1637
    function _usesroletaskMustHaveRoleTask($tag, $type)
1638
    {
1639
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
1640
            '<%tag%> must contain <%type%> defining the %type% to be used');
1641
    }
1642
 
1643
    function _cannotConflictWithAllOs($type)
1644
    {
1645
        $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
1646
            '%tag% cannot conflict with all OSes');
1647
    }
1648
 
1649
    function _invalidDepGroupName($name)
1650
    {
1651
        $this->_stack->push(__FUNCTION__, 'error', array('group' => $name),
1652
            'Invalid dependency group name "%name%"');
1653
    }
1654
 
1655
    function _multipleToplevelDirNotAllowed()
1656
    {
1657
        $this->_stack->push(__FUNCTION__, 'error', array(),
1658
            'Multiple top-level <dir> tags are not allowed.  Enclose them ' .
1659
                'in a <dir name="/">');
1660
    }
1661
 
1662
    function _multipleInstallAs($file)
1663
    {
1664
        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1665
            'Only one <install> tag is allowed for file "%file%"');
1666
    }
1667
 
1668
    function _ignoreAndInstallAs($file)
1669
    {
1670
        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1671
            'Cannot have both <ignore> and <install> tags for file "%file%"');
1672
    }
1673
 
1674
    function _analyzeBundledPackages()
1675
    {
1676
        if (!$this->_isValid) {
1677
            return false;
1678
        }
1679
        if (!$this->_pf->getPackageType() == 'bundle') {
1680
            return false;
1681
        }
1682
        if (!isset($this->_pf->_packageFile)) {
1683
            return false;
1684
        }
1685
        $dir_prefix = dirname($this->_pf->_packageFile);
1686
        $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
1687
            array('PEAR_Common', 'log');
1688
        $info = $this->_pf->getContents();
1689
        $info = $info['bundledpackage'];
1690
        if (!is_array($info)) {
1691
            $info = array($info);
1692
        }
1693
        $pkg = &new PEAR_PackageFile($this->_pf->_config);
1694
        foreach ($info as $package) {
1695
            if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
1696
                $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
1697
                $this->_isValid = 0;
1698
                continue;
1699
            }
1700
            call_user_func_array($log, array(1, "Analyzing bundled package $package"));
1701
            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1702
            $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
1703
                PEAR_VALIDATE_NORMAL);
1704
            PEAR::popErrorHandling();
1705
            if (PEAR::isError($ret)) {
1706
                call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
1707
                    'package'));
1708
                $inf = $ret->getUserInfo();
1709
                if (is_array($inf)) {
1710
                    foreach ($inf as $err) {
1711
                        call_user_func_array($log, array(1, $err['message']));
1712
                    }
1713
                }
1714
                return false;
1715
            }
1716
        }
1717
        return true;
1718
    }
1719
 
1720
    function _analyzePhpFiles()
1721
    {
1722
        if (!$this->_isValid) {
1723
            return false;
1724
        }
1725
        if (!isset($this->_pf->_packageFile)) {
1726
            $this->_cannotValidateNoPathSet();
1727
            return false;
1728
        }
1729
        $dir_prefix = dirname($this->_pf->_packageFile);
1730
        $common = new PEAR_Common;
1731
        $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
1732
            array(&$common, 'log');
1733
        $info = $this->_pf->getContents();
1734
        if (!$info || !isset($info['dir']['file'])) {
1735
            $this->_tagCannotBeEmpty('contents><dir');
1736
            return false;
1737
        }
1738
        $info = $info['dir']['file'];
1739
        if (isset($info['attribs'])) {
1740
            $info = array($info);
1741
        }
1742
        $provides = array();
1743
        foreach ($info as $fa) {
1744
            $fa = $fa['attribs'];
1745
            $file = $fa['name'];
1746
            if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
1747
                $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
1748
                $this->_isValid = 0;
1749
                continue;
1750
            }
1751
            if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
1752
                call_user_func_array($log, array(1, "Analyzing $file"));
1753
                $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
1754
                if ($srcinfo) {
1755
                    $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
1756
                }
1757
            }
1758
        }
1759
        $this->_packageName = $pn = $this->_pf->getPackage();
1760
        $pnl = strlen($pn);
1761
        foreach ($provides as $key => $what) {
1762
            if (isset($what['explicit']) || !$what) {
1763
                // skip conformance checks if the provides entry is
1764
                // specified in the package.xml file
1765
                continue;
1766
            }
1767
            extract($what);
1768
            if ($type == 'class') {
1769
                if (!strncasecmp($name, $pn, $pnl)) {
1770
                    continue;
1771
                }
1772
                $this->_stack->push(__FUNCTION__, 'warning',
1773
                    array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
1774
                    'in %file%: %type% "%name%" not prefixed with package name "%package%"');
1775
            } elseif ($type == 'function') {
1776
                if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
1777
                    continue;
1778
                }
1779
                $this->_stack->push(__FUNCTION__, 'warning',
1780
                    array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
1781
                    'in %file%: %type% "%name%" not prefixed with package name "%package%"');
1782
            }
1783
        }
1784
        return $this->_isValid;
1785
    }
1786
 
1787
    /**
1788
     * Analyze the source code of the given PHP file
1789
     *
1790
     * @param  string Filename of the PHP file
1791
     * @param  boolean whether to analyze $file as the file contents
1792
     * @return mixed
1793
     */
1794
    function analyzeSourceCode($file, $string = false)
1795
    {
1796
        if (!function_exists("token_get_all")) {
1797
            $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1798
                'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
1799
            return false;
1800
        }
1801
        if (!defined('T_DOC_COMMENT')) {
1802
            define('T_DOC_COMMENT', T_COMMENT);
1803
        }
1804
        if (!defined('T_INTERFACE')) {
1805
            define('T_INTERFACE', -1);
1806
        }
1807
        if (!defined('T_IMPLEMENTS')) {
1808
            define('T_IMPLEMENTS', -1);
1809
        }
1810
        if ($string) {
1811
            $contents = $file;
1812
        } else {
1813
            if (!$fp = @fopen($file, "r")) {
1814
                return false;
1815
            }
1816
            fclose($fp);
1817
            $contents = file_get_contents($file);
1818
        }
1819
        $tokens = token_get_all($contents);
1820
/*
1821
        for ($i = 0; $i < sizeof($tokens); $i++) {
1822
            @list($token, $data) = $tokens[$i];
1823
            if (is_string($token)) {
1824
                var_dump($token);
1825
            } else {
1826
                print token_name($token) . ' ';
1827
                var_dump(rtrim($data));
1828
            }
1829
        }
1830
*/
1831
        $look_for = 0;
1832
        $paren_level = 0;
1833
        $bracket_level = 0;
1834
        $brace_level = 0;
1835
        $lastphpdoc = '';
1836
        $current_class = '';
1837
        $current_interface = '';
1838
        $current_class_level = -1;
1839
        $current_function = '';
1840
        $current_function_level = -1;
1841
        $declared_classes = array();
1842
        $declared_interfaces = array();
1843
        $declared_functions = array();
1844
        $declared_methods = array();
1845
        $used_classes = array();
1846
        $used_functions = array();
1847
        $extends = array();
1848
        $implements = array();
1849
        $nodeps = array();
1850
        $inquote = false;
1851
        $interface = false;
1852
        for ($i = 0; $i < sizeof($tokens); $i++) {
1853
            if (is_array($tokens[$i])) {
1854
                list($token, $data) = $tokens[$i];
1855
            } else {
1856
                $token = $tokens[$i];
1857
                $data = '';
1858
            }
1859
            if ($inquote) {
1860
                if ($token != '"' && $token != T_END_HEREDOC) {
1861
                    continue;
1862
                } else {
1863
                    $inquote = false;
1864
                    continue;
1865
                }
1866
            }
1867
            switch ($token) {
1868
                case T_WHITESPACE :
1869
                    continue;
1870
                case ';':
1871
                    if ($interface) {
1872
                        $current_function = '';
1873
                        $current_function_level = -1;
1874
                    }
1875
                    break;
1876
                case '"':
1877
                case T_START_HEREDOC:
1878
                    $inquote = true;
1879
                    break;
1880
                case T_CURLY_OPEN:
1881
                case T_DOLLAR_OPEN_CURLY_BRACES:
1882
                case '{': $brace_level++; continue 2;
1883
                case '}':
1884
                    $brace_level--;
1885
                    if ($current_class_level == $brace_level) {
1886
                        $current_class = '';
1887
                        $current_class_level = -1;
1888
                    }
1889
                    if ($current_function_level == $brace_level) {
1890
                        $current_function = '';
1891
                        $current_function_level = -1;
1892
                    }
1893
                    continue 2;
1894
                case '[': $bracket_level++; continue 2;
1895
                case ']': $bracket_level--; continue 2;
1896
                case '(': $paren_level++;   continue 2;
1897
                case ')': $paren_level--;   continue 2;
1898
                case T_INTERFACE:
1899
                    $interface = true;
1900
                case T_CLASS:
1901
                    if (($current_class_level != -1) || ($current_function_level != -1)) {
1902
                        $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
1903
                        'Parser error: invalid PHP found in file "%file%"');
1904
                        return false;
1905
                    }
1906
                case T_FUNCTION:
1907
                case T_NEW:
1908
                case T_EXTENDS:
1909
                case T_IMPLEMENTS:
1910
                    $look_for = $token;
1911
                    continue 2;
1912
                case T_STRING:
1913
                    if (version_compare(zend_version(), '2.0', '<')) {
1914
                        if (in_array(strtolower($data),
1915
                            array('public', 'private', 'protected', 'abstract',
1916
                                  'interface', 'implements', 'throw')
1917
                                 )) {
1918
                            $this->_stack->push(__FUNCTION__, 'warning', array(
1919
                                'file' => $file),
1920
                                'Error, PHP5 token encountered in %file%,' .
1921
                                ' analysis should be in PHP5');
1922
                        }
1923
                    }
1924
                    if ($look_for == T_CLASS) {
1925
                        $current_class = $data;
1926
                        $current_class_level = $brace_level;
1927
                        $declared_classes[] = $current_class;
1928
                    } elseif ($look_for == T_INTERFACE) {
1929
                        $current_interface = $data;
1930
                        $current_class_level = $brace_level;
1931
                        $declared_interfaces[] = $current_interface;
1932
                    } elseif ($look_for == T_IMPLEMENTS) {
1933
                        $implements[$current_class] = $data;
1934
                    } elseif ($look_for == T_EXTENDS) {
1935
                        $extends[$current_class] = $data;
1936
                    } elseif ($look_for == T_FUNCTION) {
1937
                        if ($current_class) {
1938
                            $current_function = "$current_class::$data";
1939
                            $declared_methods[$current_class][] = $data;
1940
                        } elseif ($current_interface) {
1941
                            $current_function = "$current_interface::$data";
1942
                            $declared_methods[$current_interface][] = $data;
1943
                        } else {
1944
                            $current_function = $data;
1945
                            $declared_functions[] = $current_function;
1946
                        }
1947
                        $current_function_level = $brace_level;
1948
                        $m = array();
1949
                    } elseif ($look_for == T_NEW) {
1950
                        $used_classes[$data] = true;
1951
                    }
1952
                    $look_for = 0;
1953
                    continue 2;
1954
                case T_VARIABLE:
1955
                    $look_for = 0;
1956
                    continue 2;
1957
                case T_DOC_COMMENT:
1958
                case T_COMMENT:
1959
                    if (preg_match('!^/\*\*\s!', $data)) {
1960
                        $lastphpdoc = $data;
1961
                        if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
1962
                            $nodeps = array_merge($nodeps, $m[1]);
1963
                        }
1964
                    }
1965
                    continue 2;
1966
                case T_DOUBLE_COLON:
1967
                    if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
1968
                        $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
1969
                            'Parser error: invalid PHP found in file "%file%"');
1970
                        return false;
1971
                    }
1972
                    $class = $tokens[$i - 1][1];
1973
                    if (strtolower($class) != 'parent') {
1974
                        $used_classes[$class] = true;
1975
                    }
1976
                    continue 2;
1977
            }
1978
        }
1979
        return array(
1980
            "source_file" => $file,
1981
            "declared_classes" => $declared_classes,
1982
            "declared_interfaces" => $declared_interfaces,
1983
            "declared_methods" => $declared_methods,
1984
            "declared_functions" => $declared_functions,
1985
            "used_classes" => array_diff(array_keys($used_classes), $nodeps),
1986
            "inheritance" => $extends,
1987
            "implements" => $implements,
1988
            );
1989
    }
1990
 
1991
    /**
1992
     * Build a "provides" array from data returned by
1993
     * analyzeSourceCode().  The format of the built array is like
1994
     * this:
1995
     *
1996
     *  array(
1997
     *    'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
1998
     *    ...
1999
     *  )
2000
     *
2001
     *
2002
     * @param array $srcinfo array with information about a source file
2003
     * as returned by the analyzeSourceCode() method.
2004
     *
2005
     * @return void
2006
     *
2007
     * @access private
2008
     *
2009
     */
2010
    function _buildProvidesArray($srcinfo)
2011
    {
2012
        if (!$this->_isValid) {
2013
            return array();
2014
        }
2015
        $providesret = array();
2016
        $file = basename($srcinfo['source_file']);
2017
        $pn = $this->_pf->getPackage();
2018
        $pnl = strlen($pn);
2019
        foreach ($srcinfo['declared_classes'] as $class) {
2020
            $key = "class;$class";
2021
            if (isset($providesret[$key])) {
2022
                continue;
2023
            }
2024
            $providesret[$key] =
2025
                array('file'=> $file, 'type' => 'class', 'name' => $class);
2026
            if (isset($srcinfo['inheritance'][$class])) {
2027
                $providesret[$key]['extends'] =
2028
                    $srcinfo['inheritance'][$class];
2029
            }
2030
        }
2031
        foreach ($srcinfo['declared_methods'] as $class => $methods) {
2032
            foreach ($methods as $method) {
2033
                $function = "$class::$method";
2034
                $key = "function;$function";
2035
                if ($method{0} == '_' || !strcasecmp($method, $class) ||
2036
                    isset($providesret[$key])) {
2037
                    continue;
2038
                }
2039
                $providesret[$key] =
2040
                    array('file'=> $file, 'type' => 'function', 'name' => $function);
2041
            }
2042
        }
2043
 
2044
        foreach ($srcinfo['declared_functions'] as $function) {
2045
            $key = "function;$function";
2046
            if ($function{0} == '_' || isset($providesret[$key])) {
2047
                continue;
2048
            }
2049
            if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
2050
                $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
2051
            }
2052
            $providesret[$key] =
2053
                array('file'=> $file, 'type' => 'function', 'name' => $function);
2054
        }
2055
        return $providesret;
2056
    }
2057
}
2058
?>