テーマ/プラグイン開発者なら知っておきたい Contact Form 7 のこと

WordPress でサイト制作をしていると必ずと言っていいほどお世話になるのが、問い合わせフォームを簡単に設置できる Contact Form 7 です。有名なプラグインだけあって多くのサイトで設定方法などが解説されていますが、がっつり開発者向けの記事は見かけません。ということでここでは Contact Form 7 をプラグイン/テーマ開発者向けに解説します。記事内で引用しているソースのバージョンは、記事公開時の最新版 v5.4.1 です。

カスタム投稿タイプ wpcf7_contact_form について

サイドメニューの「お問い合わせ」で表示されるコンタクトフォーム一覧ですが、これらはカスタム投稿タイプ wpcf7_contact_form として wp_posts テーブルに保存されています。コンタクトフォームをページ内に表示させる際に

エラー: コンタクトフォームが見つかりません。

のようなショートコードを埋め込みますが、このときの id="5" というのはこの wp_posts テーブルでの ID になっています。

またコンタクトフォームの編集時にパネルで表示される各項目

  • フォーム
  • メール
  • メッセージ
  • その他の設定

はそれぞれ wp_postmeta にカスタムフィールドとして保存されています。meta_key

  • フォーム => _form
  • メール => _mail, _mail_2
  • メッセージ => _messages
  • その他の設定 => _additional_settings

となっています。「フォーム」と「その他の設定」の meta_value は文字列、「メール」と「メッセージ」は配列として保存されています。

開発時に必須の「その他の設定」

テーマを開発している最中、フォームを送信するたびに自動返信メールが送られてくると面倒です。そのために Contact Form 7 ではコンタクトフォームごとに「その他の設定」タブで追加の設定をすることができます。最もよく使うのは

skip_mail: on

です。これを「その他の設定」に書いておくと、フォームを送信しても自動返信メールが送られなくなります。

さらに Flamingo で問い合わせ内容をデータベースに保存する場合でも、

demo_mode: on

とすることで開発している間だけデータベースに保存しないようにすることができます。

WPCF7_ContactForm クラスと WPCF7_Submission クラス

コンタクトフォームの投稿オブジェクトは wpcf7_contact_form という関数を呼び出して取得することができます。これにコンタクトフォームの 投稿ID を渡すことで WPCF7_ContactForm クラスのインスタンスが返ってきます。コンタクトフォームに関するあらゆる情報はこのインスタンスから取得することができ、また submit メソッドを呼び出すことでフォームの送信処理(自動返信メールの送信など)を実行することもできます。

Contact Form 7 でもう一つ重要なクラスが WPCF7_Submission クラスです。これは WPCF7_ContactFormsubmit メソッド内でシングルトンとして生成されます。シングルトンの生成時には proceed メソッドが実行され、フォームの送信内容を含むグローバル変数 $_POST をもとに、メールの自動送信などが行われます。

プラグインの読み込み時の処理の流れ

プラグインの起点は wp-contact-form-7.php です。ここでは WPCF7_LOAD_JSWPCF7_VERIFY_NONCE などの定数が定義され、load.php が読み込まれます。

load.php ではまずincludes ディレクトリ内のファイルが読み込まれ、さらに

// load.php

if ( is_admin() ) {
    require_once WPCF7_PLUGIN_DIR . '/admin/admin.php';
} else {
    require_once WPCF7_PLUGIN_DIR . '/includes/controller.php';
}

によって管理画面側では admin/admin.php が、フロントエンド側では includes/controller.php が読み込まれます。フロントエンド側の includes/controller.php では非 Ajax リクエスト時の処理や JavaScript ファイルや CSS ファイルのキューイングが行われます。非 Ajax リクエスト時の処理については後ほど見ていきます。

admin/admin.php、または includes/controller.php の読み込みに続き、plugins_loaded アクションに wpcf7 をフックさせています。この関数内ではモジュールの読み込みとショートコードの登録が行われます。

add_action( 'plugins_loaded', 'wpcf7', 10, 0 );

function wpcf7() {
    WPCF7::load_modules();

    /* Shortcodes */
    add_shortcode( 'contact-form-7', 'wpcf7_contact_form_tag_func' );
    add_shortcode( 'contact-form', 'wpcf7_contact_form_tag_func' );
}

それに引き続いて init アクションに wpcf7_init がフックされます。ここではカスタム投稿タイプ wpcf7_contact_form を登録して wpcf7_init アクションを発火させています。recaptcha などのモジュールサービスの登録はこの wpcf7_init アクションにフックされています。

add_action( 'init', 'wpcf7_init', 10, 0 );

function wpcf7_init() {
    wpcf7_get_request_uri();
    wpcf7_register_post_types();

    do_action( 'wpcf7_init' );
}

まずはこれくらいの流れを押さえておけば大丈夫だと思います。

Ajax リクエストと非 Ajax リクエスト

Contact Form 7 ではフォームの送信を Ajax リクエストにしたり非 Ajax リクエストにしたりすることができます。この二つは Contact Form 7 の JavaScript ファイルを読み込むかどうかによって切り替わり、JavaScript ファイルを読み込めば Ajax リクエストに、読み込まれなければ非 Ajax リクエストになります。これは includes/js/index.js の圧縮前のソース、init.jssubmit.js を見ればわかりやすいです。まず init.js の中では form 要素の submit イベントに対してイベントリスナーが登録され、その中で wpcf7.submit が呼び出されます。wpcf7 オブジェクトの submit メソッドは submit.js ファイル内で定義されており、REST API のエンドポイント contact-forms/${ form.wpcf7.id }/feedback を叩くようになっています。もし includes/js/index.js ファイルが読み込まれないとそうした処理が行われず、form 要素で指定された action 属性にそのまま POST リクエストが送られ、非 Ajax リクエストとして処理されます。

デフォルトでは Ajax リクエストでフォームが送信されるようになっていて、フォームを送信すると送信ボタンの近くにローダーが表示され、送信成功/失敗のメッセージが表示されます。

非 Ajax リクエストにするには wpcf7_load_js フィルターフックを利用します。これは includes/functions.php

function wpcf7_load_js() {
    return apply_filters( 'wpcf7_load_js', WPCF7_LOAD_JS );
}

で呼び出されています。ここの wpcf7_load_js 関数が false を返すと、includes/controller.php 内の

add_action(
    'wp_enqueue_scripts',
    function () {
        $assets = array();

        // ...

        if ( wpcf7_load_js() ) {
            wpcf7_enqueue_scripts();
        }

        // ...

    },
    10, 0
);

wpcf7_enqueue_scripts が実行されず、includes/js/index.js が読み込まれないようになります。

非 Ajax リクエスト時の処理の流れ

非 Ajax リクエスト時の処理は、load.php 内で読み込まれる includes/controller.php の冒頭部分にあります。

// includes/controller.php

add_action(
    'parse_request',
    'wpcf7_control_init',
    20, 0
);

/**
 * Handles a submission in non-Ajax mode.
 */
function wpcf7_control_init() {
    if ( WPCF7_Submission::is_restful() ) {
        return;
    }

    if ( isset( $_POST['_wpcf7'] ) ) {
        $contact_form = wpcf7_contact_form( (int) $_POST['_wpcf7'] );

        if ( $contact_form ) {
            $contact_form->submit();
        }
    }
}

非 Ajax リクエスト時の処理の流れとしては以下のようになります。

  • フォームの送信ボタンを押す
  • form 要素の action 属性(多くの場合はフォームが置かれているページ URL に #wpcf7-f<form ID>-p<page ID>-o<count> のようなフラグメントがつく)に非 Ajax リクエストとして POST リクエストが投げられる
  • WordPress のコアの読み込みが始まる
  • プラグインが読み込まれ、その中で parse_requestwpcf7_control_init がフックされる
  • WP コアの読み込みが終わり、コア関数 wp が実行される
  • リクエストのクエリ変数が一通りパースされ、parse_request が発火する
  • wpcf7_control_init が呼び出される

wpcf7_control_init 関数内ではフォームの隠しフィールド _wpcf7 で渡される投稿オブジェクト ID を元に WPCF7_ContactForm クラスのインスタンスが生成され、submit メソッドが実行されます。submit メソッドの実行が終わるとそのままテンプレートが表示されますが、このときに表示される form 要素には WPCF7_Submission の状態に応じて sent などのクラスが付与されます。またメッセージが正常に送信された等のレスポンスがフォームの下に表示されます。

Ajax リクエスト時の処理の流れ

Ajax リクエストの場合は contact-forms/${ form.wpcf7.id }/feedback にリクエストが投げられます。このエンドポイントは includes/rest-api.php 内で定義されています。エンドポイントのコールバック関数として同ファイル内の wpcf7_rest_create_feedback が登録されています。

wpcf7_rest_create_feedback ではエンドポイントの URL からコンタクトフォームの投稿ID を取得し、wpcf7_contact_form 関数に渡すことで WPCF7_ContactForm のインスタンスを生成しています。そして非 Ajax リクエスト時と同様に submit メソッドを実行し、最後に WP_REST_Response を返します。フォーム側では REST API からのレスポンスを受け取り、メッセージが正常に送信された等のレスポンスを表示します。

WPCF7_ContactFormsubmit メソッド

Ajax リクエスト / 非 Ajax リクエスト共通で実行されるのが WPCF7_ContactFormsubmit メソッドです。メソッド自体はそれほど長くありませんが、その中で本質的な部分だけを抜き出すとこのような感じになります。

public function submit( $args = '' ) {
    // ... (省略)

    $submission = WPCF7_Submission::get_instance( $this, array(
        'skip_mail' => $args['skip_mail'],
    ) );

    $result = array(
        'contact_form_id' => $this->id(),
        'status' => $submission->get_status(),
        'message' => $submission->get_response(),
        'demo_mode' => $this->in_demo_mode(),
    );

    // ... (省略)

    do_action( 'wpcf7_submit', $this, $result );

    return $result;
}

特に説明するようなこともなく、WPCF7_Submission::get_instance を呼び出して、結果を格納した配列 $result を返しています。

WPCF7_Submissionproceed メソッド

先ほどでてきた WPCF7_Submission::get_instance ですが、この中で WPCF7_Submissionproceed メソッドが呼ばれています。この中で呼ばれる mail メソッドによって自動返信メールが送られます。

private function proceed() {
    $contact_form = $this->contact_form;

    switch_to_locale( $contact_form->locale() );

    $this->setup_meta_data();
    $this->setup_posted_data();

    // ... (省略)

    if ( $this->is( 'init' ) ) {
        $abort = ! $this->before_send_mail();

        if ( $abort ) {
            if ( $this->is( 'init' ) ) {
                $this->set_status( 'aborted' );
            }

            if ( '' === $this->get_response() ) {
                $this->set_response( $contact_form->filter_message(
                    __( "Sending mail has been aborted.", 'contact-form-7' ) )
                );
            }
        } elseif ( $this->mail() ) {
            $this->set_status( 'mail_sent' );
            $this->set_response( $contact_form->message( 'mail_sent_ok' ) );

            do_action( 'wpcf7_mail_sent', $contact_form );
        } else {
            $this->set_status( 'mail_failed' );
            $this->set_response( $contact_form->message( 'mail_sent_ng' ) );

            do_action( 'wpcf7_mail_failed', $contact_form );
        }
    }

    restore_previous_locale();

    $this->remove_uploaded_files();
}

Contact Form 7 で押さえておきたいアクション/フィルターフック

wpcf7_submit

フォームの送信後、自動返信メールの送信が行われた後に実行されるのが wpcf7_submit アクションフックです。これは includes/contact-form.php 内で定義されている WPCF7_ContactForm クラスの submit メソッド内で呼び出されます。

do_action( 'wpcf7_submit', $this, $result );

フック関数に渡される第一引数は WPCF7_ContactForm クラスのインスタンス、第二引数はフォーム送信後に表示されるメッセージなどを含む配列です。

array(
    'contact_form_id' => $this->id(),
    'status' => $submission->get_status(),
    'message' => $submission->get_response(),
    'demo_mode' => $this->in_demo_mode(),
);

wpcf7_submit が呼ばれるときには自動返信メールの送信等の処理は終わっており、フォームや入力内容、送信結果の情報などすべて取り出すことが可能になっているため、例えば追加で LINE にも通知したいときなどにこのフックを使用することができます。フォームから送信された内容をデータベースに保存するプラグインである Flamingo は、modules/flamingo.php にてこのアクションに wpcf7_flamingo_submit 関数がフックされており、そこでデータベースへ保存されるようになっています。

wpcf7_posted_data

送信データの追加処理に使えるのが wpcf7_posted_data フィルターフックです。これは includes/submission.php ファイル内で定義されている WPCF7_Submission クラスの setup_posted_data メソッド内で呼び出されています。

$this->posted_data = apply_filters( 'wpcf7_posted_data', $posted_data );

$posted_data にはフォームから送信されたデータが入ってきます。例えば [email* your-email] という入力フィールドがあるとすると、そこに入力されたデータは $posted_data['your-email'] で取り出すことができます。

このフックを使うと、例えば [number* unit-price][number* quantity] のような、単価とその個数を入れるような二つの数字入力フィールドがあって、さらにそれらを掛け合わせた合計値を自動返信メールに記載したい場合、[hidden total] のような隠しフィールドをフォームに用意しておき、

add_filter(
    'wpcf7_posted_data',
    function ( array $posted_data ) {
        $posted_data['total'] = (int) $posted_data['unit-price'] * (int) $posted_data['quantity'];
        return $posted_data;
    }
);

とすることで、合計値もメールに記載することができます。

Contact Form 7 と Flamingo との連携

Contact Form 7 で送信された問い合わせや、問い合わせを行ったユーザーをデータベースに保存するプラグインとして Flamingo があります。このプラグインは modules/flamingo.php の冒頭にあるように、wpcf7_submit アクションにフックさせてデータの保存処理を行っています。

Flamingo で登録されるカスタム投稿タイプ

Flamingo では

  • Flamingo_Contact
  • Flamingo_Inbound_Message
  • Flamingo_Outbound_Message

という3つのクラスに合わせて

  • flamingo_contact
  • flamingo_inbound
  • flamingo_outbound

というカスタム投稿タイプが登録されます。

これを利用すると、例えばある期間中の問い合わせ総数を

<?php

$contact_count = count(
    get_posts(
        array(
            'posts_per_page' => -1,
            'post_type'      => 'flamingo_inbound',
            'date_query'     => array(
                'after'     => '2022-1-1',
                'before'    => '2022-12-31',
                'inclusive' => true,
            ),
        )
    )
);

のようにして取得することができます。

プラグイン用の関数について

Contact Form 7 では多くの関数が定義されていますが、プラグインのカスタマイズで使いそうな関数は

にまとめられています。例えば

// includes/validation-functions.php

/**
 * @param string $email メールアドレス.
 */
function wpcf7_is_email_in_site_domain( $email ): bool

を使うと、引数で与えられたメールアドレスがサイトのドメインに属しているかどうかをチェックできます。

Contact Form 7 のカスタマイズ

非 Ajax リクエストにする

wpcf7_load_jsfalse を返して includes/js/index.js を読み込まないようにすれば、フォーム送信を非 Ajax リクエストにすることができます。

add_filter( 'wpcf7_load_js', '__return_false' );

メールの自動返信をスキップする

コンタクトフォームの「その他の設定」タブに skip_mail: on を追加することでもスキップできますが、wpcf7_skip_mail フィルターで false を返してもスキップすることができます。

add_filter( 'wpcf7_skip_mail', '__return_false' );

wpcf7_special_mail_tags で独自のメールタグを追加する

返信メールではフォームに追加した項目に加えて、[_remote_ip][_date][_site_title] などのメールタグを利用することができます。さらに独自のタグを追加したい場合は wpcf7_special_mail_tags フィルターに関数をフックさせます。例えば Flamingo をインストールして、

<?php

/**
 * 引数で与えられた年の問い合わせ数を取得する.
 *
 * @param integer|string $year
 * @return integer
 */
function my_contact_count( int|string $year ): int {
    return count(
        get_posts(
            array(
                'posts_per_page' => -1,
                'post_type'      => 'flamingo_inbound',
                'date_query'     => array(
                    'after'     => "{$year}-1-1",
                    'before'    => "{$year}-12-31",
                    'inclusive' => true,
                ),
            )
        )
    );
}

/**
 * 受付番号(2023-000001, 2023-00002, ...)を返す.
 *
 * @return string
 */
function my_receipt_number(): string {
    $year = wp_date( 'Y' );
    return $year . '-' . str_pad( (string) ( my_contact_count( $year ) + 1 ), 6, '0', STR_PAD_LEFT );
}

/**
 * 返信メールに利用できる特別なタグとして [receipt_number] を追加する.
 */
add_filter(
    'wpcf7_special_mail_tags',
    fn( $output, $name ) => 'receipt_number' === $name ? my_receipt_number() : $output,
    10,
    2
);

のようにすると、返信メールの中で

受付番号: [receipt_number]

とすることで、連番のお問い合わせ受付番号を表示させることができます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です