<?php
/**
 * Copyright (c) 2004-2009, Davey Shafik <davey@php.net>
 *                          Laurent Laville <pear@laurent-laville.org>
 *
 * 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 <davey@php.net>
 * @author   Laurent Laville <pear@laurent-laville.org>
 * @license  http://www.opensource.org/licenses/bsd-license.php  BSD
 * @version  CVS: $Id: Cli.php,v 1.75 2009/01/02 10:18:47 farell Exp $
 * @link     http://pear.php.net/package/PHP_CompatInfo
 * @since    File available since Release 0.8.0
 */

require_once 'PHP/CompatInfo.php';
require_once 'Console/Getargs.php';

/**
 * CLI Script to Check Compatibility of chunk of PHP code
 *
 * <code>
 * <?php
 *     require_once 'PHP/CompatInfo/Cli.php';
 *     $cli = new PHP_CompatInfo_Cli();
 *     $cli->run();
 * ?>
 * </code>
 *
 * @category  PHP
 * @package   PHP_CompatInfo
 * @author    Davey Shafik <davey@php.net>
 * @author    Laurent Laville <pear@laurent-laville.org>
 * @copyright 2003 Davey Shafik and Synaptic Media. All Rights Reserved.
 * @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 0.8.0
 */

class PHP_CompatInfo_Cli
{
    /**
     * @var    array    Current CLI Flags
     * @since  0.8.0
     */
    var $opts = array();

    /**
     * Unified data source reference
     *
     * @var    string   Directory, File or String to be processed
     * @since  1.8.0b3
     */
    var $dataSource;

    /**
     * @var    array    Current parser options
     * @since  1.4.0
     */
    var $options = array();


    /**
     * Command-Line Class constructor
     *
     * Command-Line Class constructor (ZE2) for PHP5+
     *
     * @since  version 0.8.0 (2004-04-22)
     */
    function __construct()
    {
        $this->opts = array(
            'dir' =>
                array('short' => 'd',
                      'desc'  => 'Parse DIR to get its compatibility info',
                      'default' => '',
                      'min'   => 0 , 'max' => 1),
            'file' =>
                array('short' => 'f',
                      'desc' => 'Parse FILE to get its compatibility info',
                      'default' => '',
                      'min'   => 0 , 'max' => 1),
            'string' =>
                array('short' => 's',
                      'desc' => 'Parse STRING to get its compatibility info',
                      'default' => '',
                      'min'   => 0 , 'max' => 1),
            'verbose' =>
                array('short'   => 'v',
                      'desc'    => 'Set the verbose level',
                      'default' => 1,
                      'min'     => 0 , 'max' => 1),
            'no-recurse' =>
                array('short' => 'n',
                      'desc'  => 'Do not recursively parse files when using --dir',
                      'max'   => 0),
            'ignore-files' =>
                array('short'   => 'if',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'file to ignore',
                      'default' => 'files.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-dirs' =>
                array('short'   => 'id',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'directory to ignore',
                      'default' => 'dirs.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-functions' =>
                array('short'   => 'in',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php function to ignore',
                      'default' => 'functions.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-constants' =>
                array('short'   => 'ic',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php constant to ignore',
                      'default' => 'constants.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-extensions' =>
                array('short'   => 'ie',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php extension to ignore',
                      'default' => 'extensions.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-versions' =>
                array('short'   => 'iv',
                      'desc'    => 'PHP versions - functions to exclude '
                                 . 'when parsing source code',
                      'default' => '5.0.0',
                      'min'     => 0 , 'max' => 2),
            'ignore-functions-match' =>
                array('short'   => 'inm',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php function pattern to ignore',
                      'default' => 'functions-match.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-extensions-match' =>
                array('short'   => 'iem',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php extension pattern to ignore',
                      'default' => 'extensions-match.txt',
                      'min'     => 0 , 'max' => 1),
            'ignore-constants-match' =>
                array('short'   => 'icm',
                      'desc'    => 'Data file name which contains a list of '
                                 . 'php constant pattern to ignore',
                      'default' => 'constants-match.txt',
                      'min'     => 0 , 'max' => 1),
            'file-ext' =>
                array('short'   => 'fe',
                      'desc'    => 'A comma separated list of file extensions '
                                 . 'to parse (only valid if parsing a directory)',
                      'default' => 'php, php4, inc, phtml',
                      'min'     => 0 , 'max' => 1),
            'report' =>
                array('short' => 'r',
                      'desc' => 'Print either "xml" or "csv" report',
                      'default' => 'text',
                      'min'   => 0 , 'max' => 1),
            'output-level' =>
                array('short' => 'o',
                      'desc' => 'Print Path/File + Version with additional data',
                      'default' => 31,
                      'min'   => 0 , 'max' => 1),
            'tab' =>
                array('short' => 't',
                      'desc'  => 'Columns width',
                      'default' => '29,12,20',
                      'min'   => 0 , 'max' => 1),
            'progress' =>
                array('short' => 'p',
                      'desc' => 'Show a wait message [text] or a progress bar [bar]',
                      'default' => 'bar',
                      'min'   => 0 , 'max' => 1),
            'summarize' =>
                array('short' => 'S',
                      'desc' => 'Print only summary when parsing directory',
                      'max'   => 0),
            'version' =>
                array('short' => 'V',
                      'desc'  => 'Print version information',
                      'max'   => 0),
            'help' =>
                array('short' => 'h',
                      'desc'  => 'Show this help',
                      'max'   => 0),
        );
    }

    /**
     * Command-Line Class constructor
     *
     * Command-Line Class constructor (ZE1) for PHP4
     *
     * @since  version 0.8.0 (2004-04-22)
     */
    function PHP_CompatInfo_Cli()
    {
        $this->__construct();
    }

    /**
     * Run the CLI version
     *
     * Run the CLI version of PHP_CompatInfo
     *
     * @return void
     * @access public
     * @since  version 0.8.0 (2004-04-22)
     */
    function run()
    {
        $args = & Console_Getargs::factory($this->opts);
        if (PEAR::isError($args)) {
            if ($args->getCode() === CONSOLE_GETARGS_HELP) {
                $error = '';
            } else {
                $error = $args->getMessage();
            }
            $this->_printUsage($error);
            return;
        }

        // default parser options
        $this->options = array(
            'file_ext' => array('php', 'php4', 'inc', 'phtml'),
            'recurse_dir' => true,
            'debug' => false,
            'is_string' => false,
            'ignore_files' => array(),
            'ignore_dirs' => array()
            );

        // version
        $V = $args->getValue('V');
        if (isset($V)) {
            $error = 'PHP_CompatInfo (cli) version 1.9.0'
                   . ' (http://pear.php.net/package/PHP_CompatInfo)';
            echo $error;
            return;
        }

        // debug
        if ($args->isDefined('v')) {
            $v = $args->getValue('v');
            if ($v > 3) {
                $this->options['debug'] = true;
            }
        }

        // no-recurse
        if ($args->isDefined('n')) {
            $this->options['recurse_dir'] = false;
        }

        // dir
        if ($args->isDefined('d')) {
            $d = $args->getValue('d');
            if (file_exists($d)) {
                if ($d{strlen($d)-1} == '/' || $d{strlen($d)-1} == '\\') {
                    $d = substr($d, 0, -1);
                }
                $this->dataSource = realpath($d);
            } else {
                $error = 'Failed opening directory "' . $d
                       . '". Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // file
        if ($args->isDefined('f')) {
            $f = $args->getValue('f');
            if (file_exists($f)) {
                $this->dataSource = $f;
            } else {
                $error = 'Failed opening file "' . $f
                       . '". Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // string
        if ($args->isDefined('s')) {
            $s = $args->getValue('s');
            if (!empty($s)) {
                $this->dataSource           = sprintf("<?php %s ?>", $s);
                $this->options['is_string'] = true;
            } else {
                $error = 'Failed opening string "' . $s
                       . '". Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-files
        $if = $args->getValue('if');
        if (isset($if)) {
            if (file_exists($if)) {
                $options                       = $this->_parseParamFile($if);
                $this->options['ignore_files'] = $options['std'];
            } else {
                $error = 'Failed opening file "' . $if
                       . '" (ignore-files option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-dirs
        $id = $args->getValue('id');
        if (isset($id)) {
            if (file_exists($id)) {
                $options                      = $this->_parseParamFile($id);
                $this->options['ignore_dirs'] = $options['std'];
            } else {
                $error = 'Failed opening file "' . $id
                       . '" (ignore-dirs option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-functions
        $in = $args->getValue('in');
        if (isset($in)) {
            if (file_exists($in)) {
                $options                           = $this->_parseParamFile($in);
                $this->options['ignore_functions'] = $options['std'];
            } else {
                $error = 'Failed opening file "' . $in
                       . '" (ignore-functions option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-constants
        $ic = $args->getValue('ic');
        if (isset($ic)) {
            if (file_exists($ic)) {
                $options                           = $this->_parseParamFile($ic);
                $this->options['ignore_constants'] = $options['std'];
            } else {
                $error = 'Failed opening file "' . $ic
                       . '" (ignore-constants option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-extensions
        $ie = $args->getValue('ie');
        if (isset($ie)) {
            if (file_exists($ie)) {
                $options                            = $this->_parseParamFile($ie);
                $this->options['ignore_extensions'] = $options['std'];
            } else {
                $error = 'Failed opening file "' . $ie
                       . '" (ignore-extensions option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-versions
        $iv = $args->getValue('iv');
        if (isset($iv)) {
            if (!is_array($iv)) {
                $iv = array($iv);
            }
            $this->options['ignore_versions'] = $iv;
        }

        // ignore-functions-match
        $inm = $args->getValue('inm');
        if (isset($inm)) {
            if (file_exists($inm)) {
                $patterns = $this->_parseParamFile($inm, true);
                if (count($patterns['std']) > 0
                    && count($patterns['reg']) > 0) {
                    $error = 'Mixed "function_exists" and '
                           . '"preg_match" conditions are not allowed. '
                           . 'Please check your spelling and try again.';
                    $this->_printUsage($error);
                    return;

                } elseif (count($patterns['std']) > 0) {
                    $this->options['ignore_functions_match']
                        = array('function_exists', $patterns['std']);
                } elseif (count($patterns['reg']) > 0) {
                    $this->options['ignore_functions_match']
                        = array('preg_match', $patterns['reg']);
                }
            } else {
                $error = 'Failed opening file "' . $inm
                       . '" (ignore-functions-match option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-extensions-match
        $iem = $args->getValue('iem');
        if (isset($iem)) {
            if (file_exists($iem)) {
                $patterns = $this->_parseParamFile($iem, true);
                if (count($patterns['std']) > 0
                    && count($patterns['reg']) > 0) {
                    $error = 'Mixed "extension_loaded" and '
                           . '"preg_match" conditions are not allowed. '
                           . 'Please check your spelling and try again.';
                    $this->_printUsage($error);
                    return;

                } elseif (count($patterns['std']) > 0) {
                    $this->options['ignore_extensions_match']
                        = array('extension_loaded', $patterns['std']);
                } elseif (count($patterns['reg']) > 0) {
                    $this->options['ignore_extensions_match']
                        = array('preg_match', $patterns['reg']);
                }
            } else {
                $error = 'Failed opening file "' . $iem
                       . '" (ignore-extensions-match option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // ignore-constants-match
        $icm = $args->getValue('icm');
        if (isset($icm)) {
            if (file_exists($icm)) {
                $patterns = $this->_parseParamFile($icm, true);
                if (count($patterns['std']) > 0
                    && count($patterns['reg']) > 0) {
                    $error = 'Mixed "defined" and '
                           . '"preg_match" conditions are not allowed. '
                           . 'Please check your spelling and try again.';
                    $this->_printUsage($error);
                    return;

                } elseif (count($patterns['std']) > 0) {
                    $this->options['ignore_constants_match']
                        = array('defined', $patterns['std']);
                } elseif (count($patterns['reg']) > 0) {
                    $this->options['ignore_constants_match']
                        = array('preg_match', $patterns['reg']);
                }
            } else {
                $error = 'Failed opening file "' . $icm
                       . '" (ignore-constants-match option). '
                       . 'Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // file-ext
        if ($args->isDefined('d') && $args->isDefined('fe')) {
            $fe = $args->getValue('fe');
            if (is_string($fe)) {
                $this->options['file_ext'] = explode(',', $fe);
            } else {
                $error = 'No valid file extensions provided "'
                       . '". Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }

        // file or directory options are minimum required to work
        if (!$args->isDefined('f')
            && !$args->isDefined('d')
            && !$args->isDefined('s')) {
            $error = 'ERROR: You must supply at least '
                   . 'one string, file or directory to process';
            $this->_printUsage($error);
            return;
        }

        if ($args->isDefined('r')) {
            $report = $args->getValue('r');
        } else {
            $report = 'text';
        }

        if ($args->isDefined('t')) {
            $defs = array('f' => 29, 'e' => 12, 'c' => 20);
            $tabs = $args->getValue('t');
            $tabs = explode(',', $tabs);
            for ($t = 0; $t < 3; $t++) {
                if (isset($tabs[$t])) {
                    if ($t == 0) {
                        $defs['f'] = (int)$tabs[$t];
                    } elseif ($t == 1) {
                        $defs['e'] = (int)$tabs[$t];
                    } else {
                        $defs['c'] = (int)$tabs[$t];
                    }
                }
            }
            $conf = array('colwidth' => $defs);
        } else {
            $conf = array();
        }
        $conf = array_merge($conf, array('args' => $args->getValues()));

        $compatInfo = new PHP_CompatInfo($report, $conf);

        // dir
        if ($args->isDefined('d')) {
            $d     = $args->getValue('d');
            $files = $compatInfo->parser->getFilelist($d, $this->options);
            if (count($files) == 0) {
                $error = 'No valid files into directory "'. $d
                       . '". Please check your spelling and try again.';
                $this->_printUsage($error);
                return;
            }
        }
        $compatInfo->parseData($this->dataSource, $this->options);
    }

    /**
     * Parse content of parameter files
     *
     * Parse content of parameter files used by switches
     * <ul>
     * <li>ignore-files
     * <li>ignore-dirs
     * <li>ignore-functions
     * <li>ignore-constants
     * <li>ignore-extensions
     * <li>ignore-functions-match
     * <li>ignore-extensions-match
     * <li>ignore-constants-match
     * </ul>
     *
     * @param string $fn          Parameter file name
     * @param bool   $withPattern TRUE if the file may contain regular expression
     *
     * @return array
     * @access private
     * @since  version 1.7.0b4 (2008-04-03)
     */
    function _parseParamFile($fn, $withPattern = false)
    {
        $lines    = file($fn);
        $patterns = array('std' => array(), 'reg' => array());
        foreach ($lines as $line) {
            $line = rtrim($line);  // remove line ending
            if (strlen($line) == 0) {
                continue;  // skip empty lines
            }
            if ($line{0} == ';') {
                continue;  // skip this pattern: consider as comment line
            }
            if ($line{0} == '=') {
                list($p, $s)       = explode('=', $line);
                $patterns['reg'][] = '/'.$s.'/';
            } else {
                if ($withPattern === true) {
                    $patterns['std'][] = '/'.$line.'/';
                } else {
                    $patterns['std'][] = $line;
                }
            }
        }
        return $patterns;
    }

    /**
     * Show full help information
     *
     * @param string $footer (optional) page footer content
     *
     * @return void
     * @access private
     * @since  version 0.8.0 (2004-04-22)
     */
    function _printUsage($footer = '')
    {
        $header = 'Usage: '
            . basename($_SERVER['SCRIPT_NAME']) . " [options]\n\n";
        echo Console_Getargs::getHelp($this->opts, $header,
            "\n$footer\n", 78, 2)."\n";
    }
}
?>