万が一、当サイトで重大な問題を発見した際などは、フォーラムや WordSlack #docs チャンネルでお知らせください。</p>
カスタムクエリ
目次
プラグインは通常、アクションフックやフィルターフックを追加してWordPressの振る舞いを変更することで、WordPressの機能を拡張します。しかし、時としてカスタムクエリを発行して基本的なフックで行える以上のことをする必要があります。それはWordPressに一つのフックを追加するほど簡単なことではありません。この記事では、カスタムクエリとはなんであり、プラグイン作者がどうすれば実装できるのかを説明します。
注意点:
- この記事ではあなたが基本的なプラグインの作成やプラグイン用のテーブル作成(プラグインに必要な場合)、アクションとフィルターのプラグイン API、PHP、そしてMySQLデータベースのクエリ言語を熟知していることを前提としています。
- この記事の適用範囲はブログの閲覧者に表示される領域だけであり、管理画面は含みません(しかしながら、あなたのコードが投稿を表示する管理画面にいくらか影響を与えることはありえます)
- すべてのファイル名はWordPressのルートディレクトリからの相対名です。
背景の説明
定義
この記事の文脈では、クエリ はWordPressが表示すべき投稿を見つけるためにループ内で使われるデータベースクエリのことです(データベースクエリ はジェネリックなデータベースクエリを指します)。デフォルトでは、WordPressクエリは現在リクエストされているページに属する投稿を検索し、それが単一の投稿なのか、単一の静的ページなのか、カテゴリーアーカイブなのか、日付アーカイブなのか、検索結果なのか、フィードなのか、ブログのメインページなのかを判断します。クエリは投稿の最大数を制限され(管理画面の設定ページで決められます)、投稿は日付の降順(新しいものが最初)で取得されます。プラグインはこの振る舞いに上書きする形でカスタムクエリを使用します。以下がその例です:
- 用語集 カテゴリーではアルファベット順というように、投稿を異なる順序で表示する
- ページに表示される投稿数の初期値を上書きする:たとえば、用語集プラグインは用語集カテゴリーを表示するときだけ投稿数の上限を増やす。
- 特定のページから特定の投稿を除く:たとえば、用語集カテゴリーの投稿はホーム、アーカイブページからは除外され、用語集カテゴリーページにだけ表示される
- WordPressのデフォルトのキーワード検索を拡張し(通常は投稿タイトルと本文だけを検索する)、地名タグ付けプラグインの都市、州、国名フィールドといった他のフィールドも検索させる
- example.com/blog?geostate=oregonやexample.com/blog/geostate/oregonといったカスタムのURLを可能にし、オレゴン州というタグをつけられた投稿のアーカイブを参照させる。
WordPressのデフォルトの振る舞い
WordPressクエリのデフォルト動作を変更する前に、WordPressがそもそもどういった動きをするのかを知っておくのは重要です。WordPressがあなたのブログページを構築するためにどのような処理をするかの概説と、この振る舞いを変更するためにプラグインが何をできるのかがQuery Overviewen:Query Overviewにあります。
カスタムクエリの実装
それではカスタムクエリを発行する準備ができました! この節ではクエリの修正がどのように実装されるのかをいくつかの例でお見せします。簡単な例から始め、難しいものへと移っていきましょう。
表示順序と投稿数制限
はじめの例では、用語集プラグインについて考えてみましょう。用語集プラグインによって、サイト管理者はあらかじめ決められた用語集カテゴリー(プラグインによってグローバル変数$gloss_categoryに保存されています)に投稿することができます。サイト管理者は用語集カテゴリーが表示されたときは、日付順ではなくアルファベット順に並び、設定ページに入力した数ではなくすべての投稿を出力したいのです。
さて、クエリを二つの方法で修正する必要がでてきました:
- クエリのORDER BY節にフィルターを追加し、用語集カテゴリーを見ているときだけアルファベット順に変えます。フィルターの名前は 'posts_orderby'で、SQL文中でORDER BYの直後にあるテキストにフィルターをかけます。
- クエリのLIMIT節にフィルターをかけ、制限を外します。このフィルターは'post_limits'という名で、SQL文でLIMITキーワードを含むテキストにフィルターをかけます。
どちらの場合も、用語集カテゴリーを表示している時にだけフィルター関数に修正させます(関数is_categoryを使います)。したがって、やるべきことは以下の通りです:
add_filter('posts_orderby', 'gloss_alphabetical' ); add_filter('post_limits', 'gloss_limits' ); function gloss_alphabetical( $orderby ) { global $gloss_category; if( is_category( $gloss_category )) { // 投稿タイトルをアルファベット順に return "post_title ASC"; } // 用語集カテゴリでなければ、$orderbyをそのまま返します return $orderby; } function gloss_limits( $limits ) { global $gloss_category; if( is_category( $gloss_category )) { // 制限を解除 return ""; } // 用語集カテゴリでなければ、デフォルトの制限値をそのまま返します return $limits; }
カテゴリの除外
用語集プラグインにもう少しつきあいましょう。特定の画面(ホーム、カテゴリー以外のアーカイブページ)とフィードから用語集のエントリーを除外したいとします。このためには、'pre_get_posts'アクションを追加してリクエストされた画面を推測し、画面によっては用語集カテゴリを除外します。クエリ文字列(上述の通り、$wp_query->query_varsに保存されています)にカテゴリIDの前に-記号をつけた文字列を追加することでそのカテゴリを除外できるという事実を利用しましょう。これがそのコードです。
add_action('pre_get_posts', 'gloss_remove_glossary_cat' ); function gloss_remove_glossary_cat( $notused ) { global $wp_query; global $gloss_category; // 用語集を除外したいページかを判断する // 除外するのはカテゴリ以外のアーカイブ、フィード、ホーム if( is_home() || is_feed() || ( is_archive() && !is_category() )) { $wp_query->query_vars['cat'] = '-' . $gloss_category; } }
キーワード検索プラグインテーブル
次の例では、地理タグ付けプラグインについて考えてみましょう。このプラグインはそれぞれの投稿に一つ以上の都市、州、国名をタグ付けします。プラグインはそれらの情報を独自のデータベーステーブルに保存します。テーブル名はグローバル変数$geotag_tableに保存されており、geotag_post_id, geotag_city, geotag_state, geotag_countryフィールドを持っていることにしましょう。この例のアイデアでは、誰かがキーワード検索をしたとき(通常の検索は投稿タイトルと本文だけを検索します)キーワードがプラグインテーブルの都市名、州名、国名フィールドにマッチすると、その投稿を返します。
それでは、いくつかの方法でSQL文を修正する必要があります(ただし、検索結果画面だけです):
- 'posts_join'フィルターによって投稿テーブルにプラグインのテーブルを連結します。このフィルターはSQL文のJOIN節で動作します。
- SQL文のWHERE節を拡張して、プラグインテーブルのフィールドも探すようにします。これは'posts_where'フィルターを使います。このアイデアを使えば、WordPressが投稿タイトルフィールドを探すときの動作をカスタムテーブルのフィールドに対しても適用できます(WordPressのなにかと複雑なロジックをコピーするよりはいいでしょう)。WordPressはこのような節を追加します:(post_title LIKE 'xyz')
- SQL文にGROUP BY節を追加します。それにより、投稿がPortland, OregonあるいはSalem, Oregonという二つのタグを持っていて、閲覧者がOregonで検索したときに、同じ投稿を二つ返さなくて済みます。これは'posts_groupby'フィルターを使います。SQL文のGROUP BY節で動作します。
このアイデアを心に留めておいてください。これがそのコードです:
add_filter('posts_join', 'geotag_search_join' ); add_filter('posts_where', 'geotag_search_where' ); add_filter('posts_groupby', 'geotag_search_groupby' ); function geotag_search_join( $join ) { global $geotag_table, $wpdb; if( is_search() ) { $join .= " LEFT JOIN $geotag_table ON " . $wpdb->posts . ".ID = " . $geotag_table . ".geotag_post_id "; } return $join; } function geotag_search_where( $where ) { if( is_search() ) { $where = preg_replace( "/\(\s*post_title\s+LIKE\s*(\'[^\']+\')\s*\)/", "(post_title LIKE \\1) OR (geotag_city LIKE \\1) OR (geotag_state LIKE \\1) OR (geotag_country LIKE \\1)", $where ); } return $where; } function geotag_search_groupby( $groupby ) { global $wpdb; if( !is_search() ) { return $groupby; } // 投稿IDでグルーピングする必要があります $mygroupby = "{$wpdb->posts}.ID"; if( preg_match( "/$mygroupby/", $groupby )) { // グルーピングは済んでいます return $groupby; } if( !strlen(trim($groupby))) { // groupby was empty, use ours return $mygroupby; } // wasn't empty, append ours return $groupby . ", " . $mygroupby; }
カスタムアーカイブ
地理タグ付けプラグインにもう少しつきあいましょう。カスタムパーマリンクを可能にし、www.example.com/blog?geostate=oregonという形式のURLでWordPressに対してoregonというタグを持つ投稿を表示するよう伝えます。
これを動かすには、プラグインは次のことをおこなわなければなりません:
- WordPressがURLを解析するときに州名がクエリ変数に保存されるようにします。このためには、WordPressが理解できるクエリ変数のリストにgeostateを追加します(query_varsフィルターを使います)。以下がそのコードです。
add_filter('query_vars', 'geotag_queryvars' ); function geotag_queryvars( $qvars ) { $qvars[] = 'geostate'; return $qvars; }
- geostateがクエリ変数内に見つかったら、正しいクエリを発行します。これは前の例で説明したカスタムクエリと似ています。ただ一つの違いは、is_searchをチェックしたり、posts_whereなどのデータベースフィルターを利用する代わりに、geostateクエリ変数が特定されたかどうかを知る必要があります。ここでは、上の例で使ったif( is_search() )宣言の代わりにこのようなコードを使います:
global $wp_query; if( isset( $wp_query->query_vars['geostate'] )) { // 上の例と同じように、where/join/groupbyフィルターで修正する }
- おそらく、プラグインはこれらのパーマリンクを出力する必要があります。たとえば、geotags_list_statesという関数を持っているかもしれません。これは地理名テーブルにどんな州が存在しているかをチェックして、そのリンクを出力します:
function geotags_list_states( $sep = ", " ) { global $geotag_table, $wpdb; // DBから州名のリストを取得 $qry = "SELECT geotag_state FROM $geotag_table " . " GROUP BY geotag_state ORDER BY geotag_state"; $states = $wpdb->get_results( $qry ); // リンクのリストを作る $before = '<a href="' . get_bloginfo('home') . '?geostate='; $mid = '">'; $after = "</a> "; $cur_sep = ""; foreach( $states as $row ) { $state = $row->state; echo $cur_sep . $before . rawurlencode($state) . $mid . $state . $after; // 一回目以外はセパレータが必要 $cur_sep = $sep; } }
カスタムアーカイブのパーマリンク
ブログユーザがデフォルト以外のパーマリンクを有効にしていれば、さきほどの例からもう一歩進んで、Oregon州のタグがついた投稿のアーカイブページのURLをexample.com/blog/geostate/oregonにしてみましょう。このためには、WordPressがパーマリンク形式のURLを解析するリライトルールに手を加えなければなりません。はっきり言うと、WordPressが/geostate/oregonというURLが?geostate=oregonと同じだと理解するためのリライトルールを追加する必要があります。(URLリライトのプロセスに関する詳しい情報はクエリ概説を見てください)
具体的に新しいリライトルールを定義するためには、二つのステップがあります。
- initフィルターを使ってキャッシュされたリライトルールを消去し、WordPressにリライトルールを再解析させます
- generate_rewrite_rulesアクションを使って、解析用の新しいルールを追加します
これが消去用のコードです。
add_action('init', 'geotags_flush_rewrite_rules'); function geotags_flush_rewrite_rules() { global $wp_rewrite; $wp_rewrite->flush_rules(); }
ルール生成はもう少し複雑です。基本的にリライトルール配列は想定されるURLにマッチする正規表現をキーに持ち、それに対応するパーマリンク以外のURLを値に持つ連想配列です。したがって、/geostate/oregon(任意の州)というURLにマッチし、WordPressに対してそれが?geostate=oregonに対応することを伝えるリライトルールを定義するためには、このようにします:
add_action('generate_rewrite_rules', 'geotags_add_rewrite_rules'); function geotags_add_rewrite_rules( $wp_rewrite ) { $new_rules = array( 'geostate/(.+)' => 'index.php?geostate=' . $wp_rewrite->preg_index(1) ); $wp_rewrite->rules = $new_rules + $wp_rewrite->rules; }