Яндекс SmartCaptcha + Contact Form 7 (невидимая)

Тут каптча вообще не торчит в форме: человек жмёт отправку, и проверка крутится в фоне.
Ниже показываю связку, которую сам ставлю: блок в functions.php и тег [yandex_captcha] в CF7.

Коротко по режимам.

Про обычную (visible) версию у меня отдельная статья.
Здесь разбираю только invisible: никаких чекбоксов, токен получаю через smartCaptcha.execute().

Что нужно для старта

  • Активный Contact Form 7

  • Ключи Yandex SmartCaptcha: sitekey (клиентский) и secret (серверный)

Куда вставлять

Правлю файл темы functions.php.
Если старый блок интеграции уже есть, просто меняю его целиком на код ниже.

Полный код (invisible)

php
// Yandex Smart Captcha для Contact Form 7 - тег [yandex_captcha]
if (!defined('YANDEX_SMARTCAPTCHA_SITEKEY')) {
    define('YANDEX_SMARTCAPTCHA_SITEKEY', 'ваш_клиентский_ключ');
}
if (!defined('YANDEX_SMARTCAPTCHA_SECRET')) {
    define('YANDEX_SMARTCAPTCHA_SECRET', 'ваш_серверный_ключ');
}

add_action('wpcf7_before_send_mail', 'yandex_smart_captcha_verify', 10, 1);
function yandex_smart_captcha_verify($contact_form) {
    $submission = WPCF7_Submission::get_instance();
    if (!$submission) {
        return;
    }

    $smart_captcha_token = isset($_POST['smart-token']) ? sanitize_text_field(wp_unslash($_POST['smart-token'])) : '';

    if ($smart_captcha_token === '') {
        $submission->set_status('spam');
        $submission->set_response('Пройдите проверку каптчи');
        return;
    }

    // POST https://captcha-api.yandex.ru/validate (без /v1/ - иначе 404)
    $url = 'https://captcha-api.yandex.ru/validate';

    $response = wp_remote_post($url, [
        'timeout' => 10,
        'body' => [
            'secret' => YANDEX_SMARTCAPTCHA_SECRET,
            'token' => $smart_captcha_token,
            'ip' => isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '',
        ],
    ]);

    if (is_wp_error($response) || (int) wp_remote_retrieve_response_code($response) !== 200) {
        $submission->set_status('spam');
        $submission->set_response('Ошибка проверки каптчи');
        return;
    }

    $body = json_decode(wp_remote_retrieve_body($response), true);

    if (!is_array($body) || !isset($body['status']) || $body['status'] !== 'ok') {
        $error_message = 'Каптча не пройдена';
        if (is_array($body) && !empty($body['message'])) {
            $error_message = 'SmartCaptcha: ' . sanitize_text_field($body['message']);
        }
        $submission->set_status('spam');
        $submission->set_response($error_message);
    }
}

add_action('wpcf7_init', 'kpv_add_yandex_captcha_cf7_tag');
function kpv_add_yandex_captcha_cf7_tag() {
    if (function_exists('wpcf7_add_form_tag')) {
        wpcf7_add_form_tag('yandex_captcha', 'kpv_yandex_captcha_tag_handler', ['nameless' => true]);
    } elseif (function_exists('wpcf7_add_shortcode')) {
        wpcf7_add_shortcode('yandex_captcha', 'kpv_yandex_captcha_tag_handler');
    }
}

function kpv_yandex_captcha_tag_handler($tag) {
    $uid = 'ysc-token-' . wp_generate_password(12, false, false);
    $sitekey = YANDEX_SMARTCAPTCHA_SITEKEY;

    return sprintf(
        '<div class="form__captcha"><span class="wpcf7-form-control-wrap yandex-captcha-wrap" data-token-field="%1$s">'
        . '<div class="smart-captcha" data-sitekey="%2$s" data-token-field="%1$s"></div>'
        . '<input type="hidden" name="smart-token" id="%1$s" value="" />'
        . '</span></div>',
        esc_attr($uid),
        esc_attr($sitekey)
    );
}

add_action('wp_enqueue_scripts', 'kpv_yandex_captcha_inline_style', 20);
function kpv_yandex_captcha_inline_style() {
    wp_add_inline_style('main', '.form__captcha{margin-bottom:20px;}');
}

add_action('wp_footer', 'kpv_yandex_captcha_scripts', 20);
function kpv_yandex_captcha_scripts() {
    if (is_admin()) {
        return;
    }
    if (!defined('WPCF7_VERSION')) {
        return;
    }
    ?>
    <script src="https://smartcaptcha.yandexcloud.net/captcha.js" defer></script>
    <script>
    (function () {
        function executeCaptcha(form, captchaEl, tokenInput) {
            if (!form || !captchaEl || !window.smartCaptcha || typeof window.smartCaptcha.execute !== 'function') {
                return;
            }
            var widgetId = captchaEl.getAttribute('data-widget-id');
            if (!widgetId) {
                return;
            }
            form.setAttribute('data-ysc-processing', '1');
            if (tokenInput) {
                tokenInput.value = '';
            }
            window.smartCaptcha.execute(isNaN(Number(widgetId)) ? widgetId : Number(widgetId));
        }

        function bindFormSubmit(form) {
            if (!form || form.getAttribute('data-ysc-submit-bound')) {
                return;
            }
            form.setAttribute('data-ysc-submit-bound', '1');

            form.addEventListener('submit', function (event) {
                if (form.getAttribute('data-ysc-captcha-passed') === '1') {
                    // Разрешаем один submit после успешной каптчи.
                    form.removeAttribute('data-ysc-captcha-passed');
                    return;
                }

                var tokenInput = form.querySelector('input[name="smart-token"]');
                var captchaEl = form.querySelector('.smart-captcha');
                if (!captchaEl) {
                    event.preventDefault();
                    return;
                }

                event.preventDefault();
                if (form.getAttribute('data-ysc-processing') === '1') {
                    return;
                }
                if (!captchaEl.getAttribute('data-widget-id')) {
                    // Виджет мог не успеть дорендериться при первом клике.
                    setTimeout(function () {
                        executeCaptcha(form, captchaEl, tokenInput);
                    }, 150);
                    return;
                }
                executeCaptcha(form, captchaEl, tokenInput);
            });
        }

        document.addEventListener('wpcf7submit', function (event) {
            var form = event.target;
            if (!form || !form.matches || !form.matches('form.wpcf7-form')) {
                return;
            }
            form.removeAttribute('data-ysc-processing');
            form.removeAttribute('data-ysc-captcha-passed');
            var tokenInput = form.querySelector('input[name="smart-token"]');
            if (tokenInput) {
                tokenInput.value = '';
            }
            var captchaEl = form.querySelector('.smart-captcha[data-widget-id]');
            var widgetId = captchaEl ? captchaEl.getAttribute('data-widget-id') : '';
            if (widgetId && window.smartCaptcha && typeof window.smartCaptcha.reset === 'function') {
                window.smartCaptcha.reset(isNaN(Number(widgetId)) ? widgetId : Number(widgetId));
            }
        });

        function renderAll() {
            if (!window.smartCaptcha || typeof window.smartCaptcha.render !== 'function') {
                return false;
            }
            document.querySelectorAll('.smart-captcha[data-sitekey]').forEach(function (el) {
                if (el.getAttribute('data-ysc-rendered')) {
                    return;
                }
                var tokenId = el.getAttribute('data-token-field');
                var input = tokenId ? document.getElementById(tokenId) : null;
                var form = el.closest('form');
                var widgetId = window.smartCaptcha.render(el, {
                    sitekey: el.getAttribute('data-sitekey'),
                    hl: 'ru',
                    invisible: true,
                    callback: function (token) {
                        if (input) {
                            input.value = token || '';
                        }
                        if (token && form) {
                            form.removeAttribute('data-ysc-processing');
                            form.setAttribute('data-ysc-captcha-passed', '1');
                            if (typeof form.requestSubmit === 'function') {
                                form.requestSubmit();
                            } else {
                                form.submit();
                            }
                        }
                    },
                    success: function (token) {
                        if (input) {
                            input.value = token || '';
                        }
                        if (token && form) {
                            form.removeAttribute('data-ysc-processing');
                            form.setAttribute('data-ysc-captcha-passed', '1');
                            if (typeof form.requestSubmit === 'function') {
                                form.requestSubmit();
                            } else {
                                form.submit();
                            }
                        }
                    },
                    'token-expired': function () {
                        if (input) {
                            input.value = '';
                        }
                        if (form) {
                            form.removeAttribute('data-ysc-processing');
                        }
                    },
                    'network-error': function () {
                        if (input) {
                            input.value = '';
                        }
                        if (form) {
                            form.removeAttribute('data-ysc-processing');
                        }
                    }
                });
                el.setAttribute('data-widget-id', String(widgetId));
                el.setAttribute('data-ysc-rendered', '1');
                bindFormSubmit(form);
            });
            return true;
        }
        function tick() {
            if (renderAll()) {
                return;
            }
            setTimeout(tick, 50);
        }
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', tick);
        } else {
            tick();
        }
    })();
    </script>
    <?php
}

Тег в форме CF7

В шаблон формы Contact Form 7 я добавляю такой тег:

text
[yandex_captcha]

Как это устроено

  1. Жмут «Отправить»

    Я на секунду останавливаю отправку, пока нет токена.

  2. Крутится проверка

    В фоне вызывается smartCaptcha.execute(…).

  3. Форма уходит

    Токен пришёл — отправляю форму ещё раз уже «по-честному».

  4. Всё сбрасываю

    После ответа CF7 чищу токен, чтобы следующая отправка снова прошла через каптчу.

Шаг проверки Что смотреть
1 Открываю страницу формы и делаю Ctrl+F5
2 Проверяю, что при отправке каптча отрабатывает и письмо уходит
3 Делаю вторую отправку подряд и смотрю, что нет ложного spam

Безопасность ключей

Ключи лучше держать в wp-config.php, а не в теме:

php
define('YANDEX_SMARTCAPTCHA_SITEKEY', 'ваш_клиентский_ключ');
define('YANDEX_SMARTCAPTCHA_SECRET', 'ваш_серверный_ключ');
Важно не промахнуться с URL.

Endpoint проверки — https://captcha-api.yandex.ru/validate
(без /v1/, иначе словите 404). В коде ниже это уже учтено.

← К списку статей

Поделиться

Комментарии

Пока нет комментариев — можете написать первый.

Оставить комментарий

Войти с помощью

или оставьте комментарий как гость

Отправка защищена сервисом Yandex SmartCaptcha. Подробности — в политике конфиденциальности.