Хранилища Subversion ant

Редакция

К новейшей редакции | Содержимое файла | Последнее изменение | Открыть журнал | RSS

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