Subversion Repositories Applications.gtt

Rev

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

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