Тут каптча вообще не торчит в форме: человек жмёт отправку, и проверка крутится в фоне.
Ниже показываю связку, которую сам ставлю: блок в functions.php и тег [yandex_captcha] в CF7.
Про обычную (visible) версию у меня отдельная статья.
Здесь разбираю только invisible: никаких чекбоксов, токен получаю через smartCaptcha.execute().
Что нужно для старта
Активный Contact Form 7
Ключи Yandex SmartCaptcha: sitekey (клиентский) и secret (серверный)
Куда вставлять
Правлю файл темы functions.php.
Если старый блок интеграции уже есть, просто меняю его целиком на код ниже.
Полный код (invisible)
// 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 я добавляю такой тег:
[yandex_captcha]
Как это устроено
- Жмут «Отправить»
Я на секунду останавливаю отправку, пока нет токена.
- Крутится проверка
В фоне вызывается smartCaptcha.execute(…).
- Форма уходит
Токен пришёл — отправляю форму ещё раз уже «по-честному».
- Всё сбрасываю
После ответа CF7 чищу токен, чтобы следующая отправка снова прошла через каптчу.
| Шаг проверки | Что смотреть |
|---|---|
| 1 | Открываю страницу формы и делаю Ctrl+F5 |
| 2 | Проверяю, что при отправке каптча отрабатывает и письмо уходит |
| 3 | Делаю вторую отправку подряд и смотрю, что нет ложного spam |
Безопасность ключей
Ключи лучше держать в wp-config.php, а не в теме:
define('YANDEX_SMARTCAPTCHA_SITEKEY', 'ваш_клиентский_ключ');
define('YANDEX_SMARTCAPTCHA_SECRET', 'ваш_серверный_ключ');
Endpoint проверки — https://captcha-api.yandex.ru/validate
(без /v1/, иначе словите 404). В коде ниже это уже учтено.
Комментарии
Пока нет комментариев — можете написать первый.