テーマ/プラグイン開発者なら知っておきたい 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_ContactForm
の submit
メソッド内でシングルトンとして生成されます。シングルトンの生成時には proceed
メソッドが実行され、フォームの送信内容を含むグローバル変数 $_POST
をもとに、メールの自動送信などが行われます。
プラグインの読み込み時の処理の流れ
プラグインの起点は wp-contact-form-7.php
です。ここでは WPCF7_LOAD_JS
や WPCF7_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.js と submit.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_request
にwpcf7_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_ContactForm
の submit
メソッド
Ajax リクエスト / 非 Ajax リクエスト共通で実行されるのが WPCF7_ContactForm
の submit
メソッドです。メソッド自体はそれほど長くありませんが、その中で本質的な部分だけを抜き出すとこのような感じになります。
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_Submission
の proceed
メソッド
先ほどでてきた WPCF7_Submission::get_instance
ですが、この中で WPCF7_Submission
の proceed
メソッドが呼ばれています。この中で呼ばれる 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_js
で false
を返して 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' );
返信メールではフォームに追加した項目に加えて、[_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]
とすることで、連番のお問い合わせ受付番号を表示させることができます。