Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * Copyright (c) 2008-2009, Davey Shafik <davey@php.net>
4
 *                          Laurent Laville <pear@laurent-laville.org>
5
 *
6
 * All rights reserved.
7
 *
8
 * Redistribution and use in source and binary forms, with or without
9
 * modification, are permitted provided that the following conditions
10
 * are met:
11
 *
12
 *     * Redistributions of source code must retain the above copyright
13
 *       notice, this list of conditions and the following disclaimer.
14
 *     * Redistributions in binary form must reproduce the above copyright
15
 *       notice, this list of conditions and the following disclaimer in the
16
 *       documentation and/or other materials provided with the distribution.
17
 *     * Neither the name of the authors nor the names of its contributors
18
 *       may be used to endorse or promote products derived from this software
19
 *       without specific prior written permission.
20
 *
21
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
25
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
 * POSSIBILITY OF SUCH DAMAGE.
32
 *
33
 * PHP versions 4 and 5
34
 *
35
 * @category PHP
36
 * @package  PHP_CompatInfo
37
 * @author   Davey Shafik <davey@php.net>
38
 * @author   Laurent Laville <pear@laurent-laville.org>
39
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD
40
 * @version  CVS: $Id: Parser.php,v 1.21 2009/01/02 10:18:47 farell Exp $
41
 * @link     http://pear.php.net/package/PHP_CompatInfo
42
 * @since    File available since Release 1.8.0b2
43
 */
44
 
45
require_once 'Event/Dispatcher.php';
46
require_once 'File/Find.php';
47
 
48
/**
49
 * An array of class init versions and extension
50
 */
51
require_once 'PHP/CompatInfo/class_array.php';
52
 
53
/**
54
 * An array of function init versions and extension
55
 */
56
require_once 'PHP/CompatInfo/func_array.php';
57
 
58
/**
59
 * An array of constants and their init versions
60
 */
61
require_once 'PHP/CompatInfo/const_array.php';
62
 
63
/**
64
 * An abstract base class for CompatInfo renderers
65
 */
66
require_once 'PHP/CompatInfo/Renderer.php';
67
 
68
/**
69
 * Event name of parsing data source start process
70
 */
71
define('PHP_COMPATINFO_EVENT_AUDITSTARTED', 'auditStarted');
72
/**
73
 * Event name of parsing data source end process
74
 */
75
define('PHP_COMPATINFO_EVENT_AUDITFINISHED', 'auditFinished');
76
/**
77
 * Event name of parsing a file start process
78
 */
79
define('PHP_COMPATINFO_EVENT_FILESTARTED', 'fileStarted');
80
/**
81
 * Event name of parsing a file end process
82
 */
83
define('PHP_COMPATINFO_EVENT_FILEFINISHED', 'fileFinished');
84
/**
85
 * Event name of parsing a file start process
86
 */
87
define('PHP_COMPATINFO_EVENT_CODESTARTED', 'codeStarted');
88
/**
89
 * Event name of parsing a file end process
90
 */
91
define('PHP_COMPATINFO_EVENT_CODEFINISHED', 'codeFinished');
92
 
93
/**
94
 * Parser logic
95
 *
96
 * This class is the model in the MVC design pattern of API 1.8.0 (since beta 2)
97
 *
98
 * @category PHP
99
 * @package  PHP_CompatInfo
100
 * @author   Laurent Laville <pear@laurent-laville.org>
101
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD
102
 * @version  Release: 1.9.0
103
 * @link     http://pear.php.net/package/PHP_CompatInfo
104
 * @since    Class available since Release 1.8.0b2
105
 */
106
class PHP_CompatInfo_Parser
107
{
108
    /**
109
     * Instance of concrete renderer used to show parse results
110
     *
111
     * @var    object
112
     * @since  1.8.0b2
113
     * @access protected
114
     */
115
    var $renderer;
116
 
117
    /**
118
     * Stores the event dispatcher which handles notifications
119
     *
120
     * @var    object
121
     * @since  1.8.0b2
122
     * @access protected
123
     */
124
    var $dispatcher;
125
 
126
    /**
127
     * Count the number of observer registered.
128
     * The Event_Dispatcher will be add on first observer registration, and
129
     * will be removed with the last observer.
130
     *
131
     * @var    integer
132
     * @since  1.8.0b2
133
     * @access private
134
     */
135
    var $_observerCount;
136
 
137
    /**
138
     * @var string Earliest version of PHP to use
139
     * @since  0.7.0
140
     */
141
    var $latest_version = '4.0.0';
142
 
143
    /**
144
     * @var string Last version of PHP to use
145
     */
146
    var $earliest_version = '';
147
 
148
    /**
149
     * @var array Parsing options
150
     */
151
    var $options;
152
 
153
    /**
154
     * @var array Data Source
155
     * @since  1.8.0b2
156
     */
157
    var $dataSource;
158
 
159
    /**
160
     * @var array Directory list found when parsing data source
161
     * @since  1.8.0b2
162
     * @see    getDirlist()
163
     */
164
    var $directories;
165
 
166
    /**
167
     * @var array List of files ignored when parsing data source
168
     * @since  1.8.0b2
169
     * @see    getIgnoredFiles()
170
     */
171
    var $ignored_files = array();
172
 
173
    /**
174
     * @var array Result of the latest data source parsing
175
     * @since  1.9.0b1
176
     * @see    parseData()
177
     */
178
    var $latest_parse = null;
179
 
180
    /**
181
     * Class constructor (ZE1) for PHP4
182
     *
183
     * @access public
184
     * @since  version 1.8.0b2 (2008-06-03)
185
     */
186
    function PHP_CompatInfo_Parser()
187
    {
188
        $this->__construct();
189
    }
190
 
191
    /**
192
     * Class constructor (ZE2) for PHP5+
193
     *
194
     * @access public
195
     * @since  version 1.8.0b2 (2008-06-03)
196
     */
197
    function __construct()
198
    {
199
        $this->options = array(
200
            'file_ext' => array('php', 'php4', 'inc', 'phtml'),
201
            'recurse_dir' => true,
202
            'debug' => false,
203
            'is_string' => false,
204
            'ignore_files' => array(),
205
            'ignore_dirs' => array()
206
            );
207
    }
208
 
209
    /**
210
     * Set up driver to be used
211
     *
212
     * Set up driver to be used, dependant on specified type.
213
     *
214
     * @param string $type Name the type of driver (html, text...)
215
     * @param array  $conf A hash containing any additional configuration
216
     *
217
     * @access public
218
     * @return void
219
     * @since  version 1.8.0b2 (2008-06-03)
220
     */
221
    function setOutputDriver($type, $conf = array())
222
    {
223
        $this->renderer =& PHP_CompatInfo_Renderer::factory($this, $type, $conf);
224
    }
225
 
226
    /**
227
     * Registers a new listener
228
     *
229
     * Registers a new listener with the given criteria.
230
     *
231
     * @param mixed  $callback A PHP callback
232
     * @param string $nName    (optional) Expected notification name
233
     *
234
     * @access public
235
     * @return void
236
     * @since  version 1.8.0b2 (2008-06-03)
237
     */
238
    function addListener($callback, $nName = EVENT_DISPATCHER_GLOBAL)
239
    {
240
        $this->dispatcher =& Event_Dispatcher::getInstance();
241
        // $this->dispatcher->setNotificationClass('PHP_CompatInfo_Audit');
242
        $this->dispatcher->addObserver($callback, $nName);
243
        $this->_observerCount++;
244
    }
245
 
246
    /**
247
     * Removes a registered listener
248
     *
249
     * Removes a registered listener that correspond to the given criteria.
250
     *
251
     * @param mixed  $callback A PHP callback
252
     * @param string $nName    (optional) Expected notification name
253
     *
254
     * @access public
255
     * @return bool  True if listener was removed, false otherwise.
256
     * @since  version 1.8.0b2 (2008-06-03)
257
     */
258
    function removeListener($callback, $nName = EVENT_DISPATCHER_GLOBAL)
259
    {
260
        $result = $this->dispatcher->removeObserver($callback, $nName);
261
 
262
        if ($result) {
263
            $this->_observerCount--;
264
            if ($this->_observerCount == 0) {
265
                unset($this->dispatcher);
266
            }
267
        }
268
        return $result;
269
    }
270
 
271
    /**
272
     * Post a new notification to all listeners registered.
273
     *
274
     * This notification occured only if a dispatcher exists. That means if
275
     * at least one listener was registered.
276
     *
277
     * @param string $event Name of the notification handler
278
     * @param array  $info  (optional) Additional information about the notification
279
     *
280
     * @access public
281
     * @return void
282
     * @since  version 1.8.0b2 (2008-06-03)
283
     */
284
    function notifyListeners($event, $info = array())
285
    {
286
        if (isset($this->dispatcher)) {
287
            $this->dispatcher->post($this, $event, $info);
288
        }
289
    }
290
 
291
    /**
292
     * Load components list
293
     *
294
     * Load components list for a PHP version or subset
295
     *
296
     * @param string         $min           PHP minimal version
297
     * @param string|boolean $max           (optional) PHP maximal version
298
     * @param boolean        $include_const (optional) include constants list
299
     *                                                 in final result
300
     * @param boolean        $groupby_vers  (optional) give initial php version
301
     *                                                 of function or constant
302
     *
303
     * @return array         An array of php function/constant names history
304
     * @access public
305
     * @static
306
     * @since  version 1.2.0 (2006-08-23)
307
     */
308
    function loadVersion($min, $max = false,
309
                         $include_const = false, $groupby_vers = false)
310
    {
311
        $keys = array();
312
        foreach ($GLOBALS['_PHP_COMPATINFO_FUNCS'] as $func => $arr) {
313
            if (isset($arr['pecl']) && $arr['pecl'] === true) {
314
                continue;
315
            }
316
            $vmin = $arr['init'];
317
            if (version_compare($vmin, $min) < 0) {
318
                continue;
319
            }
320
            if ($max) {
321
                $end = (isset($arr['end'])) ? $arr['end'] : $vmin;
322
 
323
                if (version_compare($end, $max) < 1) {
324
                    if ($groupby_vers === true) {
325
                        $keys[$vmin][] = $func;
326
                    } else {
327
                        $keys[] = $func;
328
                    }
329
                }
330
            } else {
331
                if ($groupby_vers === true) {
332
                    $keys[$vmin][] = $func;
333
                } else {
334
                    $keys[] = $func;
335
                }
336
            }
337
        }
338
        if ($groupby_vers === true) {
339
            foreach ($keys as $vmin => $func) {
340
                sort($keys[$vmin]);
341
            }
342
            ksort($keys);
343
        } else {
344
            sort($keys);
345
        }
346
 
347
        if ($include_const === true) {
348
            $keys = array('functions' => $keys, 'constants' => array());
349
            foreach ($GLOBALS['_PHP_COMPATINFO_CONST'] as $const => $arr) {
350
                $vmin = $arr['init'];
351
                if (version_compare($vmin, $min) < 0) {
352
                    continue;
353
                }
354
                if ($max) {
355
                    $end = (isset($arr['end'])) ? $arr['end'] : $vmin;
356
 
357
                    if (version_compare($end, $max) < 1) {
358
                        if ($groupby_vers === true) {
359
                            $keys['constants'][$vmin][] = $arr['name'];
360
                        } else {
361
                            $keys['constants'][] = $arr['name'];
362
                        }
363
                    }
364
                } else {
365
                    if ($groupby_vers === true) {
366
                        $keys['constants'][$vmin][] = $arr['name'];
367
                    } else {
368
                        $keys['constants'][] = $arr['name'];
369
                    }
370
                }
371
            }
372
            ksort($keys['constants']);
373
        }
374
        return $keys;
375
    }
376
 
377
    /**
378
     * Returns list of directory parsed
379
     *
380
     * Returns list of directory parsed, depending of restrictive parser options.
381
     *
382
     * @param mixed $dir     The directory name
383
     * @param array $options An array of parser options. See parseData() method.
384
     *
385
     * @access public
386
     * @return array   list of directories that should be parsed
387
     * @since  version 1.8.0b2 (2008-06-03)
388
     */
389
    function getDirlist($dir, $options)
390
    {
391
        if (!isset($this->directories)) {
392
            $this->getFilelist($dir, $options);
393
        }
394
 
395
        return $this->directories;
396
    }
397
 
398
    /**
399
     * Returns list of files parsed
400
     *
401
     * Returns list of files parsed, depending of restrictive parser options.
402
     *
403
     * @param mixed $dir     The directory name where to look files
404
     * @param array $options An array of parser options. See parseData() method.
405
     *
406
     * @access public
407
     * @return array   list of files that should be parsed
408
     * @since  version 1.8.0b2 (2008-06-03)
409
     */
410
    function getFilelist($dir, $options)
411
    {
412
        $skipped = array();
413
        $ignored = array();
414
 
415
        $options             = array_merge($this->options, $options);
416
        $options['file_ext'] = array_map('strtolower', $options['file_ext']);
417
 
418
        if ($dir{strlen($dir)-1} == '/' || $dir{strlen($dir)-1} == '\\') {
419
            $dir = substr($dir, 0, -1);
420
        }
421
 
422
        // use system directory separator rather than forward slash by default
423
        $ff         = new File_Find();
424
        $ff->dirsep = DIRECTORY_SEPARATOR;
425
 
426
        // get directory list that should be ignored from scope
427
        $ignore_dirs = array();
428
        if (count($options['ignore_dirs']) > 0) {
429
            foreach ($options['ignore_dirs'] as $cond) {
430
                $cond        = str_replace('\\', "\\\\", $cond);
431
                $dirs        = $ff->search('`'.$cond.'`', $dir, 'perl',
432
                                           true, 'directories');
433
                $ignore_dirs = array_merge($ignore_dirs, $dirs);
434
            }
435
        }
436
 
437
        // get file list that should be ignored from scope
438
        $ignore_files = array();
439
        if (count($options['ignore_files']) > 0) {
440
            foreach ($options['ignore_files'] as $cond) {
441
                $cond         = str_replace('\\', "\\\\", $cond);
442
                $files        = $ff->search('`'.$cond.'`', $dir, 'perl',
443
                                            true, 'files');
444
                $ignore_files = array_merge($ignore_files, $files);
445
            }
446
        }
447
 
448
        list($directories, $files) = $ff->maptree($dir);
449
 
450
        foreach ($files as $file) {
451
 
452
            $file_info = pathinfo($file);
453
            if ($options['recurse_dir'] == false
454
                && $file_info['dirname'] != $dir) {
455
                $skipped[] = $file;
456
                continue;
457
            }
458
            if (in_array($file_info['dirname'], $ignore_dirs)) {
459
                $ignored[] = $file;
460
 
461
            } elseif (in_array($file, $ignore_files)) {
462
                $ignored[] = $file;
463
 
464
            } else {
465
                if (isset($file_info['extension'])
466
                    && in_array(strtolower($file_info['extension']),
467
                                $options['file_ext'])) {
468
                    continue;
469
                }
470
                $ignored[] = $file;
471
            }
472
        }
473
 
474
        $files = PHP_CompatInfo_Parser::_arrayDiff($files,
475
                                                   array_merge($ignored, $skipped));
476
        $this->directories
477
               = PHP_CompatInfo_Parser::_arrayDiff($directories, $ignore_dirs);
478
        $this->ignored_files
479
               = $ignored;
480
 
481
        return $files;
482
    }
483
 
484
    /**
485
     * Returns list of files ignored
486
     *
487
     * Returns list of files ignored while parsing directories
488
     *
489
     * @access public
490
     * @return array or false on error
491
     * @since  version 1.8.0b2 (2008-06-03)
492
     */
493
    function getIgnoredFiles()
494
    {
495
        return $this->ignored_files;
496
    }
497
 
498
    /**
499
     * Returns the latest parse data source ignored functions
500
     *
501
     * Returns the latest parse data source ignored functions list
502
     *
503
     * @param mixed $file (optional) A specific filename or not (false)
504
     *
505
     * @access public
506
     * @return mixed Null on error or if there were no previous data parsing
507
     * @since  version 1.9.0b2 (2008-12-19)
508
     */
509
    function getIgnoredFunctions($file = false)
510
    {
511
        if (!is_array($this->latest_parse)) {
512
            // no code analysis found
513
            $functions = null;
514
        } elseif ($file === false) {
515
            $functions = $this->latest_parse['ignored_functions'];
516
        } elseif (isset($this->latest_parse[$file])) {
517
            $functions = $this->latest_parse[$file]['ignored_functions'];
518
        } else {
519
            $functions = null;
520
        }
521
 
522
        return $functions;
523
    }
524
 
525
    /**
526
     * Returns the latest parse data source ignored extensions
527
     *
528
     * Returns the latest parse data source ignored extensions list
529
     *
530
     * @param mixed $file (optional) A specific filename or not (false)
531
     *
532
     * @access public
533
     * @return mixed Null on error or if there were no previous data parsing
534
     * @since  version 1.9.0b2 (2008-12-19)
535
     */
536
    function getIgnoredExtensions($file = false)
537
    {
538
        if (!is_array($this->latest_parse)) {
539
            // no code analysis found
540
            $extensions = null;
541
        } elseif ($file === false) {
542
            $extensions = $this->latest_parse['ignored_extensions'];
543
        } elseif (isset($this->latest_parse[$file])) {
544
            $extensions = $this->latest_parse[$file]['ignored_extensions'];
545
        } else {
546
            $extensions = null;
547
        }
548
 
549
        return $extensions;
550
    }
551
 
552
    /**
553
     * Returns the latest parse data source ignored constants
554
     *
555
     * Returns the latest parse data source ignored constants list
556
     *
557
     * @param mixed $file (optional) A specific filename or not (false)
558
     *
559
     * @access public
560
     * @return mixed Null on error or if there were no previous data parsing
561
     * @since  version 1.9.0b2 (2008-12-19)
562
     */
563
    function getIgnoredConstants($file = false)
564
    {
565
        if (!is_array($this->latest_parse)) {
566
            // no code analysis found
567
            $constants = null;
568
        } elseif ($file === false) {
569
            $constants = $this->latest_parse['ignored_constants'];
570
        } elseif (isset($this->latest_parse[$file])) {
571
            $constants = $this->latest_parse[$file]['ignored_constants'];
572
        } else {
573
            $constants = null;
574
        }
575
 
576
        return $constants;
577
    }
578
 
579
    /**
580
     * Returns the latest parse data source version
581
     *
582
     * Returns the latest parse data source version, minimum and/or maximum
583
     *
584
     * @param mixed $file (optional) A specific filename or not (false)
585
     * @param bool  $max  (optional) Level with or without contextual data
586
     *
587
     * @access public
588
     * @return mixed Null on error or if there were no previous data parsing
589
     * @since  version 1.9.0b1 (2008-11-30)
590
     */
591
    function getVersion($file = false, $max = false)
592
    {
593
        $key = ($max === true) ? 'max_version' : 'version';
594
 
595
        if (!is_array($this->latest_parse)) {
596
            // no code analysis found
597
            $version = null;
598
        } elseif ($file === false) {
599
            $version = $this->latest_parse[$key];
600
        } elseif (isset($this->latest_parse[$file])) {
601
            $version = $this->latest_parse[$file][$key];
602
        } else {
603
            $version = null;
604
        }
605
 
606
        return $version;
607
    }
608
 
609
    /**
610
     * Returns the latest parse data source classes declared
611
     *
612
     * Returns the latest parse data source classes declared (internal or
613
     * end-user defined)
614
     *
615
     * @param mixed $file (optional) A specific filename or not (false)
616
     *
617
     * @access public
618
     * @return mixed Null on error or if there were no previous data parsing
619
     * @since  version 1.9.0b1 (2008-11-30)
620
     */
621
    function getClasses($file = false)
622
    {
623
        if (!is_array($this->latest_parse)) {
624
            // no code analysis found
625
            $classes = null;
626
        } elseif ($file === false) {
627
            $classes = $this->latest_parse['classes'];
628
        } elseif (isset($this->latest_parse[$file])) {
629
            $classes = $this->latest_parse[$file]['classes'];
630
        } else {
631
            $classes = null;
632
        }
633
 
634
        return $classes;
635
    }
636
 
637
    /**
638
     * Returns the latest parse data source functions declared
639
     *
640
     * Returns the latest parse data source functions declared (internal or
641
     * end-user defined)
642
     *
643
     * @param mixed $file (optional) A specific filename or not (false)
644
     *
645
     * @access public
646
     * @return mixed Null on error or if there were no previous data parsing
647
     * @since  version 1.9.0b1 (2008-11-30)
648
     */
649
    function getFunctions($file = false)
650
    {
651
        if (!is_array($this->latest_parse)) {
652
            // no code analysis found
653
            $functions = null;
654
        } elseif ($file === false) {
655
            $functions = $this->latest_parse['functions'];
656
        } elseif (isset($this->latest_parse[$file])) {
657
            $functions = $this->latest_parse[$file]['functions'];
658
        } else {
659
            $functions = null;
660
        }
661
 
662
        return $functions;
663
    }
664
 
665
    /**
666
     * Returns the latest parse data source extensions used
667
     *
668
     * Returns the latest parse data source extensions used
669
     *
670
     * @param mixed $file (optional) A specific filename or not (false)
671
     *
672
     * @access public
673
     * @return mixed Null on error or if there were no previous data parsing
674
     * @since  version 1.9.0b1 (2008-11-30)
675
     */
676
    function getExtensions($file = false)
677
    {
678
        if (!is_array($this->latest_parse)) {
679
            // no code analysis found
680
            $extensions = null;
681
        } elseif ($file === false) {
682
            $extensions = $this->latest_parse['extensions'];
683
        } elseif (isset($this->latest_parse[$file])) {
684
            $extensions = $this->latest_parse[$file]['extensions'];
685
        } else {
686
            $extensions = null;
687
        }
688
 
689
        return $extensions;
690
    }
691
 
692
    /**
693
     * Returns the latest parse data source constants declared
694
     *
695
     * Returns the latest parse data source constants declared (internal or
696
     * end-user defined)
697
     *
698
     * @param mixed $file (optional) A specific filename or not (false)
699
     *
700
     * @access public
701
     * @return mixed Null on error or if there were no previous data parsing
702
     * @since  version 1.9.0b1 (2008-11-30)
703
     */
704
    function getConstants($file = false)
705
    {
706
        if (!is_array($this->latest_parse)) {
707
            // no code analysis found
708
            $constants = null;
709
        } elseif ($file === false) {
710
            $constants = $this->latest_parse['constants'];
711
        } elseif (isset($this->latest_parse[$file])) {
712
            $constants = $this->latest_parse[$file]['constants'];
713
        } else {
714
            $constants = null;
715
        }
716
 
717
        return $constants;
718
    }
719
 
720
    /**
721
     * Returns the latest parse data source tokens declared
722
     *
723
     * Returns the latest parse data source PHP5+ tokens declared
724
     *
725
     * @param mixed $file (optional) A specific filename or not (false)
726
     *
727
     * @access public
728
     * @return mixed Null on error or if there were no previous data parsing
729
     * @since  version 1.9.0b1 (2008-11-30)
730
     */
731
    function getTokens($file = false)
732
    {
733
        if (!is_array($this->latest_parse)) {
734
            // no code analysis found
735
        } elseif ($file === false) {
736
            $tokens = $this->latest_parse['tokens'];
737
        } elseif (isset($this->latest_parse[$file])) {
738
            $tokens = $this->latest_parse[$file]['tokens'];
739
        } else {
740
            $tokens = null;
741
        }
742
 
743
        return $tokens;
744
    }
745
 
746
    /**
747
     * Returns the latest parse data source conditions
748
     *
749
     * Returns the latest parse data source conditions, with or without
750
     * contextual data
751
     *
752
     * @param mixed $file      (optional) A specific filename or not (false)
753
     * @param bool  $levelOnly (optional) Level with or without contextual data
754
     *
755
     * @access public
756
     * @return mixed Null on error or if there were no previous data parsing
757
     * @since  version 1.9.0b1 (2008-11-30)
758
     */
759
    function getConditions($file = false, $levelOnly = false)
760
    {
761
        if (!is_array($this->latest_parse)) {
762
            // no code analysis found
763
            $conditions = null;
764
        } elseif ($file === false) {
765
            $conditions = $this->latest_parse['cond_code'];
766
        } elseif (isset($this->latest_parse[$file])) {
767
            $conditions = $this->latest_parse[$file]['cond_code'];
768
        } else {
769
            $conditions = null;
770
        }
771
 
772
        if (is_array($conditions) && $levelOnly === true) {
773
            $conditions = $conditions[0];
774
        }
775
        return $conditions;
776
    }
777
 
778
    /**
779
     * Parse a data source
780
     *
781
     * Parse a data source with auto detect ability. This data source, may be
782
     * one of these follows: a directory, a file, a string (chunk of code),
783
     * an array of multiple origin.
784
     *
785
     * Each of five parsing functions support common and specifics options.
786
     *
787
     *  * Common options :
788
     *  - 'debug'                   Contains a boolean to control whether
789
     *                              extra ouput is shown.
790
     *  - 'ignore_functions'        Contains an array of functions to ignore
791
     *                              when calculating the version needed.
792
     *  - 'ignore_constants'        Contains an array of constants to ignore
793
     *                              when calculating the version needed.
794
     *  - 'ignore_extensions'       Contains an array of php extensions to ignore
795
     *                              when calculating the version needed.
796
     *  - 'ignore_versions'         Contains an array of php versions to ignore
797
     *                              when calculating the version needed.
798
     *  - 'ignore_functions_match'  Contains an array of function patterns to ignore
799
     *                              when calculating the version needed.
800
     *  - 'ignore_extensions_match' Contains an array of extension patterns to ignore
801
     *                              when calculating the version needed.
802
     *  - 'ignore_constants_match'  Contains an array of constant patterns to ignore
803
     *                              when calculating the version needed.
804
     *
805
     *  * parseArray, parseDir|parseFolder, specific options :
806
     *  - 'file_ext'                Contains an array of file extensions to parse
807
     *                              for PHP code. Default: php, php4, inc, phtml
808
     *  - 'ignore_files'            Contains an array of files to ignore.
809
     *                              File names are case insensitive.
810
     *
811
     *  * parseArray specific options :
812
     *  - 'is_string'               Contains a boolean which says if the array values
813
     *                              are strings or file names.
814
     *
815
     *  * parseDir|parseFolder specific options :
816
     *  - 'recurse_dir'             Boolean on whether to recursively find files
817
     *  - 'ignore_dirs'             Contains an array of directories to ignore.
818
     *                              Directory names are case insensitive.
819
     *
820
     * @param mixed $dataSource The data source (may be file, dir, string, or array)
821
     * @param array $options    An array of options. See above.
822
     *
823
     * @access public
824
     * @return array or false on error
825
     * @since  version 1.8.0b2 (2008-06-03)
826
     */
827
    function parseData($dataSource, $options = array())
828
    {
829
        $this->options = array_merge($this->options, $options);
830
 
831
        $dataType  = gettype($dataSource);
832
        $dataCount = 0;
833
        // - when array source with mixed content incompatible
834
        // - if all directories are not readable
835
        // - if data source invalid type: other than file, directory, string
836
 
837
        if ($dataType == 'string' || $dataType == 'array') {
838
            if (is_array($dataSource)) {
839
                //$dataType = 'array';
840
            } elseif (is_dir($dataSource)) {
841
                $dataType   = 'directory';
842
                $dataSource = array($dataSource);
843
            } elseif (is_file($dataSource)) {
844
                $dataType   = 'file';
845
                $dataSource = array($dataSource);
846
            } elseif (substr($dataSource, 0, 5) == '<?php') {
847
                //$dataType = 'string';
848
                $this->options = array_merge($this->options,
849
                                             array('is_string' => true));
850
                $dataSource    = array($dataSource);
851
            } else {
852
                //$dataType = 'string';
853
                // directory or file are misspelled
854
            }
855
            if (is_array($dataSource)) {
856
                $dataSource = $this->_validateDataSource($dataSource,
857
                                                         $this->options);
858
                $dataCount  = count($dataSource);
859
            }
860
        }
861
 
862
        $this->dataSource = array('dataSource' => $dataSource,
863
                                  'dataType' => $dataType,
864
                                  'dataCount' => $dataCount);
865
 
866
        $eventInfo = array_merge($this->dataSource,
867
                                 array('parseOptions' => $this->options));
868
 
869
        // notify all observers that parsing data source begin
870
        $this->notifyListeners(PHP_COMPATINFO_EVENT_AUDITSTARTED, $eventInfo);
871
 
872
        if ($dataCount == 0) {
873
            $parseData = false;
874
        } else {
875
            switch ($dataType) {
876
            case 'array' :
877
                $parseData = $this->_parseArray($dataSource, $this->options);
878
                break;
879
            case 'string' :
880
                $parseData = $this->_parseString($dataSource, $this->options);
881
                break;
882
            case 'file' :
883
                $parseData = $this->_parseFile($dataSource, $this->options);
884
                break;
885
            case 'directory' :
886
                $parseData = $this->_parseDir($dataSource, $this->options);
887
                break;
888
            }
889
        }
890
 
891
        // notify all observers that parsing data source is over
892
        $this->notifyListeners(PHP_COMPATINFO_EVENT_AUDITFINISHED, $parseData);
893
 
894
        $this->latest_parse = $parseData;
895
        return $parseData;
896
    }
897
 
898
    /**
899
     * Validate content of data source
900
     *
901
     * Validate content of data source list, before parsing each source
902
     *
903
     * @param mixed $dataSource The data source (may be file, dir, or string)
904
     * @param array $options    Parser options (see parseData() method for details)
905
     *
906
     * @access private
907
     * @return array   empty array on error
908
     * @since  version 1.8.0b3 (2008-06-07)
909
     */
910
    function _validateDataSource($dataSource, $options = array())
911
    {
912
        /**
913
         * Array by default expect to contains list of files and/or directories.
914
         * If you want a list of chunk of code (strings), 'is_string' option
915
         * must be set to true.
916
         */
917
        $list = array();
918
 
919
        foreach ($dataSource as $source) {
920
            if ($options['is_string'] === true) {
921
                if (is_string($source)) {
922
                    $list[] = $source;
923
                } else {
924
                    /**
925
                     * One of items is not a string (chunk of code). All
926
                     * data sources parsing are stopped and considered as invalid.
927
                     */
928
                    $list = array();
929
                    break;
930
                }
931
            } else {
932
                if (is_dir($source) && is_readable($source)) {
933
                    $files = $this->getFilelist($source, $options);
934
                    $list  = array_merge($list, $files);
935
                } elseif (is_file($source)) {
936
                    $list[] = $source;
937
                } else {
938
                    /**
939
                     * One of items is not a valid file or directory. All
940
                     * data sources parsing are stopped and considered as invalid.
941
                     */
942
                    $list = array();
943
                    break;
944
                }
945
            }
946
        }
947
 
948
        return $list;
949
    }
950
 
951
    /**
952
     * Parse an Array of Files
953
     *
954
     * You can parse an array of Files or Strings, to parse
955
     * strings, $options['is_string'] must be set to true
956
     *
957
     * @param array $dataSource Array of file &| directory names or code strings
958
     * @param array $options    Parser options (see parseData() method for details)
959
     *
960
     * @access private
961
     * @return array or false on error
962
     * @since  version 0.7.0 (2004-03-09)
963
     * @see    parseData()
964
     */
965
    function _parseArray($dataSource, $options = array())
966
    {
967
        // Each data source have been checked before (see _validateDataSource() )
968
        if (is_file($dataSource[0])) {
969
            $parseData = $this->_parseDir($dataSource, $options);
970
        } else {
971
            $parseData = $this->_parseString($dataSource, $options);
972
        }
973
 
974
        return $parseData;
975
    }
976
 
977
    /**
978
     * Parse a string
979
     *
980
     * Parse a string for its compatibility info.
981
     *
982
     * @param array $strings PHP Code to parse
983
     * @param array $options Parser options (see parseData() method for details)
984
     *
985
     * @access private
986
     * @return array or false on error
987
     * @since  version 0.7.0 (2004-03-09)
988
     * @see    parseData()
989
     */
990
    function _parseString($strings, $options = array())
991
    {
992
        $results = $this->_parseElements($strings, $options);
993
        return $results;
994
    }
995
 
996
    /**
997
     * Parse a single file
998
     *
999
     * Parse a single file for its compatibility info.
1000
     *
1001
     * @param string $file    File to parse
1002
     * @param array  $options Parser options (see parseData() method for details)
1003
     *
1004
     * @access private
1005
     * @return array or false on error
1006
     * @since  version 0.7.0 (2004-03-09)
1007
     * @see    parseData()
1008
     */
1009
    function _parseFile($file, $options = array())
1010
    {
1011
        $results = $this->_parseElements($file, $options);
1012
        return $results;
1013
    }
1014
 
1015
    /**
1016
     * Parse a directory
1017
     *
1018
     * Parse a directory recursively for its compatibility info
1019
     *
1020
     * @param array $files   Files list of folder to parse
1021
     * @param array $options Parser options (see parseData() method for details)
1022
     *
1023
     * @access private
1024
     * @return array or false on error
1025
     * @since  version 0.8.0 (2004-04-22)
1026
     * @see    parseData()
1027
     */
1028
    function _parseDir($files, $options = array())
1029
    {
1030
        $results = $this->_parseElements($files, $options);
1031
        return $results;
1032
    }
1033
 
1034
    /**
1035
     * Parse a list of elements
1036
     *
1037
     * Parse a list of directory|file elements, or chunk of code (strings)
1038
     *
1039
     * @param array $elements Array of file &| directory names or code strings
1040
     * @param array $options  Parser options (see parseData() method for details)
1041
     *
1042
     * @access private
1043
     * @return array
1044
     * @since  version 1.8.0b3 (2008-06-07)
1045
     * @see    _parseString(), _parseDir()
1046
     */
1047
    function _parseElements($elements, $options = array())
1048
    {
1049
        $files_parsed       = array();
1050
        $latest_version     = $this->latest_version;
1051
        $earliest_version   = $this->earliest_version;
1052
        $all_functions      = array();
1053
        $classes            = array();
1054
        $functions          = array();
1055
        $extensions         = array();
1056
        $constants          = array();
1057
        $tokens             = array();
1058
        $ignored_functions  = array();
1059
        $ignored_extensions = array();
1060
        $ignored_constants  = array();
1061
        $function_exists    = array();
1062
        $extension_loaded   = array();
1063
        $defined            = array();
1064
        $cond_code          = 0;
1065
 
1066
        foreach ($elements as $p => $element) {
1067
            $index = $p + 1;
1068
            if (is_file($element)) {
1069
                if (in_array($element, $options['ignore_files'])) {
1070
                    $this->ignored_files[] = $element;
1071
                    continue;
1072
                }
1073
                $eventInfo
1074
                    = array('filename' => $element, 'fileindex' => $index);
1075
                $this->notifyListeners(PHP_COMPATINFO_EVENT_FILESTARTED, $eventInfo);
1076
 
1077
                $tokens_list          = $this->_tokenize($element);
1078
                $kfile                = $element;
1079
                $files_parsed[$kfile] = $this->_parseTokens($tokens_list, $options);
1080
 
1081
                $this->notifyListeners(PHP_COMPATINFO_EVENT_FILEFINISHED);
1082
            } else {
1083
                $eventInfo
1084
                    = array('stringdata' => $element, 'stringindex' => $index);
1085
                $this->notifyListeners(PHP_COMPATINFO_EVENT_CODESTARTED, $eventInfo);
1086
 
1087
                $tokens_list          = $this->_tokenize($element, true);
1088
                $kfile                = 'string_' . $index;
1089
                $files_parsed[$kfile] = $this->_parseTokens($tokens_list, $options);
1090
 
1091
                $this->notifyListeners(PHP_COMPATINFO_EVENT_CODEFINISHED);
1092
            }
1093
        }
1094
 
1095
        foreach ($files_parsed as $fn => $file) {
1096
            $cmp = version_compare($latest_version, $file['version']);
1097
            if ($cmp === -1) {
1098
                $latest_version = $file['version'];
1099
            }
1100
            if ($file['max_version'] != '') {
1101
                $cmp = version_compare($earliest_version, $file['max_version']);
1102
                if ($earliest_version == '' || $cmp === 1) {
1103
                    $earliest_version = $file['max_version'];
1104
                }
1105
            }
1106
            foreach ($file['classes'] as $class) {
1107
                if (!in_array($class, $classes)) {
1108
                    $classes[] = $class;
1109
                }
1110
            }
1111
            foreach ($file['functions'] as $func) {
1112
                if (!in_array($func, $functions)) {
1113
                    $functions[] = $func;
1114
                }
1115
            }
1116
            foreach ($file['extensions'] as $ext) {
1117
                if (!in_array($ext, $extensions)) {
1118
                    $extensions[] = $ext;
1119
                }
1120
            }
1121
            foreach ($file['constants'] as $const) {
1122
                if (!in_array($const, $constants)) {
1123
                    $constants[] = $const;
1124
                }
1125
            }
1126
            foreach ($file['tokens'] as $token) {
1127
                if (!in_array($token, $tokens)) {
1128
                    $tokens[] = $token;
1129
                }
1130
            }
1131
            foreach ($file['ignored_functions'] as $if) {
1132
                if (!in_array($if, $ignored_functions)) {
1133
                    $ignored_functions[] = $if;
1134
                }
1135
            }
1136
            foreach ($file['ignored_extensions'] as $ie) {
1137
                if (!in_array($ie, $ignored_extensions)) {
1138
                    $ignored_extensions[] = $ie;
1139
                }
1140
            }
1141
            foreach ($file['ignored_constants'] as $ic) {
1142
                if (!in_array($ic, $ignored_constants)) {
1143
                    $ignored_constants[] = $ic;
1144
                }
1145
            }
1146
            foreach ($file['cond_code'][1][0] as $ccf) {
1147
                if (!in_array($ccf, $function_exists)) {
1148
                    $function_exists[] = $ccf;
1149
                }
1150
            }
1151
            foreach ($file['cond_code'][1][1] as $cce) {
1152
                if (!in_array($cce, $extension_loaded)) {
1153
                    $extension_loaded[] = $cce;
1154
                }
1155
            }
1156
            foreach ($file['cond_code'][1][2] as $ccc) {
1157
                if (!in_array($ccc, $defined)) {
1158
                    $defined[] = $ccc;
1159
                }
1160
            }
1161
            if ($options['debug'] === false) {
1162
                unset($files_parsed[$fn]['cond_code'][1]);
1163
            } else {
1164
                unset($file['ignored_functions']);
1165
                unset($file['ignored_extensions']);
1166
                unset($file['ignored_constants']);
1167
                unset($file['max_version']);
1168
                unset($file['version']);
1169
                unset($file['classes']);
1170
                unset($file['functions']);
1171
                unset($file['extensions']);
1172
                unset($file['constants']);
1173
                unset($file['tokens']);
1174
                unset($file['cond_code']);
1175
 
1176
                foreach ($file as $version => $file_functions) {
1177
                    // extra information available only when debug mode is on
1178
                    if (isset($all_functions[$version])) {
1179
                        foreach ($file_functions as $func) {
1180
                            $k = array_search($func, $all_functions[$version]);
1181
                            if ($k === false) {
1182
                                $all_functions[$version][] = $func;
1183
                            }
1184
                        }
1185
                    } else {
1186
                        $all_functions[$version] = $file_functions;
1187
                    }
1188
                }
1189
            }
1190
        }
1191
 
1192
        if (count($files_parsed) == 0) {
1193
            return false;
1194
        }
1195
 
1196
        if (count($function_exists) > 0) {
1197
            $cond_code += 1;
1198
        }
1199
        if (count($extension_loaded) > 0) {
1200
            $cond_code += 2;
1201
        }
1202
        if (count($defined) > 0) {
1203
            $cond_code += 4;
1204
        }
1205
        if ($options['debug'] === false) {
1206
            $cond_code = array($cond_code);
1207
        } else {
1208
            sort($function_exists);
1209
            sort($extension_loaded);
1210
            sort($defined);
1211
            $cond_code = array($cond_code, array($function_exists,
1212
                                                 $extension_loaded,
1213
                                                 $defined));
1214
        }
1215
 
1216
        sort($ignored_functions);
1217
        sort($ignored_extensions);
1218
        sort($ignored_constants);
1219
        sort($classes);
1220
        sort($functions);
1221
        natcasesort($extensions);
1222
        sort($constants);
1223
        sort($tokens);
1224
        $main_info = array('ignored_files'      => $this->getIgnoredFiles(),
1225
                           'ignored_functions'  => $ignored_functions,
1226
                           'ignored_extensions' => $ignored_extensions,
1227
                           'ignored_constants'  => $ignored_constants,
1228
                           'max_version'   => $earliest_version,
1229
                           'version'       => $latest_version,
1230
                           'classes'       => $classes,
1231
                           'functions'     => $functions,
1232
                           'extensions'    => array_values($extensions),
1233
                           'constants'     => $constants,
1234
                           'tokens'        => $tokens,
1235
                           'cond_code'     => $cond_code);
1236
 
1237
        if (count($files_parsed) == 1) {
1238
            if ($options['debug'] === false) {
1239
                $parseData = $main_info;
1240
            } else {
1241
                $main_info = array('ignored_files' => $this->getIgnoredFiles());
1242
                $parseData = array_merge($main_info,
1243
                                         $files_parsed[$kfile], $all_functions);
1244
            }
1245
        } else {
1246
            if ($options['debug'] === false) {
1247
                $parseData = array_merge($main_info, $files_parsed);
1248
            } else {
1249
                $parseData = array_merge($main_info, $all_functions, $files_parsed);
1250
            }
1251
        }
1252
 
1253
        $this->notifyListeners(PHP_COMPATINFO_EVENT_FILEFINISHED, $parseData);
1254
        return $parseData;
1255
    }
1256
 
1257
    /**
1258
     * Token a file or string
1259
     *
1260
     * @param string  $input     Filename or PHP code
1261
     * @param boolean $is_string Whether or note the input is a string
1262
     * @param boolean $debug     add token names for human read
1263
     *
1264
     * @access private
1265
     * @return array
1266
     * @since  version 0.7.0 (2004-03-09)
1267
     */
1268
    function _tokenize($input, $is_string = false, $debug = false)
1269
    {
1270
        if ($is_string === false) {
1271
            $input = file_get_contents($input, true);
1272
        }
1273
        $tokens = token_get_all($input);
1274
 
1275
        if ($debug === true) {
1276
            $r = array();
1277
            foreach ($tokens as $token) {
1278
                if (is_array($token)) {
1279
                    $token[] = token_name($token[0]);
1280
                } else {
1281
                    $token = $token[0];
1282
                }
1283
                $r[] = $token;
1284
            }
1285
        } else {
1286
            $r = $tokens;
1287
        }
1288
        return $r;
1289
    }
1290
 
1291
    /**
1292
     * Parse the given Tokens
1293
     *
1294
     * The tokens are those returned by token_get_all() which is nicely
1295
     * wrapped in PHP_CompatInfo::_tokenize
1296
     *
1297
     * @param array   $tokens  Array of PHP Tokens
1298
     * @param boolean $options Show Extra Output
1299
     *
1300
     * @access private
1301
     * @return array
1302
     * @since  version 0.7.0 (2004-03-09)
1303
     */
1304
    function _parseTokens($tokens, $options)
1305
    {
1306
        static $akeys;
1307
 
1308
        $classes            = array();
1309
        $functions          = array();
1310
        $functions_version  = array();
1311
        $latest_version     = $this->latest_version;
1312
        $earliest_version   = $this->earliest_version;
1313
        $extensions         = array();
1314
        $constants          = array();
1315
        $constant_names     = array();
1316
        $token_names        = array();
1317
        $udf                = array();
1318
        $ignore_functions   = array();
1319
        $ignored_functions  = array();
1320
        $ignore_extensions  = array();
1321
        $ignored_extensions = array();
1322
        $ignore_constants   = array();
1323
        $ignored_constants  = array();
1324
        $function_exists    = array();
1325
        $extension_loaded   = array();
1326
        $defined            = array();
1327
        $cond_code          = 0;
1328
 
1329
        if (isset($options['ignore_constants'])) {
1330
            $options['ignore_constants']
1331
                = array_map('strtoupper', $options['ignore_constants']);
1332
        } else {
1333
            $options['ignore_constants'] = array();
1334
        }
1335
        if (isset($options['ignore_extensions'])) {
1336
            $options['ignore_extensions']
1337
                = array_map('strtolower', $options['ignore_extensions']);
1338
        } else {
1339
            $options['ignore_extensions'] = array();
1340
        }
1341
        if (isset($options['ignore_versions'][0])) {
1342
            $min_ver = $options['ignore_versions'][0];
1343
        } else {
1344
            $min_ver = false;
1345
        }
1346
        if (isset($options['ignore_versions'][1])) {
1347
            $max_ver = $options['ignore_versions'][1];
1348
        } else {
1349
            $max_ver = false;
1350
        }
1351
 
1352
        if (isset($options['ignore_functions_match'])) {
1353
            list($ifm_compare, $ifm_patterns) = $options['ignore_functions_match'];
1354
        } else {
1355
            $ifm_compare = false;
1356
        }
1357
        if (isset($options['ignore_extensions_match'])) {
1358
            list($iem_compare, $iem_patterns) = $options['ignore_extensions_match'];
1359
        } else {
1360
            $iem_compare = false;
1361
        }
1362
        if (isset($options['ignore_constants_match'])) {
1363
            list($icm_compare, $icm_patterns) = $options['ignore_constants_match'];
1364
        } else {
1365
            $icm_compare = false;
1366
        }
1367
 
1368
        $token_count = sizeof($tokens);
1369
        $i           = 0;
1370
        $found_class = false;
1371
        while ($i < $token_count) {
1372
            if ($this->_isToken($tokens[$i], 'T_FUNCTION')) {
1373
                $found_func = false;
1374
            } else {
1375
                $found_func = true;
1376
            }
1377
            while ($found_func == false) {
1378
                $i += 1;
1379
                if ($this->_isToken($tokens[$i], 'T_STRING')) {
1380
                    $found_func = true;
1381
                    $func       = $tokens[$i][1];
1382
                    if ($found_class === false
1383
                        || in_array($func, $function_exists)) {
1384
                        $udf[] = $func;
1385
                    }
1386
                }
1387
            }
1388
 
1389
            // Try to detect PHP method chaining implementation
1390
            if ($this->_isToken($tokens[$i], 'T_VARIABLE')
1391
                && $this->_isToken($tokens[$i+1], 'T_OBJECT_OPERATOR')
1392
                && $this->_isToken($tokens[$i+2], 'T_STRING')
1393
                && $this->_isToken($tokens[$i+3], '(')) {
1394
 
1395
                $i                   += 3;
1396
                $php5_method_chaining = false;
1397
                while (((!is_array($tokens[$i]) && $tokens[$i] == ';') === false)
1398
                    && (!$this->_isToken($tokens[$i], 'T_CLOSE_TAG'))
1399
                    ) {
1400
                    $i += 1;
1401
                    if ((($this->_isToken($tokens[$i], ')'))
1402
                        || ($this->_isToken($tokens[$i], 'T_WHITESPACE')))
1403
                        && $this->_isToken($tokens[$i+1], 'T_OBJECT_OPERATOR')) {
1404
 
1405
                        $php5_method_chaining = true;
1406
                    }
1407
                }
1408
            }
1409
 
1410
            // Compare "ignore_functions_match" pre-condition
1411
            if (is_string($ifm_compare)) {
1412
                if (strcasecmp('preg_match', $ifm_compare) != 0) {
1413
                    // Try to catch function_exists() condition
1414
                    if ($this->_isToken($tokens[$i], 'T_STRING')
1415
                        && (strcasecmp($tokens[$i][1], $ifm_compare) == 0)) {
1416
 
1417
                        while ((!$this->_isToken($tokens[$i],
1418
                                                 'T_CONSTANT_ENCAPSED_STRING'))) {
1419
                            $i += 1;
1420
                        }
1421
                        $func = trim($tokens[$i][1], "'");
1422
 
1423
                        /**
1424
                         * try if function_exists()
1425
                         * match one or more pattern condition
1426
                         */
1427
                        foreach ($ifm_patterns as $pattern) {
1428
                            if (preg_match($pattern, $func) === 1) {
1429
                                $ignore_functions[] = $func;
1430
                            }
1431
                        }
1432
                    }
1433
                }
1434
            }
1435
 
1436
            // Compare "ignore_extensions_match" pre-condition
1437
            if (is_string($iem_compare)) {
1438
                if (strcasecmp('preg_match', $iem_compare) != 0) {
1439
                    // Try to catch extension_loaded() condition
1440
                    if ($this->_isToken($tokens[$i], 'T_STRING')
1441
                        && (strcasecmp($tokens[$i][1], $iem_compare) == 0)) {
1442
 
1443
                        while ((!$this->_isToken($tokens[$i],
1444
                                                 'T_CONSTANT_ENCAPSED_STRING'))) {
1445
                            $i += 1;
1446
                        }
1447
                        $ext = trim($tokens[$i][1], "'");
1448
 
1449
                        /**
1450
                         * try if extension_loaded()
1451
                         * match one or more pattern condition
1452
                         */
1453
                        foreach ($iem_patterns as $pattern) {
1454
                            if (preg_match($pattern, $ext) === 1) {
1455
                                $ignore_extensions[] = $ext;
1456
                            }
1457
                        }
1458
                    }
1459
                }
1460
            }
1461
 
1462
            // Compare "ignore_constants_match" pre-condition
1463
            if (is_string($icm_compare)) {
1464
                if (strcasecmp('preg_match', $icm_compare) != 0) {
1465
                    // Try to catch defined() condition
1466
                    if ($this->_isToken($tokens[$i], 'T_STRING')
1467
                        && (strcasecmp($tokens[$i][1], $icm_compare) == 0)) {
1468
 
1469
                        while ((!$this->_isToken($tokens[$i],
1470
                                                 'T_CONSTANT_ENCAPSED_STRING'))) {
1471
                            $i += 1;
1472
                        }
1473
                        $cst = trim($tokens[$i][1], "'");
1474
 
1475
                        /**
1476
                         * try if defined()
1477
                         * match one or more pattern condition
1478
                         */
1479
                        foreach ($icm_patterns as $pattern) {
1480
                            if (preg_match($pattern, $cst) === 1) {
1481
                                $ignore_constants[] = $cst;
1482
                            }
1483
                        }
1484
                    }
1485
                }
1486
            }
1487
 
1488
            // try to detect class instantiation
1489
            if ($this->_isToken($tokens[$i], 'T_STRING')
1490
                && (isset($tokens[$i-2]))
1491
                && $this->_isToken($tokens[$i-2], 'T_NEW')) {
1492
 
1493
                $is_class  = true;
1494
                $classes[] = $tokens[$i][1];
1495
            } else {
1496
                $is_class = false;
1497
            }
1498
 
1499
            if ($this->_isToken($tokens[$i], 'T_STRING')
1500
                && $is_class == false
1501
                && (isset($tokens[$i+1]))
1502
                && $this->_isToken($tokens[$i+1], '(')) {
1503
 
1504
                $is_function = false;
1505
 
1506
                if (isset($tokens[$i-1])
1507
                    && !$this->_isToken($tokens[$i-1], 'T_DOUBLE_COLON')
1508
                    && !$this->_isToken($tokens[$i-1], 'T_OBJECT_OPERATOR')) {
1509
 
1510
                    if (isset($tokens[$i-2])
1511
                        && $this->_isToken($tokens[$i-2], 'T_FUNCTION')) {
1512
                        // its a function declaration
1513
                    } else {
1514
                        $is_function = true;
1515
                    }
1516
                }
1517
                if ($is_function == true || !is_array($tokens[$i-1])) {
1518
                    $functions[] = strtolower($tokens[$i][1]);
1519
                }
1520
            }
1521
 
1522
            // try to detect condition function_exists()
1523
            if ($this->_isToken($tokens[$i], 'T_STRING')
1524
                && (strcasecmp($tokens[$i][1], 'function_exists') == 0)) {
1525
 
1526
                $j = $i;
1527
                while ((!$this->_isToken($tokens[$j], ')'))) {
1528
                    if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) {
1529
                        $t_string          = $tokens[$j][1];
1530
                        $t_string          = trim($t_string, "'");
1531
                        $t_string          = trim($t_string, '"');
1532
                        $function_exists[] = $t_string;
1533
                    }
1534
                    $j++;
1535
                }
1536
            }
1537
            // try to detect condition extension_loaded()
1538
            if ($this->_isToken($tokens[$i], 'T_STRING')
1539
                && (strcasecmp($tokens[$i][1], 'extension_loaded') == 0)) {
1540
 
1541
                $j = $i;
1542
                while ((!$this->_isToken($tokens[$j], ')'))) {
1543
                    if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) {
1544
                        $t_string           = $tokens[$j][1];
1545
                        $t_string           = trim($t_string, "'");
1546
                        $t_string           = trim($t_string, '"');
1547
                        $extension_loaded[] = $t_string;
1548
                    }
1549
                    $j++;
1550
                }
1551
            }
1552
            // try to detect condition defined()
1553
            if ($this->_isToken($tokens[$i], 'T_STRING')
1554
                && (strcasecmp($tokens[$i][1], 'defined') == 0)) {
1555
 
1556
                $j = $i;
1557
                while ((!$this->_isToken($tokens[$j], ')'))) {
1558
                    if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) {
1559
                        $t_string  = $tokens[$j][1];
1560
                        $t_string  = trim($t_string, "'");
1561
                        $t_string  = trim($t_string, '"');
1562
                        $defined[] = $t_string;
1563
                    }
1564
                    $j++;
1565
                }
1566
            }
1567
 
1568
            // try to detect beginning of a class
1569
            if ($this->_isToken($tokens[$i], 'T_CLASS')) {
1570
                $found_class = true;
1571
            }
1572
 
1573
            if (is_array($tokens[$i])) {
1574
                if (!isset($akeys)) {
1575
                    // build contents one time only (static variable)
1576
                    $akeys = array_keys($GLOBALS['_PHP_COMPATINFO_CONST']);
1577
                }
1578
                $const = strtoupper($tokens[$i][1]);
1579
                $found = array_search($const, $akeys);
1580
                if ($found !== false) {
1581
                    if ($this->_isToken($tokens[$i], 'T_ENCAPSED_AND_WHITESPACE')) {
1582
                        // PHP 5 constant tokens found into a string
1583
                    } else {
1584
                        // Compare "ignore_constants_match" free condition
1585
                        $icm_preg_match = false;
1586
                        if (is_string($icm_compare)) {
1587
                            if (strcasecmp('preg_match', $icm_compare) == 0) {
1588
                                /**
1589
                                 * try if preg_match()
1590
                                 * match one or more pattern condition
1591
                                 */
1592
                                foreach ($icm_patterns as $pattern) {
1593
                                    if (preg_match($pattern, $const) === 1) {
1594
                                        $icm_preg_match = true;
1595
                                        break;
1596
                                    }
1597
                                }
1598
                            }
1599
                        }
1600
 
1601
                        $init = $GLOBALS['_PHP_COMPATINFO_CONST'][$const]['init'];
1602
                        if (!PHP_CompatInfo_Parser::_ignore($init,
1603
                                                            $min_ver, $max_ver)) {
1604
                            $constants[] = $const;
1605
                            if (in_array($const, $ignore_constants)
1606
                                || in_array($const, $options['ignore_constants'])
1607
                                || $icm_preg_match) {
1608
                                $ignored_constants[] = $const;
1609
                            } else {
1610
                                $latest_version = $init;
1611
                            }
1612
                        }
1613
                    }
1614
                }
1615
            }
1616
            $i += 1;
1617
        }
1618
 
1619
        $classes   = array_unique($classes);
1620
        $functions = array_unique($functions);
1621
        if (isset($options['ignore_functions'])) {
1622
            $options['ignore_functions']
1623
                = array_map('strtolower', $options['ignore_functions']);
1624
        } else {
1625
            $options['ignore_functions'] = array();
1626
        }
1627
        if (count($ignore_functions) > 0) {
1628
            $ignore_functions = array_map('strtolower', $ignore_functions);
1629
            $options['ignore_functions']
1630
                = array_merge($options['ignore_functions'], $ignore_functions);
1631
            $options['ignore_functions']
1632
                = array_unique($options['ignore_functions']);
1633
        }
1634
        if (count($ignore_extensions) > 0) {
1635
            $options['ignore_extensions']
1636
                = array_merge($options['ignore_extensions'], $ignore_extensions);
1637
            $options['ignore_extensions']
1638
                = array_unique($options['ignore_extensions']);
1639
        }
1640
 
1641
        foreach ($classes as $name) {
1642
            if (!isset($GLOBALS['_PHP_COMPATINFO_CLASS'][$name])) {
1643
                continue;  // skip this unknown class
1644
            }
1645
            $class = $GLOBALS['_PHP_COMPATINFO_CLASS'][$name];
1646
            if (PHP_CompatInfo_Parser::_ignore($class['init'], $min_ver, $max_ver)) {
1647
                continue;  // skip this class version
1648
            }
1649
 
1650
            $cmp = version_compare($latest_version, $class['init']);
1651
            if ($cmp === -1) {
1652
                $latest_version = $class['init'];
1653
            }
1654
            if (array_key_exists('end', $class)) {
1655
                $cmp = version_compare($earliest_version, $class['end']);
1656
                if ($earliest_version == '' || $cmp === 1) {
1657
                    $earliest_version = $class['end'];
1658
                }
1659
            }
1660
 
1661
            if (array_key_exists('ext', $class)) {
1662
                // this class depends of an extension
1663
                $extensions[] = $class['ext'];
1664
            }
1665
        }
1666
 
1667
        foreach ($functions as $name) {
1668
            if (!isset($GLOBALS['_PHP_COMPATINFO_FUNCS'][$name])) {
1669
                continue;  // skip this unknown function
1670
            }
1671
            $func = $GLOBALS['_PHP_COMPATINFO_FUNCS'][$name];
1672
 
1673
            // retrieve if available the extension name
1674
            if ((isset($func['ext']))
1675
                && ($func['ext'] != 'standard')
1676
                && ($func['ext'] != 'zend')) {
1677
                $extension = $func['ext'];
1678
            } else {
1679
                $extension = false;
1680
            }
1681
 
1682
            // Compare "ignore_functions_match" free condition
1683
            $ifm_preg_match = false;
1684
            if (is_string($ifm_compare)) {
1685
                if (strcasecmp('preg_match', $ifm_compare) == 0) {
1686
                    /**
1687
                     * try if preg_match()
1688
                     * match one or more pattern condition
1689
                     */
1690
                    foreach ($ifm_patterns as $pattern) {
1691
                        if (preg_match($pattern, $name) === 1) {
1692
                            $ifm_preg_match = true;
1693
                            break;
1694
                        }
1695
                    }
1696
                }
1697
            }
1698
 
1699
            if ((!in_array($name, $udf))
1700
                && (!in_array($name, $options['ignore_functions']))
1701
                && ($ifm_preg_match === false)) {
1702
 
1703
                if ($extension && !in_array($extension, $extensions)) {
1704
                    $extensions[] = $extension;
1705
                }
1706
 
1707
                // Compare "ignore_extensions_match" free condition
1708
                $iem_preg_match = false;
1709
                if (is_string($iem_compare)) {
1710
                    if (strcasecmp('preg_match', $iem_compare) == 0) {
1711
                        /**
1712
                         * try if preg_match()
1713
                         * match one or more pattern condition
1714
                         */
1715
                        foreach ($iem_patterns as $pattern) {
1716
                            if (preg_match($pattern, $extension) === 1) {
1717
                                $iem_preg_match = true;
1718
                                break;
1719
                            }
1720
                        }
1721
                    }
1722
                }
1723
 
1724
                if ($extension
1725
                    && (in_array($extension, $options['ignore_extensions'])
1726
                        || $iem_preg_match)) {
1727
                    if (!in_array($extension, $ignored_extensions)) {
1728
                        // extension is ignored (only once)
1729
                        $ignored_extensions[] = $extension;
1730
                    }
1731
                    // all extension functions are also ignored
1732
                    $ignored_functions[] = $name;
1733
                    continue;  // skip this extension function
1734
                }
1735
 
1736
                if (PHP_CompatInfo_Parser::_ignore($func['init'],
1737
                                                   $min_ver, $max_ver)) {
1738
                    continue;  // skip this function version
1739
                }
1740
 
1741
                if ($options['debug'] == true) {
1742
                    $functions_version[$func['init']][] = array(
1743
                        'function' => $name,
1744
                        'extension' => $extension,
1745
                        'pecl' => $func['pecl']
1746
                        );
1747
                }
1748
                if ($extension === false
1749
                    || (isset($func['pecl']) && $func['pecl'] === false) ) {
1750
                    $cmp = version_compare($latest_version, $func['init']);
1751
                    if ($cmp === -1) {
1752
                        $latest_version = $func['init'];
1753
                    }
1754
                    if (array_key_exists('end', $func)) {
1755
                        $cmp = version_compare($earliest_version, $func['end']);
1756
                        if ($earliest_version == '' || $cmp === 1) {
1757
                            $earliest_version = $func['end'];
1758
                        }
1759
                    }
1760
                }
1761
 
1762
            } else {
1763
                // function is ignored
1764
                $ignored_functions[] = $name;
1765
            }
1766
        }
1767
 
1768
        $ignored_constants = array_unique($ignored_constants);
1769
        $constants         = array_unique($constants);
1770
        foreach ($constants as $constant) {
1771
            $const = $GLOBALS['_PHP_COMPATINFO_CONST'][$constant];
1772
            if (PHP_CompatInfo_Parser::_ignore($const['init'], $min_ver, $max_ver)) {
1773
                continue;  // skip this constant version
1774
            }
1775
            if (!in_array($constant, $ignored_constants)) {
1776
                $cmp = version_compare($latest_version, $const['init']);
1777
                if ($cmp === -1) {
1778
                    $latest_version = $const['init'];
1779
                }
1780
                if (array_key_exists('end', $const)) {
1781
                    $cmp = version_compare($earliest_version, $const['end']);
1782
                    if ($earliest_version == '' || $cmp === 1) {
1783
                        $earliest_version = $const['end'];
1784
                    }
1785
                }
1786
            }
1787
            if (!in_array($const['name'], $constant_names)) {
1788
                // split PHP5 tokens and pure PHP constants
1789
                if ($const['name'] == strtolower($const['name'])) {
1790
                    $token_names[] = $const['name'];
1791
                } else {
1792
                    $constant_names[] = $const['name'];
1793
                }
1794
            }
1795
        }
1796
 
1797
        if (isset($php5_method_chaining)
1798
            && $php5_method_chaining === true
1799
            && version_compare($latest_version, '5.0.0') < 0) {
1800
            // when PHP Method chaining is detected, only available for PHP 5
1801
            $latest_version = '5.0.0';
1802
        }
1803
 
1804
        ksort($functions_version);
1805
 
1806
        if (count($function_exists) > 0) {
1807
            $function_exists = array_unique($function_exists);
1808
            $cond_code      += 1;
1809
        }
1810
        if (count($extension_loaded) > 0) {
1811
            $extension_loaded = array_unique($extension_loaded);
1812
            $cond_code       += 2;
1813
        }
1814
        if (count($defined) > 0) {
1815
            $defined    = array_unique($defined);
1816
            $cond_code += 4;
1817
        }
1818
        $cond_code = array($cond_code, array($function_exists,
1819
                                             $extension_loaded,
1820
                                             $defined));
1821
 
1822
        sort($ignored_functions);
1823
        sort($ignored_extensions);
1824
        sort($ignored_constants);
1825
        sort($classes);
1826
        sort($functions);
1827
        natcasesort($extensions);
1828
        sort($constant_names);
1829
        sort($token_names);
1830
        $main_info = array('ignored_functions'  => $ignored_functions,
1831
                           'ignored_extensions' => $ignored_extensions,
1832
                           'ignored_constants'  => $ignored_constants,
1833
                           'max_version' => $earliest_version,
1834
                           'version'     => $latest_version,
1835
                           'classes'     => $classes,
1836
                           'functions'   => $functions,
1837
                           'extensions'  => array_values($extensions),
1838
                           'constants'   => $constant_names,
1839
                           'tokens'      => $token_names,
1840
                           'cond_code'   => $cond_code);
1841
 
1842
        $functions_version = array_merge($main_info, $functions_version);
1843
        return $functions_version;
1844
    }
1845
 
1846
    /**
1847
     * Checks if function which has $init version should be keep
1848
     * or ignore (version is between $min_ver and $max_ver).
1849
     *
1850
     * @param string $init    version of current function
1851
     * @param string $min_ver minimum version of function to ignore
1852
     * @param string $max_ver maximum version of function to ignore
1853
     *
1854
     * @access private
1855
     * @return boolean True to ignore function/constant, false otherwise
1856
     * @since  version 1.4.0 (2006-09-27)
1857
     * @static
1858
     */
1859
    function _ignore($init, $min_ver, $max_ver)
1860
    {
1861
        if ($min_ver) {
1862
            $cmp = version_compare($init, $min_ver);
1863
            if ($max_ver && $cmp >= 0) {
1864
                $cmp = version_compare($init, $max_ver);
1865
                if ($cmp < 1) {
1866
                    return true;
1867
                }
1868
            } elseif ($cmp === 0) {
1869
                return true;
1870
            }
1871
        }
1872
        return false;
1873
    }
1874
 
1875
    /**
1876
     * Checks if the given token is of this symbolic name
1877
     *
1878
     * @param mixed  $token    Single PHP token to test
1879
     * @param string $symbolic Symbolic name of the given token
1880
     *
1881
     * @access private
1882
     * @return bool
1883
     * @since  version 1.7.0b4 (2008-04-03)
1884
     */
1885
    function _isToken($token, $symbolic)
1886
    {
1887
        if (is_array($token)) {
1888
            $t = token_name($token[0]);
1889
        } else {
1890
            $t = $token;
1891
        }
1892
        return ($t == $symbolic);
1893
    }
1894
 
1895
    /**
1896
     * Computes the difference of arrays
1897
     *
1898
     * Computes the difference of arrays and returns result without original keys
1899
     *
1900
     * @param array $array1 The array to compare from
1901
     * @param array $array2 The array to compare against
1902
     *
1903
     * @access private
1904
     * @static
1905
     * @link   http://www.php.net/manual/en/function.array-diff.php#82297
1906
     * @return array
1907
     * @since  version 1.8.0b2 (2008-06-03)
1908
     */
1909
    function _arrayDiff($array1, $array2)
1910
    {
1911
        // This wrapper for array_diff rekeys the array returned
1912
        $valid_array = array_diff($array1, $array2);
1913
 
1914
        // reinstantiate $array1 variable
1915
        $array1 = array();
1916
 
1917
        // loop through the validated array and move elements to $array1
1918
        // this is necessary because the array_diff function
1919
        // returns arrays that retain their original keys
1920
        foreach ($valid_array as $valid) {
1921
            $array1[] = $valid;
1922
        }
1923
        return $array1;
1924
    }
1925
}
1926
?>