【WordPress】ACFで、『ループ外で』チェックボックスやセレクトボックスの値とラベルを全て取得 | get_field_object（っぽいもの）を使う【Advanced Custom Fields】

WordPressを使用した企業サイトで、**「管理画面のカスタム投稿一覧で、カスタムフィールドを使った絞り込み検索がしたい」**というオーダーがあった時の話。
カスタムタクソノミーや作成者での絞り込み検索の実装は過去に経験があったので、その要領でいけるだろうと考えていたら意外なところで詰まったので備忘録として。

# 要件
- カスタムフィールドの実装
- 管理画面のカスタム投稿一覧で、カスタムフィールドで絞り込み検索できるようにする

実装するカスタムフィールドはこんな感じ。
![test-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284502/9ee13734-be23-abd6-b1f4-45da41d6f0a8.jpeg)
よくある簡単なセレクトボックス。


　

## 実装
ひとまず、管理一覧画面に自前の検索用セレクトボックスを追加するには`restrict_manage_posts`のアクションフックを使ってよしなにコードを書けばよい。

で、ACFには`get_field_object`という、カスタムフィールドの名前からそのフィールドの情報を引っ張ってくる便利な関数があるので、それを使えばまあ何とかなるだろうと考えて以下のようなコードを書いてみた。
上のテスト用フィールドではセレクトボックスのフィールドタイプなので、`get_field_object('カスタムフィールドの名前')['choices']`で選択肢の値とラベルのセットを取得できる。

```function.php
function add_custom_edit_post_search_form( $post_type ){
	if ( $post_type === 'カスタム投稿タイプのスラッグ' ){
		$selected = get_query_var('test') ? get_query_var('test') : '';
		$field_obj = get_field_object('test');
		printf('<select name="%s">', $field_obj);
		printf('<option value="">すべての%s</option>', $field_obj['label']);
		foreach($field_obj['choices'] as $value => $label){
			$selected_text = '';
			if($selected === $value) {
				$selected_text = ' selected';
			}
			printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label);
		}
		printf('</select>');
	}
};
add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1);

// 実際は`query_vars`や`pre_get_posts`等のフックと組み合わせて、実際に投稿を絞り込まないと意味はないが、
// 本筋の話ではないためここでは省く
```

これでいける…かと思いきや、選択したフィールド値を持つ投稿が1件もない場合（要するに「見つかりませんでした」の時）にエラーになる。

色々と調べてみると、`get_field_object`は引数に投稿IDを取っており、投稿に対してしか使えない。
何とも困ったので調べてみると同じ状況に陥っている人がいたようで、フィールドグループのpost_IDからフィールド情報を引っ張ってくるやり方を紹介しているページを見つけた。

[https://www.healing-solutions.jp/tech/4298/](https://www.healing-solutions.jp/tech/4298/)

上記は2017年の記事で、ACFの仕様が変わったのか残念ながら上記のやり方ではできなかったが、**「フィールド情報をカスタム投稿として保存しているなら、直接`get_posts()`すればいけるのでは？」**と思い、
さらに調べてみたら、フィールドグループは`"acf-field-group"`、その中のフィールドは`"acf-field"`という投稿タイプのポストデータとして`wp_posts`テーブルに保存されていた。

![test-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284502/30e2fae5-88f0-8a2c-ed14-953891fc070b.jpeg)


（上がフィールドグループの投稿データ、下がフィールド名「test」の投稿データ）
　
　
　
　

###データの中身
上記画像を見て分かる通り、`get_field_object`で取得する際の`['label']`や`['name']`は、それぞれ`post_title`や`post_excerpt`に保存されている。

`get_field_object`で取得できるのは、以下のような感じ。
※一部はダミーテキストと差し替えています

```php:var_dump(get_field_object('test'))

var_dump(get_field_object('test'));

// 出力
array(25) {
  ["ID"]=>
  int(661)
  ["key"]=>
  string(19) "field_*************"
  ["label"]=>
  string(9) "テスト"
  ["name"]=>
  string(4) "test"
  ["prefix"]=>
  string(3) "acf"
  ["type"]=>
  string(6) "select"
  ["value"]=>
  string(4) "hoge"
  ["menu_order"]=>
  int(0)
  ["instructions"]=>
  string(27) "これはテストです。"
  ["required"]=>
  int(1)
  ["id"]=>
  string(0) ""
  ["class"]=>
  string(0) ""
  ["conditional_logic"]=>
  int(0)
  ["parent"]=>
  int(660)
  ["wrapper"]=>
  array(3) {
    ["width"]=>
    string(0) ""
    ["class"]=>
    string(0) ""
    ["id"]=>
    string(0) ""
  }
  ["choices"]=>
  array(6) {
    ["hoge"]=>
    string(6) "ほげ"
    ["fuga"]=>
    string(6) "ふが"
    ["piyo"]=>
    string(6) "ぴよ"
    ["foo"]=>
    string(6) "ふー"
    ["bar"]=>
    string(6) "ばー"
    ["baz"]=>
    string(6) "ばず"
  }
  ["default_value"]=>
  array(1) {
    [0]=>
    string(4) "hoge"
  }
  ["allow_null"]=>
  int(0)
  ["multiple"]=>
  int(0)
  ["ui"]=>
  int(1)
  ["ajax"]=>
  int(1)
  ["return_format"]=>
  string(5) "value"
  ["placeholder"]=>
  string(0) ""
  ["_name"]=>
  string(4) "test"
  ["_valid"]=>
  int(1)
}
```
　


　

対して、以下は`get_posts()`で取得できる投稿データ。
※一部はダミーテキストと差し替えています

```php:var_dump(get_posts())
$args = array(
	's'			=> 'test',		// フィールド名は抜粋に入っているため、キーワード検索で良い
	'post_type'	=> 'acf-field',	
);
$acf_fields = get_posts( $args )[0];

// post_contentの中身はシリアライズ化されているので、もとに戻す。
$acf_fields->post_content = unserialize($acf_fields->post_content);

var_dump($acf_fields);

// 出力
object(WP_Post)#9316 (24) {
  ["ID"]=>
  int(661)
  ["post_author"]=>
  string(1) "1"
  ["post_date"]=>
  string(19) "2020-**-** **:**:**"
  ["post_date_gmt"]=>
  string(19) "2020-**-** **:**:**"
  ["post_content"]=>
  array(13) {
    ["type"]=>
    string(6) "select"
    ["instructions"]=>
    string(27) "これはテストです。"
    ["required"]=>
    int(1)
    ["conditional_logic"]=>
    int(0)
    ["wrapper"]=>
    array(3) {
      ["width"]=>
      string(0) ""
      ["class"]=>
      string(0) ""
      ["id"]=>
      string(0) ""
    }
    ["choices"]=>
    array(6) {
      ["hoge"]=>
      string(6) "ほげ"
      ["fuga"]=>
      string(6) "ふが"
      ["piyo"]=>
      string(6) "ぴよ"
      ["foo"]=>
      string(6) "ふー"
      ["bar"]=>
      string(6) "ばー"
      ["baz"]=>
      string(6) "ばず"
    }
    ["default_value"]=>
    array(1) {
      [0]=>
      string(4) "hoge"
    }
    ["allow_null"]=>
    int(0)
    ["multiple"]=>
    int(0)
    ["ui"]=>
    int(1)
    ["ajax"]=>
    int(1)
    ["return_format"]=>
    string(5) "value"
    ["placeholder"]=>
    string(0) ""
  }
  ["post_title"]=>
  string(9) "テスト"
  ["post_excerpt"]=>
  string(4) "test"
  ["post_status"]=>
  string(7) "publish"
  ["comment_status"]=>
  string(6) "closed"
  ["ping_status"]=>
  string(6) "closed"
  ["post_password"]=>
  string(0) ""
  ["post_name"]=>
  string(19) "field_*************"
  ["to_ping"]=>
  string(0) ""
  ["pinged"]=>
  string(0) ""
  ["post_modified"]=>
  string(19) "2020-**-** **:**:**"
  ["post_modified_gmt"]=>
  string(19) "2020-**-** **:**:**"
  ["post_content_filtered"]=>
  string(0) ""
  ["post_parent"]=>
  int(660)
  ["guid"]=>
  string(54) "https://www.example.com/?post_type=acf-field&p=661"
  ["menu_order"]=>
  int(0)
  ["post_type"]=>
  string(9) "acf-field"
  ["post_mime_type"]=>
  string(0) ""
  ["comment_count"]=>
  string(1) "0"
  ["filter"]=>
  string(3) "raw"
}
```

### 対応表

あまり使わないであろうものは省略。

|`$field = get_field_object()`|→|`$field = get_posts()`|
|:---|:---:|:--|
|`$field["ID"]`|→|`$field->ID`|
|`$field["key"]`|→|`$field->post_name`|
|`$field["label"]`|→|`$field->post_title`|
|`$field["name"]`|→|`$field->post_excerpt`|
|`$field["type"]`|→|`$field->post_content["type"]`|
|`$field["instructions"]`|→|`$field->post_content["instructions"]`|
|`$field["choices"]`|→|`$field->post_content["choices"]`|
|`$field["default_value"]`|→|`$field->post_content["default_value"]`|
|`$field["placeholder"]`|→|`$field->post_content["placeholder"]`|
|`$field["wrapper"]['id']`|→|`$field->post_content["wrapper"]['id']`|

フィールド名は`post_excerpt`、フィールドラベルは`post_title`、
その他のだいたいのデータは`post_content`内にシリアライズ化されて入っている模様。
　


## 最終的な実装
上記の表を踏まえて、`get_posts()`で取得したオブジェクトから適宜データを引っ張ってこればよい。
ただし注意点として、先にも書いたとおり`post_content`の中身はシリアライズ化されているため、`unserialize()`するのを忘れないように注意。

```function.php
function add_custom_edit_post_search_form( $post_type ){
	if ( $post_type === 'カスタム投稿タイプのスラッグ' ){
		$selected = get_query_var('test') ? get_query_var('test') : '';

		$args = array(
			's'	=> 'test',
			//'name'	=> 'field_*************',	あまり無いとは思うが、同じ名前のフィールドを複数使っている場合はフィールドキーで取得するほうが良い
			//'parent'	=> ***,						もしくは、フィールドグループが親投稿として指定されているのでそれと組み合わせる
			'post_type'	=> 'acf-field',	
		);
		$field_obj = get_posts( $args )[0];
		$field_obj->post_content = unserialize($field_obj->post_content);

		printf('<select name="%s">', $field_obj->post_excerpt);
		printf('<option value="">すべての%s</option>', $field_obj->post_title);
		foreach($field_obj->post_content['choices'] as $value => $label){
			$selected_text = '';
			if($selected === $value) {
				$selected_text = ' selected';
			}
			printf('<option value="%s"%s>%s</option>', $value, $selected_text, $label);
		}
		printf('</select>');
	}
};
add_action('restrict_manage_posts', 'add_custom_edit_post_search_form', 10, 1);

```

　

管理画面以外でも、たとえばサイドバー等に検索用のセレクトボックスを表示したいときなど、何かしらループ外で処理したいときに役に立つと思われる。