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

namespace PHPCompatibility\Sniffs\ParameterValues;

use PHPCompatibility\AbstractFunctionCallParameterSniff;
use PHPCompatibility\Helpers\ScannedCode;
use PHPCSUtils\Tokens\Collections;
use PHPCSUtils\Utils\PassedParameters;
use PHP_CodeSniffer\Files\File;

/**
 * Detect function calls to ldap_connect() using deprecated function signatures.
 *
 * `ldap_connect()` historically supports three signatures:
 * - `ldap_connect(?string $uri = null)`
 * - `ldap_connect(?string $host = null, int $port = 389)`
 * - `ldap_connect(?string $uri, int $port, string $wallet, string $password, int $auth_mode)`
 *
 * The two parameter - $host, $port - signature is deprecated since PHP 8.3 and support will be removed in PHP 9.0.
 *
 * The three+ parameter - $uri, $port, $wallet, $password, $auth_mode - signature is deprecated since PHP 8.4
 * and support will be removed in PHP 9.0.
 *
 * PHP version 8.3
 * PHP version 8.4
 * PHP version 9.0
 *
 * {@internal Normally this would be handled via the RemovedFunctionParameters sniff, but this
 * function has *three* distinct signatures and the signatures are being deprecated in reverse order,
 * i.e. the 2-param signature is being deprecated in PHP 8.3, while the 3+ param signature is being
 * deprecated in PHP 8.4.
 * For that reason, the RemovedFunctionParameters sniff is not suitable to handle this.}
 *
 * @link https://wiki.php.net/rfc/deprecations_php_8_3#deprecate_calling_ldap_connect_with_2_parameters
 * @link https://wiki.php.net/rfc/deprecate_functions_with_overloaded_signatures#ldap_connect
 * @link https://www.php.net/ldap_connect
 *
 * @since 10.0.0
 */
final class RemovedLdapConnectSignaturesSniff extends AbstractFunctionCallParameterSniff
{

    /**
     * Functions to check for.
     *
     * @since 10.0.0
     *
     * @var array<string, bool>
     */
    protected $targetFunctions = [
        'ldap_connect' => true,
    ];

    /**
     * Do a version check to determine if this sniff needs to run at all.
     *
     * @since 10.0.0
     *
     * @return bool
     */
    protected function bowOutEarly()
    {
        return (ScannedCode::shouldRunOnOrAbove('8.3') === false);
    }

    /**
     * Process the parameters of a matched function.
     *
     * @since 10.0.0
     *
     * @param \PHP_CodeSniffer\Files\File $phpcsFile    The file being scanned.
     * @param int                         $stackPtr     The position of the current token in the stack.
     * @param string                      $functionName The token content (function name) which was matched.
     * @param array                       $parameters   Array with information about the parameters.
     *
     * @return int|void Integer stack pointer to skip forward or void to continue
     *                  normal file processing.
     */
    public function processParameters(File $phpcsFile, $stackPtr, $functionName, $parameters)
    {
        if (ScannedCode::shouldRunOnOrAbove('8.4') === true) {
            /*
             * On PHP 8.4, the 3+ param signature is deprecated.
             */
            $walletParam   = PassedParameters::getParameterFromStack($parameters, 3, 'wallet');
            $passwordParam = PassedParameters::getParameterFromStack($parameters, 4, 'password');
            $authmodeParam = PassedParameters::getParameterFromStack($parameters, 5, 'auth_mode');

            if ($walletParam !== false || $passwordParam !== false || $authmodeParam !== false) {
                // Found a 3+ param signature.
                $phpcsFile->addWarning(
                    'Calling ldap_connect() with three or more parameters is deprecated since PHP 8.4. Use ldap_connect_wallet() (PHP 8.3+) instead.',
                    $stackPtr,
                    'DeprecatedThreePlusParamSignature'
                );

                /*
                 * If this notice has been thrown, we shouldn't throw the PHP 8.3 related notice for 3+ param
                 * signatures as the "do this instead" advise conflicts.
                 */
                return;
            }
        }

        /*
         * On PHP 8.3, the 2-param signature is deprecated, including passing a non-null $port value
         * when using the 3+ param signature.
         */
        $uriParam  = PassedParameters::getParameterFromStack($parameters, 1, ['uri', 'host']);
        $portParam = PassedParameters::getParameterFromStack($parameters, 2, 'port');

        if ($uriParam === false || $portParam === false) {
            // Not the deprecated two param signature.
            return;
        }

        // Check for the, still supported, 3+ param signature and if found, check the second param is `null`.
        if (\count($parameters) > 2) {
            $cleanPort = \strtolower($portParam['clean']);
            if ($cleanPort === 'null' || $cleanPort === '\null') {
                // This is the correct value to still use the 3+ param signature. Bow out.
                return;
            }

            $find               = Collections::namespacedNameTokens();
            $find[\T_VARIABLE]  = \T_VARIABLE;
            $hasVariableContent = $phpcsFile->findNext($find, $portParam['start'], ($portParam['end'] + 1));
            if ($hasVariableContent !== false) {
                // We don't have access to the contents of the parameter. Bow out.
                return;
            }

            // Found a 3+ param signature, which doesn't pass `null` as $port.
            $phpcsFile->addWarning(
                'Calling ldap_connect() with a $port which is not `null` is deprecated since PHP 8.3. Call ldap_connect() with an LDAP-URI as the $uri parameter and pass `null` for $port instead.',
                $stackPtr,
                'DeprecatedPortNotNull'
            );
            return;
        }

        // Found the deprecated 2-param signature.
        $phpcsFile->addWarning(
            'Calling ldap_connect() with two parameters is deprecated since PHP 8.3. Call ldap_connect() with an LDAP-URI as the first (and only) parameter instead.',
            $stackPtr,
            'DeprecatedTwoParamSignature'
        );
    }
}
