他人が作った古のWordPressテーマをバージョンを上げずにカスタマイズするお仕事で辛みを感じている今日此頃。如何お過ごしでしょうか? (季節の挨拶)
※ この記事は古いWordPress (v4.1
)を触っていて発生した現象なので、新しいWordPress環境では発生しない可能性があります。
カスタムフィールドやORDER BYなど複雑なクエリで取得したいような時、posts_request
やposts_join
、posts_orderby
というフィルターを使うとSQLを直接変更してデータを取ってくることができます。
WP_Query
内で使用する場合はwp_reset_postdata()
で元のクエリに戻せるのでよいのですが、大元のwp_query
を変更するpre_get_posts
内でこれらのフィルターを使っていると該当するページ内でWP_Query
やget_posts
など使用していると、これらのサブクエリにもpre_get_posts
内で設定したフィルターがかかりSQLが変更されてしまい意図しないデータなってしまうの場合があるので注意が必要です。
e.g.
カスタム投稿 アイカツカードaikatsu_card
のアーカイブページではカスタムフィールドcard_no
とcard_type
とで並び替えたいような場合。($wp_query->set()
で実現できそうな気もするけど敢えてfilterで)
- 投稿に紐づくカスタムフィールド
card_no
,card_type
を取得する card_no
の値、card_type
の値で並び替え
<?php // functions.php add_action('pre_get_posts', 'my_custom_wp_query'); function my_custom_wp_query( $wp_query ) { // WP_Query に影響を与えないようにメインクエリに限定 if( $wp_query->is_main_query() ) { // カスタム投稿 `aikatsu_card` if( is_post_type_archive('aikatsu_card') ) { add_filter('posts_join', custom_card_archive_join, 10, 2); add_filter('posts_orderby', 'custom_card_archive_orderby', 10, 2); } } } // joinのSQLを変更するフィルター function custom_card_archive_join($join, $query) { global $wpdb; $prefix = $wpdb->prefix; $join .= "" ." INNER JOIN {$prefix}postmeta AS card_no" ." ON card_no.post_id = {$prefix}posts.ID" ." AND card_no.meta_key = 'card_no'" ." INNER JOIN {$prefix}postmeta AS card_type" ." ON card_type.post_id = {$prefix}posts.ID" ." AND card_type.meta_key = 'card_type'"; return $join; } // orderbyのSQLを変更するフィルター function custom_card_archive_orderby($orderby, $query) { $orderby = "" ."card_no.meta_value ASC" ."card_type.meta_value ASC"; return $orderby; }
これでカスタム投稿aikatsu_card
のアーカイブページは、カスタムフィールドcard_no
とcard_type
の値順に並び替えられたもので表示されます。(雑にINNER JOIN
でSQL作ったのでカスタムフィールドの値がないものは取得されない)
で、aikatsu_card
アーカイブページのテンプレートファイルなどメインクエリ内でnew WP_QUERY()
でサブクエリを実行すると、上記のフィルターがSQLにセットされてしまい意図したデータが取得できなくなるっぽい。(is_main_query
内にしているのに影響があるのはちょっと不思議。触っているWordPressがv4.1
なので、新しいバージョンでは発生しない可能性は否めません...)
wp_queryがセットされた後にフィルターを解除すればOK
This action hook runs immediately after the global WP class object is set up. The $wp object is passed to the hooked function as a reference (no return is necessary).
出典: Plugin API/Action Reference/wp « WordPress Codex
wp
というglobalのWP class object
が設定された後に実行されるアクションフックがあったので、ここでSQLを変更するフィルターを削除すれば、それ以降のクエリ作成時にフィルターがかかることはなくなります。
<?php // function.php add_action('pre_get_posts', 'my_custom_wp_query'); function my_custom_wp_query( $wp_query ) { if( $wp_query->is_main_query() ) { if( is_post_type_archive('aikatsu_card') ) { add_filter('posts_join', custom_card_archive_join, 10, 2); add_filter('posts_orderby', 'custom_card_archive_orderby', 10, 2); } } } // global WP Object 設定後に実行される add_action( 'wp', 'my_remove_custom_query_filter'); function my_remove_custom_query_filter() { global $wp_query; if($wp_query->is_main_query() ) { if( is_post_type_archive('aikatsu_card') ) { // my_custom_wp_query 内で設定した filter を削除する remove_filter( 'posts_join', 'custom_card_archive_join'); remove_filter( 'posts_orderby', 'custom_card_archive_orderby'); } } }
これで、aikatsu_card
アーカイブページ内でnew WP_Query()
やget_posts()
を実行してもフィルターの影響なくデータを取得することが出来るようになりました。
WP_Query
はサブクエリになるという認識だったので、is_main_query
内で実施したフィルターの影響は受けないと思っていたのでハマってしまいました。なんとなくバグっぽい気がしなくもないので、これが今回触っていたのが古いWordPress v4.1
だから発生した現象なのかどうか、つまり最新のバージョンでも発生するかどうかという部分は未検証です。
フィルターじゃなくて$wp_query->set()
やWP_Query()
のパラメーターで設定できるのが良いとは思うのですが、そうはいかない場合もありますよね…
そして、WordPressのコアはアップデーする前提で運用して欲しい…と切実に思うのでした。 (古い環境で関数が使えるのかどうかとか探すのが大変なので...)
[参考]
- 関数リファレンス/WP Query - WordPress Codex 日本語版
- Function Reference/get query var « WordPress Codex
- Plugin API/Action Reference/wp « WordPress Codex
- Implementing Custom Queries | Custom Queries « WordPress Codex
本件とは関係ないけどWordPressさん特定のarchive-{post_type}-taxonomy-{taxonomy}.php
みたいな投稿タイプ+タクソノミーなアーカイブのテンプレート名が欲しい…
(キッズ ハウス) KIDS HOUSE スリッパ サメ 暖かい 柔らかい ぬいぐるみ スリッパ 室内 冬スリッパ
- 出版社/メーカー: (キッズ ハウス)KIDS HOUSE
- メディア: ホーム&キッチン
- この商品を含むブログを見る