WordPress:Fetch(Ajax)でお問い合わせフォーム(プラグインなし)

WordPress:Fetch(Ajax)でお問い合わせフォーム(プラグインなし)

プラグインを使わずにお問い合わせフォームをつくります。画面遷移せずに完了するように fetch API を使います。jQuery でいうところの ajaxメソッドです。画面遷移しないことの利点は固定ページが1ページですむことくらいかと思いますが、確か Contact Form 7 は送信だけではなくバリデーションにも Ajax を使っていたと思います。

01WordPress の Fetch( Ajax, 非同期通信)

WordPress の非同期通信はすでに利用した経験があり、その際の記事が以下にもあります。

WordPress の非同期通信の基本的な流れは次のとおりです。

  • ブラウザの Javascript からサーバーの /wp-admin/admin-ajax.php にリクエスト(データ)を送る
  • その際、サーバー内で処理するための action名を action 変数(パラメータ)に入れて送る
  • サーバーでは wp_ajax_[action名] と wp_ajax_nopriv_[action名] のアクションフックで処理関数を指定する
  • 処理関数の戻り値は echo で返す
  • ブラウザには promise が返るので、結果 response オブジェクトの json, formData, blob, text メソッドを使って戻り値を処理する

という、言葉にしますとややこしいのですが、やってみると簡単なものです。

02ブラウザ側の処理(クライアントサイド)

お問い合わせフォームですので、入力されたフォームデータを fetch を使いサーバーに送ります。

フォームの作成

まず、page-enquiry.php などのファイル名で固定ページ用のフォームを作ります。

<div>
    <p id="message"></p>
    <form name="enquiry">
        <label>メールアドレス</label>
        <input type="email" name="entry-email" value="" required />
        <label>お問い合わせ内容</label>
        <textarea cols="40" rows="5" required name="entry-message"></textarea>
        <?php wp_nonce_field( 'enquiry-message', '_wpnonce', false ); ?>
        <button>送信</button>
    </form>
</div>

メールアドレスとお問い合わせ内容を入力するシンプルなフォームです。クラス名など属性は省略し最低限になっています。また、実際にはバリデーションも検討する必要がありますが、非同期通信のサンプルに特化するために省略しています。また、セキュリティのために nonce を使っています。

wp_nonce_field はフォームに hidden フィールドの nonce を追加します。nonce はフォーム送信がウェブページからのものであることをサーバー上で確認するためのものです。第3引数はデフォルトで true であり、リファラを hidden フィールドで生成します。必要なければ false を指定します。

各要素にスタイルを指定し固定ページに登録しアクセスしますとこうなります。

fetch でサーバにリクエストする

ユーザがこのフォームに入力し送信ボタンをクリックしますとフォームに submit イベントが発生します。そのイベントを受けて次の javascript の fetch でデータをサーバーに送ります。

<script>
    document.forms['enquiry'].addEventListener('submit', (e) => {
        e.preventDefault(); // イベント動作を止める
        const formData = new FormData(enquiry); // フォームデータをオブジェクトに収容する
        formData.append('action', 'enquiry_sample'); // サーバーのアクション名
        const opt = {
            method: 'post',
            body: formData
        }

        fetch('/wp-admin/admin-ajax.php', opt) // 送信
        .then(response => {
            if(!response.ok) {
                throw new Error;
            }
            return response.text();
        })
        .then(text => {
            document.getElementById('message').innerHTML = text;
            document.forms['enquiry'].style.display = 'none';
        })
        .catch(error => {
            document.getElementById('message').innerHTML = '送信できませんでした';
        });
    });
</script>

入力されたデータを FormData オブジェクトに収容します。また、append メソッドでサーバーのアクション名を追加します。

そして、fetch の第1引数に WordPress 指定のリクエスト先 /wp-admin/admin-ajax.php 、第2引数にデータを指定します。これでサーバーへのリクエストは完了します。fetch 行以下は結果を非同期通信で受けるためのものです。説明は後述します。

この javascript を page-enquiry.php の中に書くか、または別ファイルにして <script></script> タグで読み込みます。

03サーバーサイド

サーバーサイドでは /wp-admin/admin-ajax.php を経由して、指定したアクション名のアクションフックの処理関数に送られます。次のようになります。

function enquiry_sample() {
    $action = 'enquiry-message'; // wp_nonce_fieldで指定した文字列
    $nonce = $_REQUEST[ '_wpnonce' ];
    $auth = wp_verify_nonce( $nonce, $action ); // nonceを検証する
    if(!$auth){
        echo 'fatal error';
        die;
    }

    $to = $_POST['entry-email'];
    $message = $_POST['entry-message'];

    $admin_to = get_option('admin_email');
    $subject = 'お問い合わせがありました';
    $headers[] = 'Reply-To: ' . $to;
    // 管理人への送信
    $result = wp_mail($admin_to, $subject, $message, $headers );
    if($result){
        echo 'お問い合わせ内容を管理人に送信しました<br>また、メールアドレス宛に自動返信メールを送信しました';
        $subject = '自動返信メール';
        // ユーザーへの送信
        wp_mail($to, $subject, $message);
    }else{
        echo '送信できませんでした';
    }
    die;
}
add_action('wp_ajax_enquiry_sample', 'enquiry_sample');
add_action('wp_ajax_nopriv_enquiry_sample', 'enquiry_sample');

まず、リクエストがフォームからのものかどうかを wp_verify_nonce で検証し、そうでなければ fatal error を返し次へ進みません。フォームからのものであれば、管理者メールアドレスにお問い合わせ内容を送信します。

メール送信がエラーなく終了したら、クライアント(ブラウザ)に echo で完了メッセージを返し、その後、ユーザーのメールアドレスあてに自動返信メールを送信します。

クライアントに何を返すかはいろいろ方法がありますが、ここでは完了した旨をテキストで返しています。

04ブラウザ側の結果処理(クライアントサイド)

fetch はサーバーからの応答を非同期で待っていますので、サーバーが echo でデータを返しますと即座に反応します。上に記載した javascript のうちその部分を以下に再掲します。

        fetch('/wp-admin/admin-ajax.php', opt) // 送信
        .then(response => {
            if(!response.ok) {
                throw new Error;
            }
            return response.text();
        })
        .then(text => {
            document.getElementById('message').innerHTML = text;
            document.forms['enquiry'].style.display = 'none';
        })
        .catch(error => {
            document.getElementById('message').innerHTML = 'トラブルが発生しました';
        });

このあたりの用語や表現は難しく説明の言葉遣いに間違いがあるかも知れませんので正しくは MDN などのドキュメントを読んでください。

その前提の上ですが、要点は fetch でリクエストした応答は response に返りますので text メソッドでサーバーからの応答を取り出しブラウザに表示するという流れです。response のメソッドは他によく使うものとしては json, blob があります。

response が返りますとブラウザは次のようになります。

05エラーについて

このケースでどういう場合にエラーが発生するかですが、メール送信の場合はほとんどないのではないかと思います。

nonce 検証の fatal error

フォームを使わずにメールを送りつけてくるとなりますと迷惑メールということになり、エラーを返しても意味がありませんのでメッセージは fatal error のままにしてあります。

!response.ok のエラー

fetch は フェッチ API の使用 の最初の方にあるように、レスポンスが 404 や 500 でもエラーになりません。サーバーが正常に稼働していて 404 や 500 が出るエラーはほぼプログラムミスですので事前に解消すべきエラーですし、そもそもサーバーが異常であればサイト自体が稼働していないと思われます。ですので同一サーバー内で動いていればこのエラーが出る可能性はないのではないかと思います(あまり自信はありませんが…)。

メール送信のエラー

wp_mail() もそうですが、通常メール送信はメールアドレスが正しいかどうかの確認はされません。とにかく一旦送信され、送信できなかった場合は後日サーバーから送信元にお知らせが来ます。ですので、ユーザーへの wp_mail() の戻り値はネットワークエラーがなければ true が返ります。false になる場合はやはり事前に解消すべきエラーと考えられます。つまり、このケースのエラーも可能性は少ないということになります。

06デモサイト

以下にデモサイトがあります。

  • デモサイト(現在停止中)