Contact Form 7で独自にreCAPTCHA v3を用いる

以前挫折したものの今回とりあえず動くところまできたのでメモとして。いろいろ問題があるかもしれませんので留意ください。

[追記:2020.12.15]
スパムが抜けやすいようで失敗の可能性があります。のちほど再度詳細確認予定。reCAPTCHAの管理画面ではスパムも0.9と判定されていました。

前提

  • WordPressでプラグインのContact Form 7を使用
  • Contact Form 7の機能を利用せず、独自にreCAPTCHA v3を用いる
  • reCAPTCHAの管理画面からSite keyとSecret keyの値を控えておく

まだそこまで進んでいませんが最終目的はreCAPTCHA関連のJSを遅延読み込みさせたいというもので、そのためにはプラグインからreCAPTCHAを切り分ける必要がありました(そのままでも実現する方法があったかもしれませんが見つからず)。

今回は以下のページの他にプラグインのInvisible reCAPTCHAの中を見ていろいろ参考にさせていただきました。

サンプル

  1. Contact Form 7のフォーム作成画面
  2. header.php
  3. page.php
  4. functions.php

今回は上記の3ファイルと1画面で作業を行いますが、コンタクトフォームの画面以外は適宜という感じで、テーマや目的に応じて検討すれば良いかなと思います。

Contact Form 7のフォーム作成画面


<input type="hidden" name="recaptchaToken" id="recaptchaToken" />

上記を追加して保存します。nameidが同じ文字列であれば別に他のワードでも問題ありません。

header.php


<script src="https://www.google.com/recaptcha/api.js?render=[Site key]"></script>

上記を記載しJSを読み込ませます。[Site key]のところはそれぞれreCAPTCHAの管理画面から値を探して入力します。[ ]の括弧は不要です。

page.php


<script>
  document.querySelector('.wpcf7-form').addEventListener('submit', onClick);

  function onClick(e) {
    e.preventDefault();
    grecaptcha.ready(function() {
      grecaptcha.execute('[Site key]', {
        action: 'submit'
      }).then(function(token) {
        var recaptchaToken = document.getElementById('recaptchaToken');
        recaptchaToken.value = token;
        document.querySelector('.wpcf7-form').submit();
      });
    });
  }
</script>

前出のサイトを参考にして上記のように記載します。上記以外の書き方でも問題ありません。フォームを特定するクラスやID名はそれぞれ確認が必要かもしれません。document.getElementById('recaptchaToken')の部分はContact Form 7のフォーム作成画面で入れたhiddenのところのidを使います。

functions.php


add_filter('wpcf7_spam', 'MyReCaptchaTokenValid', 9);
function MyReCaptchaTokenValid()
{
    if (empty($_POST['recaptchaToken'])) {
        return true;
    }

    $secretKey = 'Secret keyを記載';
    $response = wp_remote_retrieve_body(wp_remote_get(add_query_arg([
        'secret' => $secretKey,
        'response' => $_POST['recaptchaToken'],
    ], 'https://www.google.com/recaptcha/api/siteverify')));

    if (empty($response) || !($json = json_decode($response)) || empty($json->success)) {
        return true;
    }
    return false;
}

今回難儀したのがここです。Invisible reCAPTCHAの記述を参考にしてどうにか動かせました。

ただのPHP製フォームであれば送信用のPHPに$_POST['recaptchaToken']などの値を書けば良いのですが、WPのプラグインであるContact Form 7では書く場所がありません。プラグイン内のファイルに直接は論外ですし。

そこでInvisible reCAPTCHAはどうやって実現しているのかと思って調べて、wpcf7_spamにフックさせていたのを確認しました。Contact Form 7の中身もほんの少しは見たのですが全く気づきませんでした…。

なおwpcf7_spamは真偽値を返す必要があり、以下のように設定します。最初は逆だと思い込んでいて想定する動作にならずに悩んでしまいました。

  • 不合格ならtrueを返す
  • 合格ならfalseを返す

今回はInvisible reCAPTCHAの判定方法をそのまま使っているだけなので、必要に応じて他の形に変えてもよいかなとは思います。

スコアの利用


if (empty($response) || !($json = json_decode($response)) || $json->score <= 0.7) {
  return true;
}
return false;

前項のサンプルを上記のように$json->scoreを使う形に変更すればスコアによる判定を行うことができます。現状では緩いという場合にはスコアを下げることで厳しくできます。

「1に近いほど人間の可能性が高い。0に近いほどボットの可能性が高い。」という範囲設定なので、中心の0.5から考えれば以下のようになります。

  • 0.5から0.7にすると、対象の投稿をボットと判定する可能性が高まり厳しい判定となる
  • 0.5から0.3にすると、対象の投稿をボットと判定する可能性が低下し緩い判定となる

手動で試すとスコアはおそらく0.9ばかりがでてきて不安になるかもしれませんが、この件に関しては以下のスレッドが参考になるかもしれません。

V2の今後

reCAPTCHA v2 is not going away! We will continue to fully support and improve security and usability for v2.

上記のように書かれており、V2は今後もずっと残ってしっかりメンテナンスも続く模様です。もちろん何を書いていようがいきなり中断されることはあるでしょうが、そういう意味ではV3もいつまで続くのかという点で違いもないですし、V2の使用期限は気にしないで良いように思います。

すり抜けてくるスパム投稿のスコアを通知する

すり抜けてくるスパム投稿ごとのスコアが知りたいと考え、ここまでのコードを以下のように改造し、一応の動作を確認しました。
自動返信機能で問い合わせした方にスコアを記載するわけにはいかないので、管理者メールにのみ追加する形です。


$myRecaptchaScore = 0;
add_filter('wpcf7_spam', 'MyReCaptchaTokenValid', 9);
function MyReCaptchaTokenValid()
{
    if (empty($_POST['recaptchaToken'])) {
        return true;
    }

    $secretKey = 'Secret keyを記載';
    $response = wp_remote_retrieve_body(wp_remote_get(add_query_arg([
        'secret' => $secretKey,
        'response' => $_POST['recaptchaToken'],
    ], 'https://www.google.com/recaptcha/api/siteverify')));

    if (empty($response)) {
        return true;
    }

    $json = json_decode($response);
    global $myRecaptchaScore;
    $myRecaptchaScore = $json->score;

    if ($myRecaptchaScore <= 0.8) {
        // reCAPTCHA不合格の場合
        return true;
    }
    // reCAPTCH合格の場合
    return false;
}

add_filter('wpcf7_before_send_mail', 'my_wpcf7_before_send_mail', 1, 1);
function my_wpcf7_before_send_mail($cf7)
{
    // メール送信に関する情報を取得
    $mail = $cf7->prop('mail');
    // 管理者用メールアドレスと同じなら実行
    if ($mail['recipient'] = get_option('admin_email')) {
        if ($submission = WPCF7_Submission::get_instance()) {
            $posted_data = $submission->get_posted_data();
            global $myRecaptchaScore;
            $mail['body'] .= "\n\n\nScore:" . $myRecaptchaScore;
        }
    }
    // 情報を再セット
    $cf7->set_properties(['mail' => $mail]);

    return $cf7;
}

メールに情報を追加する部分は以下を参考にしています。

2つ目のページに以下のような$cf7->prop('mail')['body']を利用して追加する方法が書かれています。これがわからなければ1つ目のページのようにプラグインのファイルを触る必要があったかもしれません。


add_action( 'wpcf7_before_send_mail', 'wpcf7_add_text_to_mail_body' );
function wpcf7_add_text_to_mail_body( $contact_form ) {

    //Get the form ID
    $form_id = $contact_form->id();

    //Do something specifically for form with the ID "123"
    if( $form_id == 123 ) {
        $submission = WPCF7_Submission::get_instance();
        $posted_data = $submission->get_posted_data();
        $values_list = $posted_data['valsitems'];
        $values_str = implode(", ", $values_list);

        // get mail property
        $mail = $contact_form->prop( 'mail' ); // returns array 

        // add content to email body
        $mail['body'] .= 'INDUSTRIES SELECTED';
        $mail['body'] .= $values_list;

        // set mail property with changed value(s)
        $contact_form->set_properties( array( 'mail' => $mail ) );
    }
}

なお上記は複数あるContact Form 7のフォームの中から特定のフォームを選んで処理を施すためのサンプルコードです。今回は必要ありませんでしたが、$form_id = $contact_form->id();IDを取得すればより絞った記述にできるのだと知れました。

結び

一応試しては見たものの、便利で高性能なプラグインや機能がすでにあるのですから、JSファイルの遅延読み込み程度のために独自実装を行うのはリスクの方が高いかなと感じました。仕組みを理解して実行するなら別ですが。

ただしスコアを操作できるため、その点ではInvisible reCAPTCHAを使用時には独自実装の方が良い可能性はありるかもしれません。

Contact Form 7に関してはスコア変更のために専用フックが用意されています。デフォルトは0.5だそうですが、もろもろの詳細は以下にて。

reCAPTCHAの遅延読み込みに関して

上記のページも参考にしつつreCAPTCHAの遅延読み込み(フォームが画面に表示されたあたりでreCAPTCHA用JSを読み込ませる)を再検討しましたが、少なくともV3でも精度を落とすだけのように思えました。

V3はreCAPTCHA用JSでカーソルの動きなども見ているようなので、遅延読み込みすればどうなるかは明白です。

V2は少々流れ自体を把握しかねていますが、ボットか否かのチェックボックスがフォーム内での操作だけを見ているなら遅延読み込みしても問題ないかもしれません。

補記

Googleの計測ツールはわかりやすく点数を出すので飛びつきやすいものの、何でもかんでも遅延読み込みさせたいというのは無茶が過ぎる可能性があるように思います。

あくまで仮定のたとえ話ですが。
あるユーザーは通信費用削減のためにWi-Fi環境下でサイトを複数開き、スクロールせずにそのまま表示の完了を待ちます。読み込みが終わったのを確認し、その場所を離れて歩きながらサイトをみようとスクロールします。するとJSや画像や動画やその他もろもろが大量にダウンロードされ始め、当人が意図しない形で通信費用が嵩みます。

非同期読み込みやpreloadならこの問題は起こりませんが、遅延読み込みではこういった問題が起こり得るのではないかとし少し懸念しています。

その意味では「サイト内の全てのデータを受信する」的なボタンの需要はあるのかもしれません。全てをJSで制御している場合であれば比較的簡易に対応可能でしょうし。

3人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄