wordpress rest-apiでカスタムフィールドを出力する際に処理結果を整形したい

#やりたいこと
- カスタムフィールドをAPI出力したい。
- APIの出力内容を整形したい。


#前提条件
- WordPress 4.9.3
- カスタムフィールド は「Smart Custom Fields」を使用。

#カスタムフィールドをapi出力する

##カスタム投稿タイプを登録


``` function.php
function create_post_type() {
  $supports = [ 
    'title',  // title
    'editor',  // editor
  ];
  register_post_type( 'coupon',  //  クーポン
    array(
      'label' => 'クーポン',  
      'public' => true,  
      'has_archive' => false,  
      'menu_position' => 7, 
      'supports' => $supports, 
      'show_in_rest' => true, //rest api での取得有効にする
      'rest_base' => 'coupon' // rest api での取得名  /wp-json/wp/v2/coupon
    )
  );
}
add_action( 'init', 'create_post_type' );
```

##カスタムフィールドを登録

「Smart Custom Fields」でカスタムフィールド を登録します。
仮に以下の２つを登録したとします。

| 名前 | 概要 |
|:-:|:-:|
| coupon_type |チェックボックスなので複数の値が入っています。 |
|  coupon_image | 画像IDが入っています。|

### function.phpに以下を追記
rest apiで出力できるようにします。

```function.php
add_action( 'rest_api_init', 'slug_register_onsen' );
function slug_register_onsen() {
    register_rest_field( 'coupon', //フィールドを追加したいcustom投稿タイプを指定（先ほど登録したカスタム投稿タイプslugを指定）
        'data', 
        array(
            'get_callback'  => function(  $object, $field_name, $request  ) {
              // 出力したいカスタムフィールドのキーをここで定義
              $meta_fields = array(
                'coupon_type',
                'coupon_image',
                'coupon_area',
              );
              $meta = array();
              foreach ( $meta_fields as $field ) {
                $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, true );
              }
              return $meta;
            },
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
```

###適当に記事を登録してここまでの出力結果を確認


http://example/wp-json/wp/v2/coupon/
カスタムフィールドの値が出力されています。

```
[
  {

  ~中略~

    status: "publish",
    type: "coupon",
    link: "http://example/coupon/xxxxxx/",
    title: {
      rendered: "クーポン1"
    },
    content: {
      rendered: "",
      protected: false
    },
    template: "",
    area: [
      5,
      2,
      8
    ],
    data: {
      coupon_type: "ハンバーガー",
      coupon_image: "11",
    },

  ~中略~

  }
]
```


#チェックボックスの値を配列で入れるようにする。
先ほどの結果で`coupon_type`の値はチェックボックスのため本来はハンバーガー以外にも値があるはずですが、一つしか入っていません。
そこで配列に格納して全て出力するようにします。

```function.php
add_action( 'rest_api_init', 'slug_register_onsen' );
function slug_register_onsen() {
    register_rest_field( 'coupon', //フィールドを追加したいcustom投稿タイプを指定（先ほど登録したカスタム投稿タイプslugを指定）
        'data', 
        array(
            'get_callback'  => function(  $object, $field_name, $request  ) {
              // 出力したいカスタムフィールドのキーをここで定義
              $meta_fields = array(
                'coupon_type',
                'coupon_image',
                'coupon_area',
              );
              $meta = array();
              foreach ( $meta_fields as $field ) {
                //********** ここから書き換え ***********//
                // チェックボックスのフィールドは配列にして格納
                if($field === 'coupon_type'){ 
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, false );
                } else{
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, true );
                }
                //********** ここまで書き換え **********//
              }
              return $meta;
            },
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
```
if文で`coupon_type`を分岐して処理を変えます。
`get_post_meta`のパラメータを`false`に変更することで値を配列で受け取るようにしています。

出力結果は以下のようになります。

```
[
  {

  ~中略~

    status: "publish",
    type: "coupon",
    link: "http://example/coupon/xxxxxx/",
    title: {
      rendered: "クーポン1"
    },
    content: {
      rendered: "",
      protected: false
    },
    template: "",
    area: [
      5,
      2,
      8
    ],
    data: {
      coupon_type: [
      　"ハンバーガー",
      　"チーズバーガー",
      　"テリヤキバーガー",
      ],
      coupon_image: "11",
    },

  ~中略~

  }
]
```
チェックボックスで選択された内容が配列で入るようになりました！


#画像の値をURLで入れるようにする。
先ほどの結果で`coupon_image`の値は画像IDになっていますが、その画像のパスを入れたい場合があります。
そこで画像はURLにして出力するようにします。

```function.php
add_action( 'rest_api_init', 'slug_register_onsen' );
function slug_register_onsen() {
    register_rest_field( 'coupon', //フィールドを追加したいcustom投稿タイプを指定（先ほど登録したカスタム投稿タイプslugを指定）
        'data', 
        array(
            'get_callback'  => function(  $object, $field_name, $request  ) {
              // 出力したいカスタムフィールドのキーをここで定義
              $meta_fields = array(
                'coupon_type',
                'coupon_image',
                'coupon_area',
              );
              $meta = array();
              foreach ( $meta_fields as $field ) {
                //********** ここから書き換え ***********//
                if($field === 'coupon_image'){
                  $imgArray = SCF::get($field);//画像情報取得
                  $imgUrl = wp_get_attachment_url($imgArray);//画像url取得

                  if($imgUrl === false){//画像が取得できなかった場合の処理
                    $meta[ $field ] = ''; //ここに画像が取得できなかった場合の処理を入れる
                  } else{
                    $meta[ $field ] = $imgUrl; //画像を取得できたものはurlを入れる
                  }
                } else if($field === 'coupon_type'){ 
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, false );
                } else{
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, true );
                }
                //********** ここまで書き換え **********//
              }
              return $meta;
            },
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
```
if文で`coupon_image`を分岐して処理を変えます。
smart custom fieldsの関数`SCF::get($field)`で画像情報を取得して`wp_get_attachment_url`で画像URLを抜き出しています。
さらに画像が登録されていない場合の処理も加えています。
画像が無ければnoImage画像のパスを入れたりするなど必要に応じた処理が可能です。上記サンプルでは空にしています。

出力結果は以下のようになります。

```
[
  {

  ~中略~

    status: "publish",
    type: "coupon",
    link: "http://example/coupon/xxxxxx/",
    title: {
      rendered: "クーポン1"
    },
    content: {
      rendered: "",
      protected: false
    },
    template: "",
    area: [
      5,
      2,
      8
    ],
    data: {
      coupon_type: [
      　"ハンバーガー",
      　"チーズバーガー",
      　"テリヤキバーガー",
      ],
      coupon_image: "/images/001.jpg",
    },

  ~中略~

  }
]
```


#カスタムタクソノミーも出力結果に入れたい！
実は上記出力結果には「関東 ＞ 東京 ＞ 渋谷区」みたいな階層を持ったタクソノミー が割り当てられています。

```
    area: [
      5,
      2,
      8
    ],
```

になっているところです。しかしタームIDだけ返されても受け取り側は困ってしまいます。受け取り側でIDを元にラベル情報を取得したりなどさらに処理が必要になってしまうからです。可能であればAPIだけで必要な情報を提供したいです。
そこでタクソノミーのラベルを出力するようにします。


```function.php
add_action( 'rest_api_init', 'slug_register_onsen' );
function slug_register_onsen() {
    register_rest_field( 'coupon', //フィールドを追加したいcustom投稿タイプを指定（先ほど登録したカスタム投稿タイプslugを指定）
        'data', 
        array(
            'get_callback'  => function(  $object, $field_name, $request  ) {
              // 出力したいカスタムフィールドのキーをここで定義
              $meta_fields = array(
                'coupon_type',
                'coupon_image',
                'coupon_area',
              );
              $meta = array();
              foreach ( $meta_fields as $field ) {
                if($field === 'coupon_image'){
                  $imgArray = SCF::get($field);//画像情報取得
                  $imgUrl = wp_get_attachment_url($imgArray);//画像url取得

                  if($imgUrl === false){//画像が取得できなかった場合の処理
                    $meta[ $field ] = ''; //ここに画像が取得できなかった場合の処理を入れる
                  } else{
                    $meta[ $field ] = $imgUrl; //画像を取得できたものはurlを入れる
                  }
                } else if($field === 'coupon_type'){ 
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, false );
                } else{
                  $meta[ $field ] = get_post_meta( $object[ 'id' ], $field, true );
                }
              }


              //********** ここから追記 ***********//
              //タクソノミーのラベル / スラッグ / エリアID 取得
              $taxonomys = get_the_terms( $object[ 'id' ], 'area');
              $taxonomys = json_decode(json_encode($taxonomys), true);// オブジェクトを配列に変換

              foreach ($taxonomys as $key => $value) {
                  $sort[$key] = $value['count'];
              }

              array_multisort($sort, SORT_DESC, $taxonomys); //(count値でソート)

              $area_names = array();
              $area_term  = array();
              $area_id   = 0;

              foreach ($taxonomys as $key => $value) {
                  $area_names[] = $value['name'];
                  $area_term[]  = $value['slug'];
                  $area_id[]    = $value['term_id'];
              }

              // タクソノミーのラベルを挿入
              $meta['area_name'] = $area_names;
              $meta['area_term'] = $area_term;
              $meta['area_id']  = $area_id;
              //********** ここまで追記 **********//

              return $meta;
            },
            'update_callback' => null,
            'schema'          => null,
        )
    );
}
```

`get_the_terms( $object[ 'id' ], 'area');` でこの投稿のタクソノミー情報を取得しています。
`area`はカスタムタクソノミー の名前ですので環境に合わせて変更してください。

次に各タームは階層に関係なく順番はバラバラで入っています。なので普通に取ってくると「渋谷区,関東,東京」みたいに階層構造がわからない状態です。
しかも`get_the_terms`で取得した情報には親子関係を表す要素は入っていません。
なので`count`値を元に並べ替えします。count値はそのタームに所属している投稿数ですので子のcount値が親より大きいことはありません。

`$sort[$key] = $value['count'];`でcount値を抜き出し、それを基準に`array_multisort($sort, SORT_DESC, $taxonomys);`でcount値順で並び替えます。

ここまでくればあとはチェックボックスなどと要領は同じです。
今回はエリアのラベル、スラッグ、IDを取得するようにしています。IDは最下層のタームIDが入るようになっています。


出力結果は以下のようになります。

```
[
  {

  ~中略~

    status: "publish",
    type: "coupon",
    link: "http://example/coupon/xxxxxx/",
    title: {
      rendered: "クーポン1"
    },
    content: {
      rendered: "",
      protected: false
    },
    template: "",
    area: [
      5,
      2,
      8
    ],
    data: {
      coupon_type: [
      　"ハンバーガー",
      　"チーズバーガー",
      　"テリヤキバーガー",
      ],
      coupon_image: "/images/001.jpg",
      area_name: [
      　"関東",
      　"東京",
      　"渋谷区",
      ],
      area_term: [
      　"kanto",
      　"tokyo",
      　"shibuya",
      ],
      area_id: [
        5,
        8,
        2
      ],
    },

  ~中略~

  }
]
```

タクソノミー情報が階層順で出力されました。これで取得する側も扱いやすいデータとなりました。