1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4: */
  3. // +----------------------------------------------------------------------+
  4. // | PHP version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at the following url:           |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Davey Shafik <davey@php.net>                                |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: CompatInfo.php,v 1.15 2005/03/06 00:38:01 davey Exp $
  20.  
  21. /**
  22. * Check Compatibility of chunk of PHP code
  23. * @package PHP_CompatInfo
  24. * @category PHP
  25. */
  26.  
  27. /**
  28. * An array of function init versions and extension
  29. */
  30. require_once 'PHP/data/func_array.php';
  31.  
  32. /**
  33. * An array of constants and their init versions
  34. */
  35. require_once 'PHP/data/const_array.php';
  36.  
  37. /**
  38. * Check Compatibility of chunk of PHP code
  39. *
  40. * @package PHP_CompatInfo
  41. * @author Davey Shafik <davey@php.net>
  42. * @copyright Copyright 2003 Davey Shafik and Synaptic Media. All Rights Reserved.
  43. * @example docs/examples/checkConstants.php Example that shows minimum version with Constants
  44. * @example docs/examples/parseFile.php Example on how to parse a file
  45. * @example docs/examples/parseDir.php Example on how to parse a directory
  46. * @example docs/examples/parseArray.php Example on using using parseArray() to parse a script
  47. * @example docs/examples/parseString.php Example on how to parse a string
  48. * @example docs/examples/Cli.php Example of using PHP_CompatInfo_Cli
  49. */
  50.  
  51. class PHP_CompatInfo {
  52.  
  53.     /**
  54.      * @var string Earliest version of PHP to use
  55.      */
  56.  
  57.     var $latest_version = '4.0.0';
  58.  
  59.     /**
  60.      * @var boolean Toggle parseDir recursion
  61.      */
  62.  
  63.     var $recurse_dir = true;
  64.  
  65.     /**
  66.      * Parse a file for its Compatibility info
  67.      *
  68.      * @param string $file Path of File to parse
  69.      * @param array $options An array of options where:
  70.      *                          'debug' contains a boolean
  71.      *                              to control whether extra
  72.      *                              ouput is shown.
  73.      *                          'ignore_functions' contains an array
  74.      *                              of functions to ignore when
  75.      *                              calculating the version needed.
  76.      * @access public
  77.      * @return Array
  78.      */
  79.  
  80.     function parseFile($file, $options = array())
  81.     {
  82.         $options = array_merge(array('debug' => false),$options);
  83.         if (!($tokens = $this->_tokenize($file))) {
  84.             return false;
  85.         }
  86.         return $this->_parseTokens($tokens,$options);
  87.     }
  88.  
  89.     /**
  90.      * Parse a string for its Compatibility info
  91.      *
  92.      * @param string $string PHP Code to parses
  93.      * @param array $options An array of options where:
  94.      *                          'debug' contains a boolean
  95.      *                              to control whether extra
  96.      *                              ouput is shown.
  97.      *                          'ignore_functions' contains an array
  98.      *                              of functions to ignore when
  99.      *                              calculating the version needed.
  100.      * @access public
  101.      * @return Array
  102.      */
  103.  
  104.     function  parseString($string, $options = array())
  105.     {
  106.         $options = array_merge(array('debug' => false),$options);
  107.         if (!($tokens = $this->_tokenize($string,true))) {
  108.             return false;
  109.         }
  110.         return $this->_parseTokens($tokens,$options);
  111.     }
  112.  
  113.     /**
  114.      * Parse a directory recursively for its Compatibility info
  115.      *
  116.      * @see PHP_CompatInfo::_fileList()
  117.      * @param string $dir Path of folder to parse
  118.      * @param array $options Array of user options where:
  119.      *                              'file_ext' Contains an array of file
  120.      *                                         extensions to parse for PHP
  121.      *                                         code. Default: php, php4,
  122.      *                                         inc, phtml
  123.      *                              'recurse_dir' Boolean on whether to
  124.      *                                         recursively find files
  125.      *                              'debug' contains a boolean
  126.      *                                         to control whether extra
  127.      *                                         ouput is shown.
  128.      *                              'ignore_files' contains an array of
  129.      *                                         files to ignore. File
  130.      *                                         names are case insensitive.
  131.      *                              'ignore_dirs' contains an array of
  132.      *                                         directories to ignore.
  133.      *                                         Directory names are case
  134.      *                                         insensitive.
  135.      *                          'ignore_functions' contains an array
  136.      *                                         of functions to ignore when
  137.      *                                         calculating the version needed.
  138.      * @access public
  139.      * @return array
  140.      */
  141.  
  142.     function parseDir($dir,$options = array())
  143.     {
  144.         $files = array();
  145.         $latest_version = $this->latest_version;
  146.         $extensions = array();
  147.         $constants = array();
  148.         $ignored = array();
  149.         $default_options = array('file_ext' => array('php','php4','inc','phtml'), 'recurse_dir' => true, 'debug' => false, 'ignore_files' => array(), 'ignore_dirs' => array());
  150.         $options = array_merge($default_options,$options);
  151.  
  152.         if(is_dir($dir) && is_readable($dir)) {
  153.             if($dir{strlen($dir)-1} == '/' || $dir{strlen($dir)-1} == '\\') {
  154.                 $dir = substr($dir,0,-1);
  155.             }
  156.             array_map('strtolower', $options['file_ext']);
  157.             array_map('strtolower', $options['ignore_files']);
  158.             array_map('strtolower', $options['ignore_dirs']);
  159.             $files_raw = $this->_fileList($dir,$options);
  160.             foreach($files_raw as $file) {
  161.                 if(in_array(strtolower($file),$options['ignore_files'])) {
  162.                     $ignored[] = $file;
  163.                     continue;
  164.                 }
  165.                 $file_info = pathinfo($file);
  166.                 if (isset($file_info['extension']) && in_array(strtolower($file_info['extension']),$options['file_ext'])) {
  167.                     $tokens = $this->_tokenize($file);
  168.                     if ($tokens != false) {
  169.                         $files[$file] = $this->_parseTokens($tokens,$options);
  170.                     } else {
  171.                         return false;
  172.                     }
  173.                 }
  174.             }
  175.             foreach($files as $file) {
  176.                 $cmp = version_compare($latest_version,$file['version']);
  177.                 if ((int)$cmp === -1) {
  178.                     $latest_version = $file['version'];
  179.                 }
  180.                 foreach($file['extensions'] as $ext) {
  181.                     if(!in_array($ext,$extensions)) {
  182.                         $extensions[] = $ext;
  183.                     }
  184.                 }
  185.                 foreach ($file['constants'] as $const) {
  186.                     if(!in_array($const,$constants)) {
  187.                         $constants[] = $const;
  188.                     }
  189.                 }
  190.             }
  191.  
  192.             if (sizeof($files) < 1) {
  193.                 return false;
  194.             }
  195.  
  196.             $files['constants'] = $constants;
  197.             $files['extensions'] = $extensions;
  198.             $files['version'] = $latest_version;
  199.             $files['ignored_files'] = $ignored;
  200.  
  201.             return array_reverse($files);
  202.         } else {
  203.             return false;
  204.         }
  205.     }
  206.  
  207.     /**
  208.      * Alias of parseDir
  209.      *
  210.      * @uses PHP_CompatInfo::parseDir()
  211.      * @access public
  212.      */
  213.  
  214.     function parseFolder($folder,$options = array()) {
  215.         return $this->parseDir($folder,$options);
  216.     }
  217.  
  218.     /**
  219.      * Parse an Array of Files
  220.      *
  221.      * You can parse an array of Files or Strings, to parse
  222.      * strings, $options['is_string'] must be set to true
  223.      *
  224.      * @param array $files Array of file names or code strings
  225.      * @param array $options An array of options where:
  226.      *                          'file_ext' Contains an array of file
  227.      *                              extensions to parse for PHP
  228.      *                              code. Default: php, php4,
  229.      *                              inc, phtml
  230.      *                          'debug' contains a boolean
  231.      *                              to control whether extra
  232.      *                              ouput is shown.
  233.      *                          'is_string' contains a boolean
  234.      *                              which says if the array values
  235.      *                              are strings or file names.
  236.      *                          'ignore_files' contains an array of
  237.      *                                          files to ignore. File
  238.      *                                          names are case sensitive.
  239.      *                          'ignore_functions' contains an array
  240.      *                                         of functions to ignore when
  241.      *                                         calculating the version needed.
  242.      * @access public
  243.      * @return array
  244.      */
  245.  
  246.     function parseArray($files,$options = array()) {
  247.         $latest_version = $this->latest_version;
  248.         $extensions = array();
  249.         $constants = array();
  250.         $options = array_merge(array('file_ext' => array('php','php4','inc','phtml'), 'is_string' => false,'debug' => false, 'ignore_files' => array()),$options);
  251.         $options['ignore_files'] = array_map("strtolower",$options['ignore_files']);
  252.         foreach($files as $file) {
  253.             if ($options['is_string'] == false) {
  254.                 $pathinfo = pathinfo($file);
  255.                 if (!in_array(strtolower($file),$options['ignore_files']) && in_array($pathinfo['extension'],$options['file_ext'])) {
  256.                     $tokens = $this->_tokenize($file,$options['is_string']);
  257.                     if ($tokens != false) {
  258.                         $files_parsed[$file] = $this->_parseTokens($tokens,$options);
  259.                     } else {
  260.                         $files_parsed[$file] = false;
  261.                     }
  262.                 } else {
  263.                     $ignored[] = $file;
  264.                 }
  265.             } else {
  266.                 $tokens = $this->_tokenize($file,$options['is_string']);
  267.                 if ($tokens != false) {
  268.                     $files_parsed[] = $this->_parseTokens($tokens,$options);
  269.                 } else {
  270.                     $files_parsed[] = false;
  271.                 }
  272.             }
  273.         }
  274.  
  275.         foreach($files_parsed as $file) {
  276.             if ($file != false) {
  277.                 $cmp = version_compare($latest_version,$file['version']);
  278.                 if ((int)$cmp === -1) {
  279.                     $latest_version = $file['version'];
  280.                 }
  281.                 foreach($file['extensions'] as $ext) {
  282.                     if(!in_array($ext,$extensions)) {
  283.                         $extensions[] = $ext;
  284.                     }
  285.                 }
  286.                 foreach($file['constants'] as $const) {
  287.                     if(!in_array($const,$constants)) {
  288.                         $constants[] = $const;
  289.                     }
  290.                 }
  291.             }
  292.         }
  293.  
  294.         $files_parsed['constants'] = $constants;
  295.         $files_parsed['extensions'] = $extensions;
  296.         $files_parsed['version'] = $latest_version;
  297.         $files_parsed['ignored_files']isset($ignored) ? $ignored : array();
  298.         return array_reverse($files_parsed);
  299.     }
  300.  
  301.     /**
  302.      * Parse the given Tokens
  303.      *
  304.      * The tokens are those returned by
  305.      * token_get_all() which is nicely
  306.      * wrapped in PHP_CompatInfo::_tokenize
  307.      *
  308.      * @param array $tokens Array of PHP Tokens
  309.      * @param boolean $debug Show Extra Output
  310.      * @access private
  311.      * @return array
  312.      */
  313.  
  314.     function _parseTokens($tokens, $options)
  315.     {
  316.         $functions = array();
  317.         $functions_version = array();
  318.         $latest_version = $this->latest_version;
  319.         $extensions = array();
  320.         $constants = array();
  321.         $constant_names = array();
  322.         $udf = array();
  323.  
  324.         /* Check for PHP 5 stuffs */
  325.  
  326.         $php5_tokens = @array(
  327.                         T_ABSTRACT => 'abstract',
  328.                         T_CATCH => 'catch',
  329.                         T_FINAL => 'final',
  330.                         T_INSTANCEOF => 'instanceof',
  331.                         T_PRIVATE => 'private',
  332.                         T_PROTECTED => 'protected',
  333.                         T_PUBLIC => 'public',
  334.                         T_THROW => 'throw',
  335.                         T_TRY => 'try',
  336.                         T_CLONE => 'clone',
  337.                         T_INTERFACE => 'interface',
  338.                         T_IMPLEMENTS => 'implements',
  339.                         );
  340.  
  341.         foreach ($php5_tokens as $php5_token => $value) {
  342.             if (in_array(array($php5_token, $value), $tokens)
  343.                 || in_array(array($php5_token, strtoupper($value)), $tokens)
  344.                 || in_array(array($php5_token, ucfirst($value)), $tokens)) {
  345.                 $constants[] = $php5_token;
  346.                 $latest_version = '5.0.0';
  347.                 break;
  348.             }
  349.         }
  350.  
  351.         $token_count = sizeof($tokens);
  352.         $i = 0;
  353.         while ($i < $token_count) {
  354.             $found_func = true;
  355.             if (is_array($tokens[$i]) && (token_name($tokens[$i][0]) == 'T_FUNCTION')) {
  356.                 $found_func = false;
  357.             }
  358.             while ($found_func == false) {
  359.                 $i += 1;
  360.                 if (is_array($tokens[$i]) && (token_name($tokens[$i][0]) == 'T_STRING')) {
  361.                     $found_func = true;
  362.                     $udf[] = $tokens[$i][1];
  363.                 }
  364.             }
  365.             if (is_array($tokens[$i]) && (token_name($tokens[$i][0]) == 'T_STRING')) {
  366.                 if (isset($tokens[$i + 1]) && ($tokens[$i + 1][0] == '(') &&
  367.                     (is_array($tokens[$i - 1])) &&
  368.                     (token_name($tokens[$i - 1][0]) != 'T_DOUBLE_COLON') &&
  369.                     (token_name($tokens[$i - 1][0]) != 'T_OBJECT_OPERATOR')) {
  370.                     $functions[] = $tokens[$i][1];
  371.                 }
  372.             }
  373.             if (is_array($tokens[$i])) {
  374.                 if (in_array(token_name($tokens[$i][0]),$GLOBALS['const'])) {
  375.                     $constants[] = token_name($tokens[$i][0]);
  376.                 }
  377.             } else {
  378.                 if (in_array($tokens[$i][0], $GLOBALS['const'])) {
  379.                     $constants[] = $tokens[$i][0];
  380.                 }
  381.             }
  382.             $i += 1;
  383.         }
  384.  
  385.         $functions = array_unique($functions);
  386.         if (isset($options['ignore_functions'])) {
  387.             $options['ignore_functions'] = array_map("strtolower",$options['ignore_functions']);
  388.         } else {
  389.             $options['ignore_functions'] = array();
  390.         }
  391.         foreach($functions as $name) {
  392.             if (isset($GLOBALS['funcs'][$name]) && (!in_array($name,$udf) && (!in_array($name,$options['ignore_functions'])))) {
  393.                 if ($options['debug'] == true) {
  394.                     $functions_version[$GLOBALS['funcs'][$name]['init']][] = array('function' => $name, 'extension' => $GLOBALS['funcs'][$name]['ext']);
  395.                 }
  396.                 $cmp = version_compare($latest_version,$GLOBALS['funcs'][$name]['init']);
  397.                 if ((int)$cmp === -1) {
  398.                     $latest_version = $GLOBALS['funcs'][$name]['init'];
  399.                 }
  400.                 if ((!empty($GLOBALS['funcs'][$name]['ext'])) && ($GLOBALS['funcs'][$name]['ext'] != 'ext_standard') && ($GLOBALS['funcs'][$name]['ext'] != 'zend'))  {
  401.                     $extension = substr($GLOBALS['funcs'][$name]['ext'],4);
  402.                     if ($extension{0} != '_') {
  403.                         if(!in_array($extension,$extensions)) {
  404.                             $extensions[] = $extension;
  405.                         }
  406.                     } else {
  407.                         $ext = substr($extension, 1);
  408.                         if(!in_array($extension,$extensions)) {
  409.                             $extensions[] = $extension;
  410.                         }
  411.                     }
  412.                 }
  413.             }
  414.         }
  415.  
  416.         $constants = array_unique($constants);
  417.         foreach($constants as $constant) {
  418.             $cmp = version_compare($latest_version,$GLOBALS['const'][$constant]['init']);
  419.             if ((int)$cmp === -1) {
  420.                 $latest_version = $GLOBALS['const'][$constant]['init'];
  421.             }
  422.             if(!in_array($GLOBALS['const'][$constant]['name'],$constant_names)) {
  423.                 $constant_names[] = $GLOBALS['const'][$constant]['name'];
  424.             }
  425.         }
  426.  
  427.         ksort($functions_version);
  428.  
  429.         $functions_version['constants'] = $constant_names;
  430.         $functions_version['extensions'] = $extensions;
  431.         $functions_version['version'] = $latest_version;
  432.         $functions_version = array_reverse($functions_version);
  433.         return $functions_version;
  434.     }
  435.  
  436.     /**
  437.      * Token a file or string
  438.      *
  439.      * @param string $input Filename or PHP code
  440.      * @param boolean $is_string Whether or note the input is a string
  441.      * @access private
  442.      * @return array
  443.      */
  444.  
  445.     function _tokenize($input,$is_string = false)
  446.     {
  447.         if ($is_string == false) {
  448.             $input = @file_get_contents($input,1);
  449.             if (is_string($input)) {
  450.                 return token_get_all($input);
  451.             }
  452.             return false;
  453.         } else {
  454.             return token_get_all($input);
  455.         }
  456.     }
  457.  
  458.     /**
  459.      * Retrieve a listing of every file in $directory and
  460.      * all subdirectories. Taken from PEAR_PackageFileManager_File
  461.      *
  462.      * @param string $directory full path to the directory you want the list of
  463.      * @access private
  464.      * @return array list of files in a directory
  465.      */
  466.  
  467.     function _fileList($directory,$options)
  468.     {
  469.         $ret = false;
  470.         if (@is_dir($directory) && (!in_array(strtolower($directory),$options['ignore_dirs']))) {
  471.             $ret = array();
  472.             $d = @dir($directory);
  473.             while($d && $entry=$d->read()) {
  474.                 if ($entry{0} != '.') {
  475.                     if (is_file($directory . DIRECTORY_SEPARATOR . $entry)) {
  476.                         $ret[] = $directory . DIRECTORY_SEPARATOR . $entry;
  477.                     }
  478.                     if (is_dir($directory . DIRECTORY_SEPARATOR . $entry) && ($options['recurse_dir'] != false)) {
  479.                         $tmp = $this->_fileList($directory . DIRECTORY_SEPARATOR . $entry,$options);
  480.                         if (is_array($tmp)) {
  481.                             foreach($tmp as $ent) {
  482.                                 $ret[] = $ent;
  483.                             }
  484.                         }
  485.                     }
  486.                 }
  487.             }
  488.             if ($d) {
  489.                 $d->close();
  490.             }
  491.         } else {
  492.             return false;
  493.         }
  494.  
  495.         return $ret;
  496.     }
  497. }
  498.  
  499. ?>