アクションフックpre_get_postsによるクエリ操作と管理画面のカスタムについてのメモ。
例題
猫にまつわる雑学を投稿をしているカスタム投稿タイプ cat_story
において、
以下のカスタムフィールドを設定し、条件に当てはまる記事だけ表示をしたい:
- カスタムフィールド
cat_type
が何かしら設定してある - カスタムフィールド
adsence
が true である - カスタムフィールド
recommend_point
が 0 以上である
さらに並び替えの順序にも要求がある:
- カスタムフィールド
custom_order
の数字が大きい順にソート
カスタムフィールドの設定
これは脳死で Advanced Custom Field 使うのが楽と思う。
Exportしたphp:
if(function_exists("register_field_group")) { register_field_group(array ( 'id' => 'acf_news', 'title' => 'news', 'fields' => array ( array ( 'key' => 'field_5b5964e901fa4', 'label' => '猫タイプ', 'name' => 'cat_type', 'type' => 'select', 'choices' => array ( 'wild-cat' => '野生', 'house-cat' => '家猫', 'field-cat' => '野良', ), 'default_value' => '', 'allow_null' => 1, 'multiple' => 0, ), array ( 'key' => 'field_5b59655a01fa5', 'label' => '広告の有無', 'name' => 'adsence', 'type' => 'true_false', 'message' => '', 'default_value' => 0, ), array ( 'key' => 'field_5b59658101fa6', 'label' => 'おすすめ度', 'name' => 'recommend_point', 'type' => 'number', 'instructions' => 'いわゆる星の数', 'default_value' => 0, 'placeholder' => '', 'prepend' => '', 'append' => '', 'min' => 1, 'max' => 5, 'step' => '', ), array ( 'key' => 'field_5b59659e01fa7', 'label' => 'カスタム順序', 'name' => 'custom_order', 'type' => 'number', 'default_value' => 0, 'placeholder' => '', 'prepend' => '', 'append' => '', 'min' => '', 'max' => '', 'step' => '', ), ), 'location' => array ( array ( array ( 'param' => 'post_type', 'operator' => '==', 'value' => 'news', 'order_no' => 0, 'group_no' => 0, ), ), ), 'options' => array ( 'position' => 'normal', 'layout' => 'no_box', 'hide_on_screen' => array ( ), ), 'menu_order' => 0, )); }
pre_get_posts による実装
get_postsでWP_Query使ってマッチする投稿だけ得る。でも一応できるけど、
今推奨されてるのは アクションフック pre_get_posts によって実行前にクエリを編集する方法なので、こちらでやっつけることにする。
meta_query
のところは、WP_Queryの「カスタムフィールドのパラメータ」 のところとほぼ同じなんだけれども、書き方によってキーが変わる。
//meta_key array( 'meta_key' => 'cat_type', 'meta_compare' => 'EXISTS' ); //meta_query array( 'meta_query' => array( array( 'key' => 'cat_type', 'compare' => 'EXISTS' ) ) );
他の操作についてもWP_Queryそのままなので便利だと思う。
正規表現マッチはcompare = REGEXP で可能。
//wild-から始まるcat_typeと一致するものに限定する場合 array( 'meta_query' => array( array( 'key' => 'cat_type', 'value' => '^wild\-', 'compare' => 'REGEXP' ) ) );
投稿一覧テーブルにカスタムフィールドのカラムを追加
例題:カスタムフィールドによるフィルタとソートを管理画面の投稿一覧でも使いたい
まず、設定の見える化するためカラムに項目を追加する。
manage_{$post_type}_posts_columns
フックで列項目の追加や削除ができる。
カラムの削除は unset
を使う。
/** * $columns = { * ["cb"]=> * string(25) "<input type="checkbox" />" * ["title"]=> string * ["author"]=> string(9) "作成者" * ["comments"]=> * string(119) "<span class="vers comment-grey-bubble" title="コメント"><span class="screen-reader-text">コメント</span></span>" * ["date"]=> * string(6) "日付" * } */ add_filter('manage_cat_story_posts_columns' , function ($columns) { //削除 unset( $columns['author'] ); unset( $columns['comments'] ); $new_columns = array( 'cat_type' => '猫タイプ', 'adsence' => '広告の有無', 'recommend_point' => 'おすすめ度', 'custom_order' => '並び順' ); //dateの前に追加する $pos = array_search('date', array_keys($columns)); $columns = array_merge( array_slice($columns, 0, $pos), $new_columns, array_slice($columns, $pos) ); return $columns; });
値の表示
追加したカラムの値を表示するのはアクションフックの manage_{$post_type}_posts_custom_column で。
add_action( 'manage_cat_story_posts_custom_column' , function ( $column, $post_id ) { switch ( $column ) { case 'cat_type' : echo '—'; break; case 'adsence': echo '—'; break; case 'recommend_point': echo '—'; break; case 'custom_order': echo '—'; break; } }, 10, 2 );
これで追加したカラムに全部ハイフンが入る。
※custom_order が抜けてるけど気にしない
カスタムフィールドに入力されている値の表示は、ACF利用なら get_field
で:
add_action( 'manage_cat_story_posts_custom_column' , function( $column, $post_id ) { switch ( $column ) { case 'cat_type' : echo '—'; break; case 'adsence': echo (int) get_field('adsence', $post_id) === 1 ? '○' : '—'; break; case 'recommend_point': echo get_field('recommend_point', $post_id); break; case 'custom_order': echo get_field('custom_order', $post_id); break; } }, 10, 2 );
WordPress内蔵の関数ならget_post_meta
で:
echo get_post_meta( $post_id, 'custom_order', true );
cat_type
は選択肢設定済みのセレクトメニューなので、項目を流用する。
項目を変更する予定がないなら直接配列に書いてしまう方が楽だと思う。
case 'cat_type' : $cat_type = array ( 'wild-cat' => '野生', 'house-cat' => '家猫', 'field-cat' => '野良', ); $cat_type_val = get_field('cat_type', $post_id); echo !empty($cat_type_val) ? $cat_type[$cat_type_val] : '—'; break;
変更する可能性がある場合は、ACFのカスタムフィールド設定のものを使えばいいと思う。
その場合は、最初に貼ったACFのExportソースを見るとわかるように、カスタムフィールドの設定が乱数を使ったキーで設定してあるので、
設定画面のHTMLソースやExportしたソースからこのキーを取得する。
case 'cat_type' : $cat_type = get_field_object('field_5b5964e901fa4')['choices']; $cat_type_val = get_field('cat_type', $post_id); echo !empty($cat_type_val) ? $cat_type[$cat_type_val] : '—'; break;
並び替えカラムの指定
日付やタイトルにできる簡単なソート機能は、manage_{$this->screen->id}_sortable_columns フィルタでカラムのキーを指定するだけで追加できる:
/** * 並び替えカラムの指定 */ add_filter("manage_edit-cat_story_sortable_columns", function ( $columns ) { $columns['recommend_point'] = 'recommend_point'; $columns['custom_order'] = 'custom_order'; return $columns; });
ただしこれだとソートのボタンは動くがソート機能が働かないので、 アクションフック parse_query で追加する。
//カスタムソートの追加 add_action( 'parse_query', function ($query) { global $typenow, $pagenow; if ( 'cat_story' == $typenow && is_admin() && $pagenow == 'edit.php') { //recommend_point順の並び替え if( isset($_GET['orderby']) && $_GET['orderby'] === 'recommend_point' ) { $query->query_vars['meta_key'] = 'recommend_point'; $query->query_vars['orderby'] = 'meta_value_num'; } //recommend_point順の並び替え if( isset($_GET['orderby']) && $_GET['orderby'] === 'custom_order' ) { $query->query_vars['meta_key'] = 'custom_order'; $query->query_vars['orderby'] = 'meta_value_num'; } } });
絞り込み検索の追加
フィルタしたい場合のやつは絞り込み検索に条件として追加する。
これはアクションフック restrict_manage_posts でできる。
//絞り込み検索にカスタムフィールドのものを追加する add_action( 'restrict_manage_posts', function($post_type) { $output = ''; if ( $post_type !== 'cat_story') { return; } $cat_type = get_field_object('field_5b5964e901fa4')['choices']; $selected = isset($_GET['cat_type']) ? $_GET['cat_type'] : ''; $output .= "<select name='cat_type' class='postform'>\n"; $output .= '<option '.selected($selected,'',false).' value="">猫タイプ</option>'; foreach ($cat_type as $value => $label) { $output .= sprintf ( '<option value="%s"%s>%s</option>', $value, $value == $selected ? ' selected="selected"':'', $label ); } $output .= "</select>\n"; $adsence_val = array_key_exists('adsence', $_GET) ? $_GET['adsence'] : ''; $output .= "<select name='adsence' class='postform'>\n"; $output .= '<option '.selected($adsence_val,'',false).' value="">広告の有無</option>'; $output .= '<option '.selected($adsence_val,1,false).' value="1">広告あり</option>'; $output .= '<option '.selected($adsence_val,0,false).' value="0">広告なし</option>'; $output .= "</select>\n"; echo $output; }, 10);
項目は値の時と同じで。選択中の値は$_GET
から得る。
この場合もクエリを操作しないと挙動しないので、前述の parse_query
にフィルタ分を追加する。
$meta_query = array(); if( array_key_exists('cat_type', $_GET) && $_GET['cat_type'] !== '') { $meta_query[] = array( 'key' => 'cat_type', 'value' => $_GET['cat_type'], 'compare' => '==' ); } if( array_key_exists('adsence', $_GET) && $_GET['adsence'] !== '') { $meta_query[] = array( 'key' => 'adsence', 'value' => $_GET['adsence'], 'compare' => '==' ); } if(count($meta_query) > 1) { $meta_query['relation'] = 'AND'; } if(count($meta_query) > 0) { $query->set('meta_query', $meta_query); }
ここまでの投稿一覧操作ソースまとめ:
ソート条件を増やす
例題:カスタムフィールド 収益単価 revenue
と 収益単価(Android) revenue_android
でもソートをしたいが、ラベルは同じなので表示するカラムは同じにしたい。
デフォルトで備わってるソートはDESC/ASCしか出来ないので、上記の方法だとカラムを分けるしかない。
1カラムの条件を増やしてソートしたい場合はJavaScriptで操作するしかなさそうだった。
カラムのタイトルにセレクトメニューを追加してソート条件を選べるようにする:
add_filter('manage_cat_story_posts_columns' , function ($columns) { //削除 unset( $columns['author'] ); unset( $columns['comments'] ); //収益単価のorderbyメニュー $revenue_order = '収益単価<br><select style="width: 100%" id="admin_sort-order_revenue"><option value="all"></option> <option' . _query_selected('orderby=revenue&order=desc') .' value=\'{"orderby":"revenue", "order":"desc"}\'>iOS DESC</option> <option' . _query_selected('orderby=revenue&order=asc') .' value=\'{"orderby":"revenue", "order":"asc"}\'>iOS ASC</option> <option' . _query_selected('orderby=revenue_android&order=desc') .' value=\'{"orderby":"revenue_android", "order":"desc"}\'>Android DESC</option> <option' . _query_selected('orderby=revenue_android&order=asc') .' value=\'{"orderby":"revenue_android", "order":"asc", "device":"android"}\'>Android ASC</option></select>'; $new_columns = array( 'cat_type' => '猫タイプ', 'adsence' => '広告の有無', 'recommend_point' => 'おすすめ度', 'custom_order' => '並び順', 'revenue' => $revenue_order ); //dateの前に追加する $pos = array_search('date', array_keys($columns)); $columns = array_merge( array_slice($columns, 0, $pos), $new_columns, array_slice($columns, $pos) ); return $columns; }); function _query_selected($query) { return (strpos($_SERVER['QUERY_STRING'], $query) !== false ? ' selected' : ''); }
値は単純に2行にするだけなので割愛。
セレクトメニューが選択されたときに条件を処理するスクリプトは admin_print_footer_scripts
フックで追加する。
add_action( 'admin_print_footer_scripts', function () { global $pagenow, $typenow; if ( 'cat_story' == $typenow && is_admin() && $pagenow=='edit.php') { ?> <script type="text/javascript"> (function($) { function admin_custom_sort_order() { var val= $(this).val(); var search = location.search.replace('?', ''); if(val && val !== 'all' && search) { var format_val = JSON.parse(val); if(/(&?orderby\=\w+)/.test(search)) { search = search.replace(/orderby=\w+/, 'orderby=' + format_val['orderby']) } else { search = search + '&orderby=' + format_val['orderby']; } if(/(&?order\=\w+)/.test(search)) { search = search.replace(/order=\w+/, 'order=' + format_val['order']) } else { search = search + '&order=' + format_val['order']; } location.href = location.origin + location.pathname + '?' + search; } else if(val === 'all') { search = search.replace(/orderby=\w+/, ''); search = search.replace(/order=\w+/, ''); location.href = location.origin + location.pathname + '?' + search; } else if(val) { var format_val = JSON.parse(val); location.href = location.origin + location.pathname + '?' + 'orderby=' + format_val.orderby + '&order=' + format_val.order } } //収益単価のソート $('#admin_sort-order_revenue').on('change', admin_custom_sort_order); }(jQuery)) </script> <?php } }, 100);
$_GETの値でorderbyを変更してlocation.hrefを上書きするっていうスクリプト。