Contact Form 7のパイプが思い通りに動作しないケースと回避方法

問合せフォームが必要な場合に、Contact Form 7をよく利用させていただいています。

このプラグインには、フォーム利用者から送信する値を隠ぺいする目的でパイプ機能が実装されています。
しかし、ちょっと変わった使い方をすると、意図したとおりに動かない場合があります。

パイプ機能の概略

上記ページのサンプルでは、以下のようにフォームを設定するサンプルが記載されています。

[select your-recipient "CEO|ceo@example.com"
                    "Sales|sales@example.com"
                    "Support|support@example.com"]

値を指定する箇所に、"CEO|ceo@example.com"と記述されていますね。
「|(パイプ)」の左側に表示したい文字列を指定し、右側に実際にフォームから送信される値を指定する書式です。

実際のフォームでは以下のように表示されます。
contact-fotm-pipe-select

HTMLソースでも以下の様になっており、メールアドレスを隠ぺいできています。(見やすくするために改行を入れています)



その他、利用者には詳細な説明も含めたラベルを見せたいが、送られてくるメールには事務処理に適した簡易な文言だけにしたい場合などにも使えそうです。

この機能は「ドロップダウンメニュー、ラジオボタン、チェックボックス」で有効とされています。

パイプ機能がうまく動かないケース

チェックボックス、ラジオボタンでラベル部分を空にすると、うまく動作しません。
チェックボックスやラジオボタンを選択していなくても、選択されているかのように値が送信されてしまいます。

例えば、ラベル部分を画像にしたいケースがあり、こんな風にフォームを設定しました。(単純化しています)

[radio check1 "|image-font1"]

ラベル部分には画像を表示するために、パイプの左側のラベル部分を空にしています。
表示は以下のようになりました。
思った通りに表示されいます。

contact-form-pipe2

メール設定は以下の様になっています。

ラジオボタン1:[check1]

ラジオボタンを選択していれば、[check1]と記述されている箇所へ値に該当する部分、「image-font1」がセットされて送信されるように目論んでいます。

それではラジオボタンを選択して送信しましょう。
受信したメールでは以下のようになりました。

ラジオボタン1:image-font1

 

目論みどおりです。

では、ラジオボタンを選択しないまま送信してみましょう。

ラジオボタン1:image-font1

 

Oh…
値が送信されてしまいました。
これでは、ラジオボタンが選択されているかどうかの判断がつきません

この意図せざる動作の原因

※特に詳細は知りたくなくて解決策だけ知りたい人は、この章を飛ばしてください。

チェックボックスやラジオボタンでは、何も選択されていなかった場合、そのフォームフィールド自体がサーバーに送信されません。
よくphpのソースでif(isset($_POST['hoge'])){などと記述されているのはこのためで、送信データにhogeフィールドが含まれているかどうかをチェックするものです。

しかし、このプラグインでは送信されようがされまいが、フィールドのデフォルト値を空文字として扱っています。
送信データを処理している該当箇所を見てみましょう。
バージョン4.7のcontact-form-7/includes/submission.phpの85行目です。
WPCF7_Submissionクラスのsetup_posted_dataメソッド内です。

$tags = $this->contact_form->scan_form_tags();

foreach ( (array) $tags as $tag ) {
	if ( empty( $tag['name'] ) ) {
		continue;
	}

	$name = $tag['name'];
	$value = '';

最初の$this->contact_form->scan_form_tags()で、該当フォーム設定に含まれている、全てのタグを取得しています。
そして、最終行(ソース中の85行目)で値のデフォルト値をセットしています。
空文字です。

このデフォルト値はcontact-form-7/includes/pipe.phpに定義されている、WPCF7_Pipesクラスのdo_pipeメソッドに渡されます。
このクラスは、フォーム上に定義されているパイプ機能を表しています。
上記テストで言えば、"|image-font1"に当たります。
このメソッドは以下の通りです。

public function do_pipe( $before ) {
	foreach ( $this->pipes as $pipe ) {
		if ( $pipe->before == $before ) {
			return $pipe->after;
		}
	}

	return $before;
}

$beforeは、先ほどのデフォルト値と、フォームから送信された値が渡されます。
メソッドの3行目で、定義されているパイプの前($pipe->before)、すなわちパイプのラベルにあたる部分と引数を比較し、一致すれば次の行でパイプの後($pipe->after)、すなわちパイプの値にあたる部分を返しています。

今回の意図せざる動作のになる何も選択していないケースでは、このメソッドに渡されてくるのは空文字です。
そして、パイプのラベルにあたる前部分の文字列も空です。
送信されてもいない空文字とラベルの空文字は当然一致し、選択されてもいない一致したパイプの後部分が値として返されてしまいました

ソースコードを変更するなら・・・

※誰もプラグインのソースなどは変更したくないと思いますが、仕様なので修正いただけないようなので、ここに記述しておきます。
プルリクエストを送ろうと思いましたが、公開されているgitリポジトリも見つかりませんでした。

Contact Form 7 v4.7を対象にしています。
まず、送信データのデフォルト値を空文字とするのをやめ、NULLにします。
contact-form-7/includes/submission.phpの85行目を変更します。

	$value = NULL;

そして、パイプのラベルと比較している箇所を、厳密な比較に変更します。
contact-form-7/includes/pipe.phpに定義されている、WPCF7_Pipesクラスのdo_pipeメソッドです。

public function do_pipe( $before ) {

	if ( $before === null ) {
		return '';
	}

	foreach ( $this->pipes as $pipe ) {
		if ( $pipe->before == $before ) {
			return $pipe->after;
		}
	}

	return $before;
}

メソッドの最初で、フォームから何も送信されていなければ、メール上の記載は空文字にするため、引数がNULLの場合はすぐさま空文字を返すようにしています。
ドロップダウンメニューで「空の項目を先頭に挿入」した場合もうまく動作します。
全体的なテストは実施していませんので、この変更が及ぼす他の影響については不明です。

この動作を回避するためには

WPCF7_Submissionクラスのsetup_posted_dataメソッドでは、Contact Form 7が送信データとして取得した値($posted_data)をフィルターする機能が提供されています。
wpcf7_posted_dataフィルターです。
このフィルターフックを利用して、送信されていないデータが$posted_dataに含まれていたら、空文字に置き換えます。
テーマのfunctions.phpに以下のように追記します。

add_filter( 'wpcf7_posted_data', 'my_wpcf7_posted_data', 10, 1 );
function my_wpcf7_posted_data( $posted_data ) {

	$contact_form = WPCF7_ContactForm::get_current();
	$tags         = $contact_form->scan_form_tags();

	foreach ( $tags as $tag ) {
		$field = $tag['name'];

		if ( ! isset( $_POST[ $field ] ) ) {
			unset( $posted_data[ $field ] );
			$posted_data[ $field ] = array( '' );
		}

	}

	return $posted_data;
}

これで、タグとして定義されているが実際には送信されなかった値は全て空としてメールに記載になります。

しかし、この方法でもカバーできないパターンがあります。
フォームが以下のように定義されているケースです。
[radio check3 "|text-font1" "|text-font2"]
このようなケースでも、何も選択しなかった場合には1番目の値(text-font1)が送信されていましたが、上記フィルターフックでこれは回避できました。。
しかし、2番目の「text-font2」を選択しているような状況でも1番目の値が送信されてしまいます。
これについては今のところお手上げです。

ですので、このフィルターフックは他の面でも万能ではないかもしれません。
適用にあたっては十分テストを実施してください。
そしてうまくいかない場合にはコメントをいただけるとありがたいです。