* Laurent Laville * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the authors nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * PHP versions 4 and 5 * * @category PHP * @package PHP_CompatInfo * @author Davey Shafik * @author Laurent Laville * @license http://www.opensource.org/licenses/bsd-license.php BSD * @version CVS: $Id: Parser.php,v 1.21 2009/01/02 10:18:47 farell Exp $ * @link http://pear.php.net/package/PHP_CompatInfo * @since File available since Release 1.8.0b2 */ require_once 'Event/Dispatcher.php'; require_once 'File/Find.php'; /** * An array of class init versions and extension */ require_once 'PHP/CompatInfo/class_array.php'; /** * An array of function init versions and extension */ require_once 'PHP/CompatInfo/func_array.php'; /** * An array of constants and their init versions */ require_once 'PHP/CompatInfo/const_array.php'; /** * An abstract base class for CompatInfo renderers */ require_once 'PHP/CompatInfo/Renderer.php'; /** * Event name of parsing data source start process */ define('PHP_COMPATINFO_EVENT_AUDITSTARTED', 'auditStarted'); /** * Event name of parsing data source end process */ define('PHP_COMPATINFO_EVENT_AUDITFINISHED', 'auditFinished'); /** * Event name of parsing a file start process */ define('PHP_COMPATINFO_EVENT_FILESTARTED', 'fileStarted'); /** * Event name of parsing a file end process */ define('PHP_COMPATINFO_EVENT_FILEFINISHED', 'fileFinished'); /** * Event name of parsing a file start process */ define('PHP_COMPATINFO_EVENT_CODESTARTED', 'codeStarted'); /** * Event name of parsing a file end process */ define('PHP_COMPATINFO_EVENT_CODEFINISHED', 'codeFinished'); /** * Parser logic * * This class is the model in the MVC design pattern of API 1.8.0 (since beta 2) * * @category PHP * @package PHP_CompatInfo * @author Laurent Laville * @license http://www.opensource.org/licenses/bsd-license.php BSD * @version Release: 1.9.0 * @link http://pear.php.net/package/PHP_CompatInfo * @since Class available since Release 1.8.0b2 */ class PHP_CompatInfo_Parser { /** * Instance of concrete renderer used to show parse results * * @var object * @since 1.8.0b2 * @access protected */ var $renderer; /** * Stores the event dispatcher which handles notifications * * @var object * @since 1.8.0b2 * @access protected */ var $dispatcher; /** * Count the number of observer registered. * The Event_Dispatcher will be add on first observer registration, and * will be removed with the last observer. * * @var integer * @since 1.8.0b2 * @access private */ var $_observerCount; /** * @var string Earliest version of PHP to use * @since 0.7.0 */ var $latest_version = '4.0.0'; /** * @var string Last version of PHP to use */ var $earliest_version = ''; /** * @var array Parsing options */ var $options; /** * @var array Data Source * @since 1.8.0b2 */ var $dataSource; /** * @var array Directory list found when parsing data source * @since 1.8.0b2 * @see getDirlist() */ var $directories; /** * @var array List of files ignored when parsing data source * @since 1.8.0b2 * @see getIgnoredFiles() */ var $ignored_files = array(); /** * @var array Result of the latest data source parsing * @since 1.9.0b1 * @see parseData() */ var $latest_parse = null; /** * Class constructor (ZE1) for PHP4 * * @access public * @since version 1.8.0b2 (2008-06-03) */ function PHP_CompatInfo_Parser() { $this->__construct(); } /** * Class constructor (ZE2) for PHP5+ * * @access public * @since version 1.8.0b2 (2008-06-03) */ function __construct() { $this->options = array( 'file_ext' => array('php', 'php4', 'inc', 'phtml'), 'recurse_dir' => true, 'debug' => false, 'is_string' => false, 'ignore_files' => array(), 'ignore_dirs' => array() ); } /** * Set up driver to be used * * Set up driver to be used, dependant on specified type. * * @param string $type Name the type of driver (html, text...) * @param array $conf A hash containing any additional configuration * * @access public * @return void * @since version 1.8.0b2 (2008-06-03) */ function setOutputDriver($type, $conf = array()) { $this->renderer =& PHP_CompatInfo_Renderer::factory($this, $type, $conf); } /** * Registers a new listener * * Registers a new listener with the given criteria. * * @param mixed $callback A PHP callback * @param string $nName (optional) Expected notification name * * @access public * @return void * @since version 1.8.0b2 (2008-06-03) */ function addListener($callback, $nName = EVENT_DISPATCHER_GLOBAL) { $this->dispatcher =& Event_Dispatcher::getInstance(); // $this->dispatcher->setNotificationClass('PHP_CompatInfo_Audit'); $this->dispatcher->addObserver($callback, $nName); $this->_observerCount++; } /** * Removes a registered listener * * Removes a registered listener that correspond to the given criteria. * * @param mixed $callback A PHP callback * @param string $nName (optional) Expected notification name * * @access public * @return bool True if listener was removed, false otherwise. * @since version 1.8.0b2 (2008-06-03) */ function removeListener($callback, $nName = EVENT_DISPATCHER_GLOBAL) { $result = $this->dispatcher->removeObserver($callback, $nName); if ($result) { $this->_observerCount--; if ($this->_observerCount == 0) { unset($this->dispatcher); } } return $result; } /** * Post a new notification to all listeners registered. * * This notification occured only if a dispatcher exists. That means if * at least one listener was registered. * * @param string $event Name of the notification handler * @param array $info (optional) Additional information about the notification * * @access public * @return void * @since version 1.8.0b2 (2008-06-03) */ function notifyListeners($event, $info = array()) { if (isset($this->dispatcher)) { $this->dispatcher->post($this, $event, $info); } } /** * Load components list * * Load components list for a PHP version or subset * * @param string $min PHP minimal version * @param string|boolean $max (optional) PHP maximal version * @param boolean $include_const (optional) include constants list * in final result * @param boolean $groupby_vers (optional) give initial php version * of function or constant * * @return array An array of php function/constant names history * @access public * @static * @since version 1.2.0 (2006-08-23) */ function loadVersion($min, $max = false, $include_const = false, $groupby_vers = false) { $keys = array(); foreach ($GLOBALS['_PHP_COMPATINFO_FUNCS'] as $func => $arr) { if (isset($arr['pecl']) && $arr['pecl'] === true) { continue; } $vmin = $arr['init']; if (version_compare($vmin, $min) < 0) { continue; } if ($max) { $end = (isset($arr['end'])) ? $arr['end'] : $vmin; if (version_compare($end, $max) < 1) { if ($groupby_vers === true) { $keys[$vmin][] = $func; } else { $keys[] = $func; } } } else { if ($groupby_vers === true) { $keys[$vmin][] = $func; } else { $keys[] = $func; } } } if ($groupby_vers === true) { foreach ($keys as $vmin => $func) { sort($keys[$vmin]); } ksort($keys); } else { sort($keys); } if ($include_const === true) { $keys = array('functions' => $keys, 'constants' => array()); foreach ($GLOBALS['_PHP_COMPATINFO_CONST'] as $const => $arr) { $vmin = $arr['init']; if (version_compare($vmin, $min) < 0) { continue; } if ($max) { $end = (isset($arr['end'])) ? $arr['end'] : $vmin; if (version_compare($end, $max) < 1) { if ($groupby_vers === true) { $keys['constants'][$vmin][] = $arr['name']; } else { $keys['constants'][] = $arr['name']; } } } else { if ($groupby_vers === true) { $keys['constants'][$vmin][] = $arr['name']; } else { $keys['constants'][] = $arr['name']; } } } ksort($keys['constants']); } return $keys; } /** * Returns list of directory parsed * * Returns list of directory parsed, depending of restrictive parser options. * * @param mixed $dir The directory name * @param array $options An array of parser options. See parseData() method. * * @access public * @return array list of directories that should be parsed * @since version 1.8.0b2 (2008-06-03) */ function getDirlist($dir, $options) { if (!isset($this->directories)) { $this->getFilelist($dir, $options); } return $this->directories; } /** * Returns list of files parsed * * Returns list of files parsed, depending of restrictive parser options. * * @param mixed $dir The directory name where to look files * @param array $options An array of parser options. See parseData() method. * * @access public * @return array list of files that should be parsed * @since version 1.8.0b2 (2008-06-03) */ function getFilelist($dir, $options) { $skipped = array(); $ignored = array(); $options = array_merge($this->options, $options); $options['file_ext'] = array_map('strtolower', $options['file_ext']); if ($dir{strlen($dir)-1} == '/' || $dir{strlen($dir)-1} == '\\') { $dir = substr($dir, 0, -1); } // use system directory separator rather than forward slash by default $ff = new File_Find(); $ff->dirsep = DIRECTORY_SEPARATOR; // get directory list that should be ignored from scope $ignore_dirs = array(); if (count($options['ignore_dirs']) > 0) { foreach ($options['ignore_dirs'] as $cond) { $cond = str_replace('\\', "\\\\", $cond); $dirs = $ff->search('`'.$cond.'`', $dir, 'perl', true, 'directories'); $ignore_dirs = array_merge($ignore_dirs, $dirs); } } // get file list that should be ignored from scope $ignore_files = array(); if (count($options['ignore_files']) > 0) { foreach ($options['ignore_files'] as $cond) { $cond = str_replace('\\', "\\\\", $cond); $files = $ff->search('`'.$cond.'`', $dir, 'perl', true, 'files'); $ignore_files = array_merge($ignore_files, $files); } } list($directories, $files) = $ff->maptree($dir); foreach ($files as $file) { $file_info = pathinfo($file); if ($options['recurse_dir'] == false && $file_info['dirname'] != $dir) { $skipped[] = $file; continue; } if (in_array($file_info['dirname'], $ignore_dirs)) { $ignored[] = $file; } elseif (in_array($file, $ignore_files)) { $ignored[] = $file; } else { if (isset($file_info['extension']) && in_array(strtolower($file_info['extension']), $options['file_ext'])) { continue; } $ignored[] = $file; } } $files = PHP_CompatInfo_Parser::_arrayDiff($files, array_merge($ignored, $skipped)); $this->directories = PHP_CompatInfo_Parser::_arrayDiff($directories, $ignore_dirs); $this->ignored_files = $ignored; return $files; } /** * Returns list of files ignored * * Returns list of files ignored while parsing directories * * @access public * @return array or false on error * @since version 1.8.0b2 (2008-06-03) */ function getIgnoredFiles() { return $this->ignored_files; } /** * Returns the latest parse data source ignored functions * * Returns the latest parse data source ignored functions list * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b2 (2008-12-19) */ function getIgnoredFunctions($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $functions = null; } elseif ($file === false) { $functions = $this->latest_parse['ignored_functions']; } elseif (isset($this->latest_parse[$file])) { $functions = $this->latest_parse[$file]['ignored_functions']; } else { $functions = null; } return $functions; } /** * Returns the latest parse data source ignored extensions * * Returns the latest parse data source ignored extensions list * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b2 (2008-12-19) */ function getIgnoredExtensions($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $extensions = null; } elseif ($file === false) { $extensions = $this->latest_parse['ignored_extensions']; } elseif (isset($this->latest_parse[$file])) { $extensions = $this->latest_parse[$file]['ignored_extensions']; } else { $extensions = null; } return $extensions; } /** * Returns the latest parse data source ignored constants * * Returns the latest parse data source ignored constants list * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b2 (2008-12-19) */ function getIgnoredConstants($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $constants = null; } elseif ($file === false) { $constants = $this->latest_parse['ignored_constants']; } elseif (isset($this->latest_parse[$file])) { $constants = $this->latest_parse[$file]['ignored_constants']; } else { $constants = null; } return $constants; } /** * Returns the latest parse data source version * * Returns the latest parse data source version, minimum and/or maximum * * @param mixed $file (optional) A specific filename or not (false) * @param bool $max (optional) Level with or without contextual data * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getVersion($file = false, $max = false) { $key = ($max === true) ? 'max_version' : 'version'; if (!is_array($this->latest_parse)) { // no code analysis found $version = null; } elseif ($file === false) { $version = $this->latest_parse[$key]; } elseif (isset($this->latest_parse[$file])) { $version = $this->latest_parse[$file][$key]; } else { $version = null; } return $version; } /** * Returns the latest parse data source classes declared * * Returns the latest parse data source classes declared (internal or * end-user defined) * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getClasses($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $classes = null; } elseif ($file === false) { $classes = $this->latest_parse['classes']; } elseif (isset($this->latest_parse[$file])) { $classes = $this->latest_parse[$file]['classes']; } else { $classes = null; } return $classes; } /** * Returns the latest parse data source functions declared * * Returns the latest parse data source functions declared (internal or * end-user defined) * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getFunctions($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $functions = null; } elseif ($file === false) { $functions = $this->latest_parse['functions']; } elseif (isset($this->latest_parse[$file])) { $functions = $this->latest_parse[$file]['functions']; } else { $functions = null; } return $functions; } /** * Returns the latest parse data source extensions used * * Returns the latest parse data source extensions used * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getExtensions($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $extensions = null; } elseif ($file === false) { $extensions = $this->latest_parse['extensions']; } elseif (isset($this->latest_parse[$file])) { $extensions = $this->latest_parse[$file]['extensions']; } else { $extensions = null; } return $extensions; } /** * Returns the latest parse data source constants declared * * Returns the latest parse data source constants declared (internal or * end-user defined) * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getConstants($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found $constants = null; } elseif ($file === false) { $constants = $this->latest_parse['constants']; } elseif (isset($this->latest_parse[$file])) { $constants = $this->latest_parse[$file]['constants']; } else { $constants = null; } return $constants; } /** * Returns the latest parse data source tokens declared * * Returns the latest parse data source PHP5+ tokens declared * * @param mixed $file (optional) A specific filename or not (false) * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getTokens($file = false) { if (!is_array($this->latest_parse)) { // no code analysis found } elseif ($file === false) { $tokens = $this->latest_parse['tokens']; } elseif (isset($this->latest_parse[$file])) { $tokens = $this->latest_parse[$file]['tokens']; } else { $tokens = null; } return $tokens; } /** * Returns the latest parse data source conditions * * Returns the latest parse data source conditions, with or without * contextual data * * @param mixed $file (optional) A specific filename or not (false) * @param bool $levelOnly (optional) Level with or without contextual data * * @access public * @return mixed Null on error or if there were no previous data parsing * @since version 1.9.0b1 (2008-11-30) */ function getConditions($file = false, $levelOnly = false) { if (!is_array($this->latest_parse)) { // no code analysis found $conditions = null; } elseif ($file === false) { $conditions = $this->latest_parse['cond_code']; } elseif (isset($this->latest_parse[$file])) { $conditions = $this->latest_parse[$file]['cond_code']; } else { $conditions = null; } if (is_array($conditions) && $levelOnly === true) { $conditions = $conditions[0]; } return $conditions; } /** * Parse a data source * * Parse a data source with auto detect ability. This data source, may be * one of these follows: a directory, a file, a string (chunk of code), * an array of multiple origin. * * Each of five parsing functions support common and specifics options. * * * Common options : * - 'debug' Contains a boolean to control whether * extra ouput is shown. * - 'ignore_functions' Contains an array of functions to ignore * when calculating the version needed. * - 'ignore_constants' Contains an array of constants to ignore * when calculating the version needed. * - 'ignore_extensions' Contains an array of php extensions to ignore * when calculating the version needed. * - 'ignore_versions' Contains an array of php versions to ignore * when calculating the version needed. * - 'ignore_functions_match' Contains an array of function patterns to ignore * when calculating the version needed. * - 'ignore_extensions_match' Contains an array of extension patterns to ignore * when calculating the version needed. * - 'ignore_constants_match' Contains an array of constant patterns to ignore * when calculating the version needed. * * * parseArray, parseDir|parseFolder, specific options : * - 'file_ext' Contains an array of file extensions to parse * for PHP code. Default: php, php4, inc, phtml * - 'ignore_files' Contains an array of files to ignore. * File names are case insensitive. * * * parseArray specific options : * - 'is_string' Contains a boolean which says if the array values * are strings or file names. * * * parseDir|parseFolder specific options : * - 'recurse_dir' Boolean on whether to recursively find files * - 'ignore_dirs' Contains an array of directories to ignore. * Directory names are case insensitive. * * @param mixed $dataSource The data source (may be file, dir, string, or array) * @param array $options An array of options. See above. * * @access public * @return array or false on error * @since version 1.8.0b2 (2008-06-03) */ function parseData($dataSource, $options = array()) { $this->options = array_merge($this->options, $options); $dataType = gettype($dataSource); $dataCount = 0; // - when array source with mixed content incompatible // - if all directories are not readable // - if data source invalid type: other than file, directory, string if ($dataType == 'string' || $dataType == 'array') { if (is_array($dataSource)) { //$dataType = 'array'; } elseif (is_dir($dataSource)) { $dataType = 'directory'; $dataSource = array($dataSource); } elseif (is_file($dataSource)) { $dataType = 'file'; $dataSource = array($dataSource); } elseif (substr($dataSource, 0, 5) == 'options = array_merge($this->options, array('is_string' => true)); $dataSource = array($dataSource); } else { //$dataType = 'string'; // directory or file are misspelled } if (is_array($dataSource)) { $dataSource = $this->_validateDataSource($dataSource, $this->options); $dataCount = count($dataSource); } } $this->dataSource = array('dataSource' => $dataSource, 'dataType' => $dataType, 'dataCount' => $dataCount); $eventInfo = array_merge($this->dataSource, array('parseOptions' => $this->options)); // notify all observers that parsing data source begin $this->notifyListeners(PHP_COMPATINFO_EVENT_AUDITSTARTED, $eventInfo); if ($dataCount == 0) { $parseData = false; } else { switch ($dataType) { case 'array' : $parseData = $this->_parseArray($dataSource, $this->options); break; case 'string' : $parseData = $this->_parseString($dataSource, $this->options); break; case 'file' : $parseData = $this->_parseFile($dataSource, $this->options); break; case 'directory' : $parseData = $this->_parseDir($dataSource, $this->options); break; } } // notify all observers that parsing data source is over $this->notifyListeners(PHP_COMPATINFO_EVENT_AUDITFINISHED, $parseData); $this->latest_parse = $parseData; return $parseData; } /** * Validate content of data source * * Validate content of data source list, before parsing each source * * @param mixed $dataSource The data source (may be file, dir, or string) * @param array $options Parser options (see parseData() method for details) * * @access private * @return array empty array on error * @since version 1.8.0b3 (2008-06-07) */ function _validateDataSource($dataSource, $options = array()) { /** * Array by default expect to contains list of files and/or directories. * If you want a list of chunk of code (strings), 'is_string' option * must be set to true. */ $list = array(); foreach ($dataSource as $source) { if ($options['is_string'] === true) { if (is_string($source)) { $list[] = $source; } else { /** * One of items is not a string (chunk of code). All * data sources parsing are stopped and considered as invalid. */ $list = array(); break; } } else { if (is_dir($source) && is_readable($source)) { $files = $this->getFilelist($source, $options); $list = array_merge($list, $files); } elseif (is_file($source)) { $list[] = $source; } else { /** * One of items is not a valid file or directory. All * data sources parsing are stopped and considered as invalid. */ $list = array(); break; } } } return $list; } /** * Parse an Array of Files * * You can parse an array of Files or Strings, to parse * strings, $options['is_string'] must be set to true * * @param array $dataSource Array of file &| directory names or code strings * @param array $options Parser options (see parseData() method for details) * * @access private * @return array or false on error * @since version 0.7.0 (2004-03-09) * @see parseData() */ function _parseArray($dataSource, $options = array()) { // Each data source have been checked before (see _validateDataSource() ) if (is_file($dataSource[0])) { $parseData = $this->_parseDir($dataSource, $options); } else { $parseData = $this->_parseString($dataSource, $options); } return $parseData; } /** * Parse a string * * Parse a string for its compatibility info. * * @param array $strings PHP Code to parse * @param array $options Parser options (see parseData() method for details) * * @access private * @return array or false on error * @since version 0.7.0 (2004-03-09) * @see parseData() */ function _parseString($strings, $options = array()) { $results = $this->_parseElements($strings, $options); return $results; } /** * Parse a single file * * Parse a single file for its compatibility info. * * @param string $file File to parse * @param array $options Parser options (see parseData() method for details) * * @access private * @return array or false on error * @since version 0.7.0 (2004-03-09) * @see parseData() */ function _parseFile($file, $options = array()) { $results = $this->_parseElements($file, $options); return $results; } /** * Parse a directory * * Parse a directory recursively for its compatibility info * * @param array $files Files list of folder to parse * @param array $options Parser options (see parseData() method for details) * * @access private * @return array or false on error * @since version 0.8.0 (2004-04-22) * @see parseData() */ function _parseDir($files, $options = array()) { $results = $this->_parseElements($files, $options); return $results; } /** * Parse a list of elements * * Parse a list of directory|file elements, or chunk of code (strings) * * @param array $elements Array of file &| directory names or code strings * @param array $options Parser options (see parseData() method for details) * * @access private * @return array * @since version 1.8.0b3 (2008-06-07) * @see _parseString(), _parseDir() */ function _parseElements($elements, $options = array()) { $files_parsed = array(); $latest_version = $this->latest_version; $earliest_version = $this->earliest_version; $all_functions = array(); $classes = array(); $functions = array(); $extensions = array(); $constants = array(); $tokens = array(); $ignored_functions = array(); $ignored_extensions = array(); $ignored_constants = array(); $function_exists = array(); $extension_loaded = array(); $defined = array(); $cond_code = 0; foreach ($elements as $p => $element) { $index = $p + 1; if (is_file($element)) { if (in_array($element, $options['ignore_files'])) { $this->ignored_files[] = $element; continue; } $eventInfo = array('filename' => $element, 'fileindex' => $index); $this->notifyListeners(PHP_COMPATINFO_EVENT_FILESTARTED, $eventInfo); $tokens_list = $this->_tokenize($element); $kfile = $element; $files_parsed[$kfile] = $this->_parseTokens($tokens_list, $options); $this->notifyListeners(PHP_COMPATINFO_EVENT_FILEFINISHED); } else { $eventInfo = array('stringdata' => $element, 'stringindex' => $index); $this->notifyListeners(PHP_COMPATINFO_EVENT_CODESTARTED, $eventInfo); $tokens_list = $this->_tokenize($element, true); $kfile = 'string_' . $index; $files_parsed[$kfile] = $this->_parseTokens($tokens_list, $options); $this->notifyListeners(PHP_COMPATINFO_EVENT_CODEFINISHED); } } foreach ($files_parsed as $fn => $file) { $cmp = version_compare($latest_version, $file['version']); if ($cmp === -1) { $latest_version = $file['version']; } if ($file['max_version'] != '') { $cmp = version_compare($earliest_version, $file['max_version']); if ($earliest_version == '' || $cmp === 1) { $earliest_version = $file['max_version']; } } foreach ($file['classes'] as $class) { if (!in_array($class, $classes)) { $classes[] = $class; } } foreach ($file['functions'] as $func) { if (!in_array($func, $functions)) { $functions[] = $func; } } foreach ($file['extensions'] as $ext) { if (!in_array($ext, $extensions)) { $extensions[] = $ext; } } foreach ($file['constants'] as $const) { if (!in_array($const, $constants)) { $constants[] = $const; } } foreach ($file['tokens'] as $token) { if (!in_array($token, $tokens)) { $tokens[] = $token; } } foreach ($file['ignored_functions'] as $if) { if (!in_array($if, $ignored_functions)) { $ignored_functions[] = $if; } } foreach ($file['ignored_extensions'] as $ie) { if (!in_array($ie, $ignored_extensions)) { $ignored_extensions[] = $ie; } } foreach ($file['ignored_constants'] as $ic) { if (!in_array($ic, $ignored_constants)) { $ignored_constants[] = $ic; } } foreach ($file['cond_code'][1][0] as $ccf) { if (!in_array($ccf, $function_exists)) { $function_exists[] = $ccf; } } foreach ($file['cond_code'][1][1] as $cce) { if (!in_array($cce, $extension_loaded)) { $extension_loaded[] = $cce; } } foreach ($file['cond_code'][1][2] as $ccc) { if (!in_array($ccc, $defined)) { $defined[] = $ccc; } } if ($options['debug'] === false) { unset($files_parsed[$fn]['cond_code'][1]); } else { unset($file['ignored_functions']); unset($file['ignored_extensions']); unset($file['ignored_constants']); unset($file['max_version']); unset($file['version']); unset($file['classes']); unset($file['functions']); unset($file['extensions']); unset($file['constants']); unset($file['tokens']); unset($file['cond_code']); foreach ($file as $version => $file_functions) { // extra information available only when debug mode is on if (isset($all_functions[$version])) { foreach ($file_functions as $func) { $k = array_search($func, $all_functions[$version]); if ($k === false) { $all_functions[$version][] = $func; } } } else { $all_functions[$version] = $file_functions; } } } } if (count($files_parsed) == 0) { return false; } if (count($function_exists) > 0) { $cond_code += 1; } if (count($extension_loaded) > 0) { $cond_code += 2; } if (count($defined) > 0) { $cond_code += 4; } if ($options['debug'] === false) { $cond_code = array($cond_code); } else { sort($function_exists); sort($extension_loaded); sort($defined); $cond_code = array($cond_code, array($function_exists, $extension_loaded, $defined)); } sort($ignored_functions); sort($ignored_extensions); sort($ignored_constants); sort($classes); sort($functions); natcasesort($extensions); sort($constants); sort($tokens); $main_info = array('ignored_files' => $this->getIgnoredFiles(), 'ignored_functions' => $ignored_functions, 'ignored_extensions' => $ignored_extensions, 'ignored_constants' => $ignored_constants, 'max_version' => $earliest_version, 'version' => $latest_version, 'classes' => $classes, 'functions' => $functions, 'extensions' => array_values($extensions), 'constants' => $constants, 'tokens' => $tokens, 'cond_code' => $cond_code); if (count($files_parsed) == 1) { if ($options['debug'] === false) { $parseData = $main_info; } else { $main_info = array('ignored_files' => $this->getIgnoredFiles()); $parseData = array_merge($main_info, $files_parsed[$kfile], $all_functions); } } else { if ($options['debug'] === false) { $parseData = array_merge($main_info, $files_parsed); } else { $parseData = array_merge($main_info, $all_functions, $files_parsed); } } $this->notifyListeners(PHP_COMPATINFO_EVENT_FILEFINISHED, $parseData); return $parseData; } /** * Token a file or string * * @param string $input Filename or PHP code * @param boolean $is_string Whether or note the input is a string * @param boolean $debug add token names for human read * * @access private * @return array * @since version 0.7.0 (2004-03-09) */ function _tokenize($input, $is_string = false, $debug = false) { if ($is_string === false) { $input = file_get_contents($input, true); } $tokens = token_get_all($input); if ($debug === true) { $r = array(); foreach ($tokens as $token) { if (is_array($token)) { $token[] = token_name($token[0]); } else { $token = $token[0]; } $r[] = $token; } } else { $r = $tokens; } return $r; } /** * Parse the given Tokens * * The tokens are those returned by token_get_all() which is nicely * wrapped in PHP_CompatInfo::_tokenize * * @param array $tokens Array of PHP Tokens * @param boolean $options Show Extra Output * * @access private * @return array * @since version 0.7.0 (2004-03-09) */ function _parseTokens($tokens, $options) { static $akeys; $classes = array(); $functions = array(); $functions_version = array(); $latest_version = $this->latest_version; $earliest_version = $this->earliest_version; $extensions = array(); $constants = array(); $constant_names = array(); $token_names = array(); $udf = array(); $ignore_functions = array(); $ignored_functions = array(); $ignore_extensions = array(); $ignored_extensions = array(); $ignore_constants = array(); $ignored_constants = array(); $function_exists = array(); $extension_loaded = array(); $defined = array(); $cond_code = 0; if (isset($options['ignore_constants'])) { $options['ignore_constants'] = array_map('strtoupper', $options['ignore_constants']); } else { $options['ignore_constants'] = array(); } if (isset($options['ignore_extensions'])) { $options['ignore_extensions'] = array_map('strtolower', $options['ignore_extensions']); } else { $options['ignore_extensions'] = array(); } if (isset($options['ignore_versions'][0])) { $min_ver = $options['ignore_versions'][0]; } else { $min_ver = false; } if (isset($options['ignore_versions'][1])) { $max_ver = $options['ignore_versions'][1]; } else { $max_ver = false; } if (isset($options['ignore_functions_match'])) { list($ifm_compare, $ifm_patterns) = $options['ignore_functions_match']; } else { $ifm_compare = false; } if (isset($options['ignore_extensions_match'])) { list($iem_compare, $iem_patterns) = $options['ignore_extensions_match']; } else { $iem_compare = false; } if (isset($options['ignore_constants_match'])) { list($icm_compare, $icm_patterns) = $options['ignore_constants_match']; } else { $icm_compare = false; } $token_count = sizeof($tokens); $i = 0; $found_class = false; while ($i < $token_count) { if ($this->_isToken($tokens[$i], 'T_FUNCTION')) { $found_func = false; } else { $found_func = true; } while ($found_func == false) { $i += 1; if ($this->_isToken($tokens[$i], 'T_STRING')) { $found_func = true; $func = $tokens[$i][1]; if ($found_class === false || in_array($func, $function_exists)) { $udf[] = $func; } } } // Try to detect PHP method chaining implementation if ($this->_isToken($tokens[$i], 'T_VARIABLE') && $this->_isToken($tokens[$i+1], 'T_OBJECT_OPERATOR') && $this->_isToken($tokens[$i+2], 'T_STRING') && $this->_isToken($tokens[$i+3], '(')) { $i += 3; $php5_method_chaining = false; while (((!is_array($tokens[$i]) && $tokens[$i] == ';') === false) && (!$this->_isToken($tokens[$i], 'T_CLOSE_TAG')) ) { $i += 1; if ((($this->_isToken($tokens[$i], ')')) || ($this->_isToken($tokens[$i], 'T_WHITESPACE'))) && $this->_isToken($tokens[$i+1], 'T_OBJECT_OPERATOR')) { $php5_method_chaining = true; } } } // Compare "ignore_functions_match" pre-condition if (is_string($ifm_compare)) { if (strcasecmp('preg_match', $ifm_compare) != 0) { // Try to catch function_exists() condition if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], $ifm_compare) == 0)) { while ((!$this->_isToken($tokens[$i], 'T_CONSTANT_ENCAPSED_STRING'))) { $i += 1; } $func = trim($tokens[$i][1], "'"); /** * try if function_exists() * match one or more pattern condition */ foreach ($ifm_patterns as $pattern) { if (preg_match($pattern, $func) === 1) { $ignore_functions[] = $func; } } } } } // Compare "ignore_extensions_match" pre-condition if (is_string($iem_compare)) { if (strcasecmp('preg_match', $iem_compare) != 0) { // Try to catch extension_loaded() condition if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], $iem_compare) == 0)) { while ((!$this->_isToken($tokens[$i], 'T_CONSTANT_ENCAPSED_STRING'))) { $i += 1; } $ext = trim($tokens[$i][1], "'"); /** * try if extension_loaded() * match one or more pattern condition */ foreach ($iem_patterns as $pattern) { if (preg_match($pattern, $ext) === 1) { $ignore_extensions[] = $ext; } } } } } // Compare "ignore_constants_match" pre-condition if (is_string($icm_compare)) { if (strcasecmp('preg_match', $icm_compare) != 0) { // Try to catch defined() condition if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], $icm_compare) == 0)) { while ((!$this->_isToken($tokens[$i], 'T_CONSTANT_ENCAPSED_STRING'))) { $i += 1; } $cst = trim($tokens[$i][1], "'"); /** * try if defined() * match one or more pattern condition */ foreach ($icm_patterns as $pattern) { if (preg_match($pattern, $cst) === 1) { $ignore_constants[] = $cst; } } } } } // try to detect class instantiation if ($this->_isToken($tokens[$i], 'T_STRING') && (isset($tokens[$i-2])) && $this->_isToken($tokens[$i-2], 'T_NEW')) { $is_class = true; $classes[] = $tokens[$i][1]; } else { $is_class = false; } if ($this->_isToken($tokens[$i], 'T_STRING') && $is_class == false && (isset($tokens[$i+1])) && $this->_isToken($tokens[$i+1], '(')) { $is_function = false; if (isset($tokens[$i-1]) && !$this->_isToken($tokens[$i-1], 'T_DOUBLE_COLON') && !$this->_isToken($tokens[$i-1], 'T_OBJECT_OPERATOR')) { if (isset($tokens[$i-2]) && $this->_isToken($tokens[$i-2], 'T_FUNCTION')) { // its a function declaration } else { $is_function = true; } } if ($is_function == true || !is_array($tokens[$i-1])) { $functions[] = strtolower($tokens[$i][1]); } } // try to detect condition function_exists() if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], 'function_exists') == 0)) { $j = $i; while ((!$this->_isToken($tokens[$j], ')'))) { if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) { $t_string = $tokens[$j][1]; $t_string = trim($t_string, "'"); $t_string = trim($t_string, '"'); $function_exists[] = $t_string; } $j++; } } // try to detect condition extension_loaded() if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], 'extension_loaded') == 0)) { $j = $i; while ((!$this->_isToken($tokens[$j], ')'))) { if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) { $t_string = $tokens[$j][1]; $t_string = trim($t_string, "'"); $t_string = trim($t_string, '"'); $extension_loaded[] = $t_string; } $j++; } } // try to detect condition defined() if ($this->_isToken($tokens[$i], 'T_STRING') && (strcasecmp($tokens[$i][1], 'defined') == 0)) { $j = $i; while ((!$this->_isToken($tokens[$j], ')'))) { if ($this->_isToken($tokens[$j], 'T_CONSTANT_ENCAPSED_STRING')) { $t_string = $tokens[$j][1]; $t_string = trim($t_string, "'"); $t_string = trim($t_string, '"'); $defined[] = $t_string; } $j++; } } // try to detect beginning of a class if ($this->_isToken($tokens[$i], 'T_CLASS')) { $found_class = true; } if (is_array($tokens[$i])) { if (!isset($akeys)) { // build contents one time only (static variable) $akeys = array_keys($GLOBALS['_PHP_COMPATINFO_CONST']); } $const = strtoupper($tokens[$i][1]); $found = array_search($const, $akeys); if ($found !== false) { if ($this->_isToken($tokens[$i], 'T_ENCAPSED_AND_WHITESPACE')) { // PHP 5 constant tokens found into a string } else { // Compare "ignore_constants_match" free condition $icm_preg_match = false; if (is_string($icm_compare)) { if (strcasecmp('preg_match', $icm_compare) == 0) { /** * try if preg_match() * match one or more pattern condition */ foreach ($icm_patterns as $pattern) { if (preg_match($pattern, $const) === 1) { $icm_preg_match = true; break; } } } } $init = $GLOBALS['_PHP_COMPATINFO_CONST'][$const]['init']; if (!PHP_CompatInfo_Parser::_ignore($init, $min_ver, $max_ver)) { $constants[] = $const; if (in_array($const, $ignore_constants) || in_array($const, $options['ignore_constants']) || $icm_preg_match) { $ignored_constants[] = $const; } else { $latest_version = $init; } } } } } $i += 1; } $classes = array_unique($classes); $functions = array_unique($functions); if (isset($options['ignore_functions'])) { $options['ignore_functions'] = array_map('strtolower', $options['ignore_functions']); } else { $options['ignore_functions'] = array(); } if (count($ignore_functions) > 0) { $ignore_functions = array_map('strtolower', $ignore_functions); $options['ignore_functions'] = array_merge($options['ignore_functions'], $ignore_functions); $options['ignore_functions'] = array_unique($options['ignore_functions']); } if (count($ignore_extensions) > 0) { $options['ignore_extensions'] = array_merge($options['ignore_extensions'], $ignore_extensions); $options['ignore_extensions'] = array_unique($options['ignore_extensions']); } foreach ($classes as $name) { if (!isset($GLOBALS['_PHP_COMPATINFO_CLASS'][$name])) { continue; // skip this unknown class } $class = $GLOBALS['_PHP_COMPATINFO_CLASS'][$name]; if (PHP_CompatInfo_Parser::_ignore($class['init'], $min_ver, $max_ver)) { continue; // skip this class version } $cmp = version_compare($latest_version, $class['init']); if ($cmp === -1) { $latest_version = $class['init']; } if (array_key_exists('end', $class)) { $cmp = version_compare($earliest_version, $class['end']); if ($earliest_version == '' || $cmp === 1) { $earliest_version = $class['end']; } } if (array_key_exists('ext', $class)) { // this class depends of an extension $extensions[] = $class['ext']; } } foreach ($functions as $name) { if (!isset($GLOBALS['_PHP_COMPATINFO_FUNCS'][$name])) { continue; // skip this unknown function } $func = $GLOBALS['_PHP_COMPATINFO_FUNCS'][$name]; // retrieve if available the extension name if ((isset($func['ext'])) && ($func['ext'] != 'standard') && ($func['ext'] != 'zend')) { $extension = $func['ext']; } else { $extension = false; } // Compare "ignore_functions_match" free condition $ifm_preg_match = false; if (is_string($ifm_compare)) { if (strcasecmp('preg_match', $ifm_compare) == 0) { /** * try if preg_match() * match one or more pattern condition */ foreach ($ifm_patterns as $pattern) { if (preg_match($pattern, $name) === 1) { $ifm_preg_match = true; break; } } } } if ((!in_array($name, $udf)) && (!in_array($name, $options['ignore_functions'])) && ($ifm_preg_match === false)) { if ($extension && !in_array($extension, $extensions)) { $extensions[] = $extension; } // Compare "ignore_extensions_match" free condition $iem_preg_match = false; if (is_string($iem_compare)) { if (strcasecmp('preg_match', $iem_compare) == 0) { /** * try if preg_match() * match one or more pattern condition */ foreach ($iem_patterns as $pattern) { if (preg_match($pattern, $extension) === 1) { $iem_preg_match = true; break; } } } } if ($extension && (in_array($extension, $options['ignore_extensions']) || $iem_preg_match)) { if (!in_array($extension, $ignored_extensions)) { // extension is ignored (only once) $ignored_extensions[] = $extension; } // all extension functions are also ignored $ignored_functions[] = $name; continue; // skip this extension function } if (PHP_CompatInfo_Parser::_ignore($func['init'], $min_ver, $max_ver)) { continue; // skip this function version } if ($options['debug'] == true) { $functions_version[$func['init']][] = array( 'function' => $name, 'extension' => $extension, 'pecl' => $func['pecl'] ); } if ($extension === false || (isset($func['pecl']) && $func['pecl'] === false) ) { $cmp = version_compare($latest_version, $func['init']); if ($cmp === -1) { $latest_version = $func['init']; } if (array_key_exists('end', $func)) { $cmp = version_compare($earliest_version, $func['end']); if ($earliest_version == '' || $cmp === 1) { $earliest_version = $func['end']; } } } } else { // function is ignored $ignored_functions[] = $name; } } $ignored_constants = array_unique($ignored_constants); $constants = array_unique($constants); foreach ($constants as $constant) { $const = $GLOBALS['_PHP_COMPATINFO_CONST'][$constant]; if (PHP_CompatInfo_Parser::_ignore($const['init'], $min_ver, $max_ver)) { continue; // skip this constant version } if (!in_array($constant, $ignored_constants)) { $cmp = version_compare($latest_version, $const['init']); if ($cmp === -1) { $latest_version = $const['init']; } if (array_key_exists('end', $const)) { $cmp = version_compare($earliest_version, $const['end']); if ($earliest_version == '' || $cmp === 1) { $earliest_version = $const['end']; } } } if (!in_array($const['name'], $constant_names)) { // split PHP5 tokens and pure PHP constants if ($const['name'] == strtolower($const['name'])) { $token_names[] = $const['name']; } else { $constant_names[] = $const['name']; } } } if (isset($php5_method_chaining) && $php5_method_chaining === true && version_compare($latest_version, '5.0.0') < 0) { // when PHP Method chaining is detected, only available for PHP 5 $latest_version = '5.0.0'; } ksort($functions_version); if (count($function_exists) > 0) { $function_exists = array_unique($function_exists); $cond_code += 1; } if (count($extension_loaded) > 0) { $extension_loaded = array_unique($extension_loaded); $cond_code += 2; } if (count($defined) > 0) { $defined = array_unique($defined); $cond_code += 4; } $cond_code = array($cond_code, array($function_exists, $extension_loaded, $defined)); sort($ignored_functions); sort($ignored_extensions); sort($ignored_constants); sort($classes); sort($functions); natcasesort($extensions); sort($constant_names); sort($token_names); $main_info = array('ignored_functions' => $ignored_functions, 'ignored_extensions' => $ignored_extensions, 'ignored_constants' => $ignored_constants, 'max_version' => $earliest_version, 'version' => $latest_version, 'classes' => $classes, 'functions' => $functions, 'extensions' => array_values($extensions), 'constants' => $constant_names, 'tokens' => $token_names, 'cond_code' => $cond_code); $functions_version = array_merge($main_info, $functions_version); return $functions_version; } /** * Checks if function which has $init version should be keep * or ignore (version is between $min_ver and $max_ver). * * @param string $init version of current function * @param string $min_ver minimum version of function to ignore * @param string $max_ver maximum version of function to ignore * * @access private * @return boolean True to ignore function/constant, false otherwise * @since version 1.4.0 (2006-09-27) * @static */ function _ignore($init, $min_ver, $max_ver) { if ($min_ver) { $cmp = version_compare($init, $min_ver); if ($max_ver && $cmp >= 0) { $cmp = version_compare($init, $max_ver); if ($cmp < 1) { return true; } } elseif ($cmp === 0) { return true; } } return false; } /** * Checks if the given token is of this symbolic name * * @param mixed $token Single PHP token to test * @param string $symbolic Symbolic name of the given token * * @access private * @return bool * @since version 1.7.0b4 (2008-04-03) */ function _isToken($token, $symbolic) { if (is_array($token)) { $t = token_name($token[0]); } else { $t = $token; } return ($t == $symbolic); } /** * Computes the difference of arrays * * Computes the difference of arrays and returns result without original keys * * @param array $array1 The array to compare from * @param array $array2 The array to compare against * * @access private * @static * @link http://www.php.net/manual/en/function.array-diff.php#82297 * @return array * @since version 1.8.0b2 (2008-06-03) */ function _arrayDiff($array1, $array2) { // This wrapper for array_diff rekeys the array returned $valid_array = array_diff($array1, $array2); // reinstantiate $array1 variable $array1 = array(); // loop through the validated array and move elements to $array1 // this is necessary because the array_diff function // returns arrays that retain their original keys foreach ($valid_array as $valid) { $array1[] = $valid; } return $array1; } } ?>