[WordPress]Contact Form 7に下層ページ一覧をチェックボックスとして動的に追加


WordPressのフォーム設置プラグインとして有名な[Contact Form 7](https://ja.wordpress.org/plugins/contact-form-7/)をカスタマイズし、チェックボックスを動的に追加する方法になります。

今回は、特定の親ページをに紐づく下層ページをチェックボックスとして追加します。
独自タグを定義するのですが、プラグインのバージョンによって仕様が変わり動作しない場合がありますのでご注意ください。

## 仕様

- Contact Form 7の設定画面で独自タグ ```[page_list]``` を利用

![独自タグ使用イメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/28076611-8aa8-6e8b-4794-918de5d09204.png "originaltag.png")

	
- Contact Form 7標準のチェックボックス同様、以下のオプションも選択できる
	- 必須項目
	- 名前
	- ラベルを前に、チェックボックスを後に配置する
 	- 個々の項目を label 要素で囲む 
 	- チェックボックスを排他化する 
 	- ID属性
 	- クラス属性

![チェックボックスのオプションイメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/3cf97b08-ada2-0bf2-1aea-3b20508d4d0f.png "スクリーンショット_2018-03-31_20_26_25.png")

- 固定ページ

以下のように親子階層となっている固定ページで、親ページIDもしくはスラッグを元に下層ページのタイトルを取得し、フォームにチェックボックス項目として追加される。

※ただし階層は一階層のみ。孫階層は検証していません。

○ 親ページ > 下層ページ
× 親ページ > 下層ページ > 孫ページ

![固定ページの親子階層イメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/fd1512b9-a35d-8abc-fdbc-2332741ee82e.png "childpage.png")


![フォームイメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/171d24d6-c901-8066-a882-cc153e335629.png "form1.png")



## 検証バージョン

- WordPress : 4.9.4
- Contact Form 7 : 5.0.1


## 実装

今回はチェックボックスの独自タグを追加するので、Contact Form 7のファイルを参考に ```function.php``` にカスタマイズしていきます。

Contact Form 7はインストールし、有効化しておきます。

参考元となるファイル
```WordPressディレクトリ/wp-content/plugins/contact-form-7/modules/checkbox.php```
でチェックボックスの挙動が設定されているので、ほぼこちらからコピペする形になります。

### function.php

```function.php``` の編集に慣れていない方は、function.phpの最終行以降に追加しましょう。（途中に挿入したりしない）

```php:function.php
add_action( 'wpcf7_init', 'wpcf7_add_form_tag_pagelist' );

// 独自タグ[page_list]を定義（末尾*は必須項目用）
function wpcf7_add_form_tag_pagelist() {
	wpcf7_add_form_tag( array( 'page_list', 'page_list*' ),
		'wpcf7_pagelist_form_tag_handler', // 関数名
		array(
			'name-attr' => true,
			'selectable-values' => true,
			'multiple-controls-container' => true,
		)
	);
}

// 独自タグの内容を定義する関数
function wpcf7_pagelist_form_tag_handler( $tag ) {
	if ( empty( $tag->name ) ) {
		return '';
	}

	$validation_error = wpcf7_get_validation_error( $tag->name );
	$class = wpcf7_form_controls_class( $tag->type );

	if ( $validation_error ) {
		$class .= ' wpcf7-not-valid';
	}

	// オプションの設定
	$label_first		= $tag->has_option( 'label_first' );
	$use_label_element	= $tag->has_option( 'use_label_element' );
	$exclusive			= $tag->has_option( 'exclusive' );
	$multiple			= false;

	if ( 'page_list' == $tag->basetype ) {
		$multiple = ! $exclusive;
	}

	if ( $exclusive ) {
		$class .= ' wpcf7-exclusive-checkbox';
	}

	$atts = array();

	$atts['class']	= $tag->get_class_option( $class );
	$atts['id']		= $tag->get_id_option();

	$tabindex = $tag->get_option( 'tabindex', 'signed_int', true );

	if ( false !== $tabindex ) {
		$tabindex = (int) $tabindex;
	}

	// 親ページを取得するプロパティを追加
	// parent_id:xxxx でIDから指定
	$parent_id = ''; // default
	$parent_id = ( $parent_id = $tag->get_option('parent_id', '', true ))? esc_attr($parent_id) : '';

	// parent_slug:xxxx でスラッグから指定
	$parent_slug = ( $parent_slug = $tag->get_option('parent_slug', '', true ))? esc_attr($parent_slug) : '';
	if($parent_slug) {
		$parent_slug	= get_page_by_path($parent_slug);
		$parent_id		= $parent_slug -> ID;
	}

	// 下層ページを取得
	$children = get_children(array(
		'post_parent'	=> $parent_id,
		'post_type'		=> 'page',
		'post_status'	=> 'publish',
		'order'			=> 'ASC',
		'orderby'		=> 'menu_order' // 管理画面のページ順
	));


	$html = '';
	$count = 0;

	$hangover = wpcf7_get_hangover( $tag->name, $multiple ? array() : '' );

	foreach($children as $key => $child) {
		$class = 'wpcf7-list-item';

		$checked 	= false;
		$key		= $count;
		$value		= $child->post_title;
		$label		= $value;

		if ( $hangover ) {
			if ( $multiple ) {
				$checked = in_array( $child_title, (array) $hangover, true );
			} else {
				$checked = ( $hangover === $child_title );
			}
		} else {
			$checked = in_array( $key + 1, (array) $defaults );
		}

		$item_atts = array(
			'type'		=> 'checkbox',
			'name'		=> $tag->name . ( $multiple ? '[]' : '' ),
			'value'		=> $value,
			'checked'	=> $checked ? 'checked' : '',
			'tabindex'	=> false !== $tabindex ? $tabindex : '',
		);

		$item_atts = wpcf7_format_atts( $item_atts );

		if ( $label_first ) { // put label first, input last
			$item = sprintf(
				'<span class="wpcf7-list-item-label">%1$s</span><input %2$s />',
				esc_html( $label ), $item_atts );
		} else {
			$item = sprintf(
				'<input %2$s /><span class="wpcf7-list-item-label">%1$s</span>',
				esc_html( $label ), $item_atts );
		}

		if ( $use_label_element ) {
			$item = '<label>' . $item . '</label>';
		}

		if ( false !== $tabindex && 0 < $tabindex ) {
			$tabindex += 1;
		}

		$count += 1;

		if ( 1 == $count ) {
			$class .= ' first';
		}

		if ( count( $children ) == $count ) { // last round
			$class .= ' last';
		}

		$item = '<span class="' . esc_attr( $class ) . '">' . $item . '</span>';
		$html .= $item;
	} // foreach

	$atts = wpcf7_format_atts( $atts );

	$html = sprintf(
		'<span class="wpcf7-form-control-wrap %1$s"><span %2$s>%3$s</span>%4$s</span>',
		sanitize_html_class( $tag->name ), $atts, $html, $validation_error );

	return $html;

}

// バリデートの設定
add_filter( 'wpcf7_validate_page_list', 'wpcf7_page_list_validation_filter', 10, 2 );
add_filter( 'wpcf7_validate_page_list*', 'wpcf7_page_list_validation_filter', 10, 2 );

function wpcf7_page_list_validation_filter( $result, $tag ) {
	$type = $tag->type;
	$name = $tag->name;
	$is_required = $tag->is_required() || 'page_list*' == $type; // 'page_list*' は独自タグ
	$value = isset( $_POST[$name] ) ? (array) $_POST[$name] : array();

	if ( $is_required && empty( $value ) ) {
		$result->invalidate( $tag, wpcf7_get_message( 'invalid_required' ) );
	}

	return $result;
}
```

これで独自タグが使えるようになったので実際にContact Form 7の設定画面でタグを使ってみます。

### Contact Form 7設定画面

今回追加したチェックボックスを生成する独自タグは ```[page_list]``` となります。

- 必須項目にする場合は末尾に　```*```　を加える ```[page_list*]``` 
- 親ページのIDもしくはスラッグを指定 **（※いずれか必須）**
	- IDを指定する場合は ```parent_id:親ページID``` 
	- スラッグで指定する場合は ```parent_slug:親ページスラッグ``` と書きます。 

```php
[page_list* parent_id:2601] // IDで指定する場合
[page_list* parent_slug:スラッグ] // スラッグで指定する場合
```

さらにContact Form 7の標準チェックボックス同様、以下のオプションが指定出来ます。
書き方は[Contact Form 7の仕様ページ](https://contactform7.com/ja/tag-syntax/#form_tag)を参考にしてください。

- 名前（inputのname値）
- ラベルを前に、チェックボックスを後に配置する ```label_first```
- 個々の項目を label 要素で囲む ```use_label_element```
- チェックボックスを排他化する ```exclusive```
- ID属性 ```id:hoge```
- クラス属性 ```class:foo```

※チェックボックスの項目は **タグでは指定出来ません。**

独自タグの書き方例

```php
[page_list* service use_label_element exclusive class:select_page id:check_service parent_id:2601]
```

![Contact Form 7設定画面](https://qiita-image-store.s3.amazonaws.com/0/62022/b8b9d2df-2eb7-bb81-6391-7466f0671d82.png "originaltag2.png")


## フォームの表示イメージ

![フォームイメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/5283fb52-3c62-b661-0009-7ecba6dbbe25.png "form2.png")

バリデーションも動きます。

![バリデーション表示イメージ](https://qiita-image-store.s3.amazonaws.com/0/62022/6b4b15bb-3d0d-dab7-c3a7-e780807280cd.png "validation.png")

送信すると、標準のチェックボックス同様にカンマ区切りとなります。

<img alt="メール受信サンプル" src="https://qiita-image-store.s3.amazonaws.com/0/62022/74d09378-231d-d65b-9bcb-4dcd3b2f3b4f.png" width="387">

以上になります。

固定ページ以外にもカスタム投稿や記事にも応用できそうですね。
例えばセミナー情報を投稿すると、連動して申し込みフォームにも日時選択のチェックボックスが追加される、といった使い方などが出来ると思います。

参考になりましたら幸いです。

---

下記のページを参考にさせていただきました。ありがとうございます。

- [Contact Form 7 で独自のフォームタグ作成（今年から●年後までが選択肢になるセレクトボックス）](https://qiita.com/shin1kt/items/e47f641091890bbe212f)

- [WordPress ContactForm7 動的なオリジナルのフォームタグを作りたい。](http://chaika.hatenablog.com/entry/2017/07/31/090000)