<div class="cf-turnstile"></div>
[mwform_hidden name="turnstile-check" value="1"]
/* ===============================================
# turnstile
=============================================== */
// ▼ ここだけ設定
if ( ! defined( 'MY_TURNSTILE_SITE_KEY' ) ) define( 'MY_TURNSTILE_SITE_KEY', 'ここにサイトキー' );
if ( ! defined( 'MY_TURNSTILE_SECRET_KEY' ) ) define( 'MY_TURNSTILE_SECRET_KEY', 'ここにシークレットキー' );
// 通行証(確認画面からの送信を通すための Cookie)の有効時間
define( 'MY_TURNSTILE_PASS_TTL', 10 * MINUTE_IN_SECONDS );
/* ============================================================
* 1. 画面側(ウィジェットの読み込み・送信ボタンの制御)
* ページに .cf-turnstile がある場合だけ Cloudflare のスクリプトを読み込む
* ============================================================ */
// 確認画面ではウィジェットを非表示(確認画面は入力画面と同じ本文を使い回すため)
add_action( 'wp_head', function () {
echo '<style>.mw_wp_form_confirm .cf-turnstile{display:none}</style>' . "\n";
} );
add_action( 'wp_footer', function () {
?>
<script>
(function () {
var SITE_KEY = <?php echo wp_json_encode( MY_TURNSTILE_SITE_KEY ); ?>;
function setDisabled(disabled) {
document.querySelectorAll('.mw_wp_form_input input[type="submit"], .mw_wp_form_input button[type="submit"]').forEach(function (btn) {
btn.disabled = disabled;
btn.style.opacity = disabled ? '0.5' : '';
btn.style.cursor = disabled ? 'not-allowed' : '';
});
}
window.myTurnstileOk = function () { setDisabled(false); };
window.myTurnstileNg = function () { setDisabled(true); };
function init() {
// ウィジェットがあるページだけ処理する(確認画面のものは除く)
var widgets = Array.prototype.filter.call(
document.querySelectorAll('.cf-turnstile'),
function (el) { return !el.closest('.mw_wp_form_confirm'); }
);
if (!widgets.length) { return; }
widgets.forEach(function (el) {
if (!el.getAttribute('data-sitekey')) { el.setAttribute('data-sitekey', SITE_KEY); }
el.setAttribute('data-callback', 'myTurnstileOk');
el.setAttribute('data-expired-callback', 'myTurnstileNg');
el.setAttribute('data-error-callback', 'myTurnstileNg');
});
setDisabled(true); // 検証が終わるまで送信ボタンを押せなくする
var s = document.createElement('script');
s.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
s.async = true;
s.defer = true;
document.head.appendChild(s);
}
// すでに読み込み完了していても動くようにする
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>
<?php
} );
/* ============================================================
* 2. サーバー側(Cloudflare への検証)
* ============================================================ */
function my_turnstile_verify( $token ) {
if ( empty( $token ) ) {
return false;
}
// トークンは1回しか使えないため、検証済みの結果を短時間キャッシュ
$cache_key = 'cf_ts_' . md5( $token );
if ( get_transient( $cache_key ) === 'ok' ) {
return true;
}
$res = wp_remote_post(
'https://challenges.cloudflare.com/turnstile/v0/siteverify',
array(
'timeout' => 10,
'body' => array(
'secret' => MY_TURNSTILE_SECRET_KEY,
'response' => $token,
'remoteip' => isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '',
),
)
);
if ( is_wp_error( $res ) ) {
return false;
}
$body = json_decode( wp_remote_retrieve_body( $res ), true );
if ( ! empty( $body['success'] ) ) {
set_transient( $cache_key, 'ok', 5 * MINUTE_IN_SECONDS );
return true;
}
return false;
}
// 通行証(署名付き Cookie)
function my_turnstile_set_pass() {
$time = time();
$value = $time . '.' . hash_hmac( 'sha256', (string) $time, MY_TURNSTILE_SECRET_KEY );
setcookie( 'my_ts_pass', $value, array(
'expires' => $time + MY_TURNSTILE_PASS_TTL,
'path' => '/',
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
) );
}
function my_turnstile_has_pass() {
if ( empty( $_COOKIE['my_ts_pass'] ) ) {
return false;
}
$parts = explode( '.', sanitize_text_field( wp_unslash( $_COOKIE['my_ts_pass'] ) ) );
if ( 2 !== count( $parts ) ) {
return false;
}
list( $time, $sig ) = $parts;
if ( ! ctype_digit( $time ) || ( time() - (int) $time ) > MY_TURNSTILE_PASS_TTL ) {
return false;
}
return hash_equals( hash_hmac( 'sha256', $time, MY_TURNSTILE_SECRET_KEY ), $sig );
}
// hidden の turnstile-check が付いた送信を検証し、失敗なら MW WP Form に渡す前に止める
add_action( 'init', function () {
if ( 'POST' !== $_SERVER['REQUEST_METHOD'] || ! isset( $_POST['turnstile-check'] ) ) {
return;
}
$token = isset( $_POST['cf-turnstile-response'] )
? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) )
: '';
$ok = false;
$method = 'なし';
if ( $token ) {
// 入力画面からの送信: トークンを検証し、成功したら通行証を発行
$ok = my_turnstile_verify( $token );
$method = 'token';
if ( $ok ) {
my_turnstile_set_pass();
}
} elseif ( my_turnstile_has_pass() ) {
// 確認画面からの送信: 通行証があればOK
$ok = true;
$method = 'cookie';
}
// 動作確認用ログ(WP_DEBUG_LOG 有効時のみ)
if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) {
error_log( '[turnstile] via=' . $method . ' result=' . ( $ok ? 'OK' : 'NG' ) );
}
if ( ! $ok ) {
wp_die(
'スパム対策の認証に失敗しました。ページを戻って、もう一度お試しください。',
'認証エラー',
array( 'response' => 403, 'back_link' => true )
);
}
}, 1 );
コメント