<?php
/**
 * PHPCompatibility, an external standard for PHP_CodeSniffer.
 *
 * @package   PHPCompatibility
 * @copyright 2012-2019 PHPCompatibility Contributors
 * @license   https://opensource.org/licenses/LGPL-3.0 LGPL3
 * @link      https://github.com/PHPCompatibility/PHPCompatibility
 */

namespace PHPCompatibility\Sniffs\Upgrade;

use PHPCompatibility\Sniff;
use PHPCompatibility\PHPCSHelper;
use PHP_CodeSniffer_File as File;

/**
 * Add a notification for users of low PHP versions.
 *
 * Originally PHPCompatibility supported PHP 5.1 and higher.
 * As of PHPCompatibility 8.0.0, support for PHP < 5.3 has been dropped.
 *
 * The intention is to drop support for PHP 5.3 in the (near) future.
 *
 * This sniff adds an explicit error/warning for users of the standard
 * using a PHP version below the recommended version.
 *
 * @link https://github.com/PHPCompatibility/PHPCompatibility/issues/835
 *
 * @since 9.3.0
 */
class LowPHPSniff extends Sniff
{
    /**
     * The minimum supported PHP version.
     *
     * Users on PHP versions below this will see an ERROR message.
     *
     * @since 9.3.0
     *
     * @var string
     */
    const MIN_SUPPORTED_VERSION = '5.3';

    /**
     * The minimum recommended PHP version.
     *
     * Users on PHP versions below this will see a WARNING.
     *
     * @since 9.3.0
     *
     * @var string
     */
    const MIN_RECOMMENDED_VERSION = '5.4';

    /**
     * Keep track of whether this sniff needs to actually run.
     *
     * This will be set to `false` when either a high enough PHP
     * version is detected or once the error/warning has been thrown,
     * to make sure that the notice will only be thrown once per run.
     *
     * @since 9.3.0
     *
     * @var bool
     */
    private $examine = true;


    /**
     * Returns an array of tokens this test wants to listen for.
     *
     * @since 9.3.0
     *
     * @return array
     */
    public function register()
    {
        return array(
            \T_OPEN_TAG,
        );
    }

    /**
     * Processes this test, when one of its tokens is encountered.
     *
     * @since 9.3.0
     *
     * @param \PHP_CodeSniffer_File $phpcsFile The file being scanned.
     * @param int                   $stackPtr  The position of the current token in the
     *                                         stack passed in $tokens.
     *
     * @return int|void Integer stack pointer to skip forward or void to continue
     *                  normal file processing.
     */
    public function process(File $phpcsFile, $stackPtr)
    {
        // Don't do anything if the warning has already been thrown or is not necessary.
        if ($this->examine === false) {
            return ($phpcsFile->numTokens + 1);
        }

        $phpVersion = phpversion();

        // Don't do anything if the PHPCS version used is above the minimum recommended version.
        if (version_compare($phpVersion, self::MIN_RECOMMENDED_VERSION, '>=')) {
            $this->examine = false;
            return ($phpcsFile->numTokens + 1);
        }

        if (version_compare($phpVersion, self::MIN_SUPPORTED_VERSION, '<')) {
            $isError      = true;
            $message      = 'IMPORTANT: Please be advised that the minimum PHP version the PHPCompatibility standard supports is %s. You are currently using PHP %s. Please upgrade your PHP installation. The recommended version of PHP for PHPCompatibility is %s or higher.';
            $errorCode    = 'Unsupported_' . $this->stringToErrorCode(self::MIN_SUPPORTED_VERSION);
            $replacements = array(
                self::MIN_SUPPORTED_VERSION,
                $phpVersion,
                self::MIN_RECOMMENDED_VERSION,
                $errorCode,
            );
        } else {
            $isError      = false;
            $message      = 'IMPORTANT: Please be advised that for the most reliable PHPCompatibility results, PHP %s or higher should be used. Support for lower versions will be dropped in the foreseeable future. You are currently using PHP %s. Please upgrade your PHP installation to version %s or higher.';
            $errorCode    = 'BelowRecommended_' . $this->stringToErrorCode(self::MIN_RECOMMENDED_VERSION);
            $replacements = array(
                self::MIN_RECOMMENDED_VERSION,
                $phpVersion,
                self::MIN_RECOMMENDED_VERSION,
                $errorCode,
            );
        }

        /*
         * Figure out the report width to determine how long the delimiter lines should be.
         *
         * This is not an exact calculation as there are a number of unknowns at the time the
         * notice is thrown (whether there are other notices for the file, whether those are
         * warnings or errors, whether there are auto-fixable issues etc).
         *
         * In other words, this is just an approximation to get a reasonably stable and
         * readable message layout format.
         *
         * {@internal
         * PHPCS has had some changes as to how the messages display over the years.
         * Most significantly in 2.4.0 it was attempted to solve an issue with messages
         * containing new lines. Unfortunately, that solution is buggy.
         * An improved version has been pulled upstream and will hopefully make it
         * into PHPCS 3.3.1/3.4.0.
         *
         * Anyway, this means that instead of new lines, delimiter lines will be used to improved
         * the readability of the (long) message.
         *
         * Also, as of PHPCS 2.2.0, the report width when using the `-s` option is 8 wider than
         * it should be. A patch for that is included in the same upstream PR.
         *
         * If/when the upstream PR has been merged and the minimum supported/recommended version
         * of PHPCompatibility would go beyond that, the below code should be adjusted.}
         */
        $reportWidth = PHPCSHelper::getCommandLineData($phpcsFile, 'reportWidth');
        if (empty($reportWidth)) {
            $reportWidth = 80;
        }
        $showSources = PHPCSHelper::getCommandLineData($phpcsFile, 'showSources');
        if ($showSources === true) {
            $reportWidth += 6;
        }

        $messageWidth  = ($reportWidth - 15); // 15 is length of " # | WARNING | ".
        $delimiterLine = str_repeat('-', ($messageWidth));
        $disableNotice = 'To disable this notice, add --exclude=PHPCompatibility.Upgrade.LowPHP to your command or add <exclude name="PHPCompatibility.Upgrade.LowPHP.%s"/> to your custom ruleset. ';
        $thankYou      = 'Thank you for using PHPCompatibility!';

        $message .= ' ' . $delimiterLine;
        $message .= ' ' . $disableNotice;
        $message .= ' ' . $delimiterLine;
        $message .= ' ' . $thankYou;

        $this->addMessage($phpcsFile, $message, 0, $isError, $errorCode, $replacements);

        $this->examine = false;
    }
}