(remove new page category) |
m (added language-xref link to Japanese Codex.) |
||
(20 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
+ | {{Languages| |
||
+ | {{en|Custom Queries}} |
||
+ | {{ja|カスタムクエリ}} |
||
⚫ | |||
__TOC__ |
__TOC__ |
||
+ | '''NOTICE: This article discusses how to modify queries using hooks. To find out how to define create queries from scratch, see [[Class_Reference/WP_Query#Usage|Using WP_Query]] or [[Displaying Posts Using a Custom Select Query|Displaying Posts Using a Custom SQL Query]]''' |
||
+ | |||
+ | ==Intro== |
||
Plugins generally extend the functionality of WordPress by adding [[Plugin API|Hooks]] (Actions and Filters) that change the way WordPress behaves. But sometimes a plugin needs to go beyond basic hooks by doing a custom query, and it's not as simple as just adding one filter or action to WordPress. This article describes what custom queries are, and then explains how a plugin author can implement them. |
Plugins generally extend the functionality of WordPress by adding [[Plugin API|Hooks]] (Actions and Filters) that change the way WordPress behaves. But sometimes a plugin needs to go beyond basic hooks by doing a custom query, and it's not as simple as just adding one filter or action to WordPress. This article describes what custom queries are, and then explains how a plugin author can implement them. |
||
A few notes: |
A few notes: |
||
* This article assumes you are already familiar with the basics of [[Writing a Plugin]], as well as [[Creating Tables with Plugins]] (if applicable for your plugin), the [[Plugin API]] for Actions and Filters, PHP, and the MySQL database query language. |
* This article assumes you are already familiar with the basics of [[Writing a Plugin]], as well as [[Creating Tables with Plugins]] (if applicable for your plugin), the [[Plugin API]] for Actions and Filters, PHP, and the MySQL database query language. |
||
+ | |||
* This article applies only to the viewer-facing blog pages, not the administration screens (although some of what you do may affect administration screens that lists posts as well). |
* This article applies only to the viewer-facing blog pages, not the administration screens (although some of what you do may affect administration screens that lists posts as well). |
||
+ | |||
* All file names mentioned are relative to the root WordPress directory. |
* All file names mentioned are relative to the root WordPress directory. |
||
Line 64: | Line 73: | ||
return $limits; |
return $limits; |
||
} |
} |
||
⚫ | |||
+ | |||
+ | Note: If you need to trigger the filter for an archive instead of a category, you need to also make sure that you are on the front end. Otherwise the filters will interfere with the entry listings and prevent the column sort order from working. In that case you need to modify the function conditionals like this: |
||
+ | |||
+ | <pre> |
||
+ | if( !is_admin() && is_archive( $your_archive ) ){ ... } |
||
</pre> |
</pre> |
||
=== Category Exclusion === |
=== Category Exclusion === |
||
− | To continue with the glossary plugin, we also want to exclude glossary entries from appearing on certain screens (home, non-category archives) and feeds. To do this, we will add a <tt>'pre_get_posts'</tt> action that will detect what type of screen was requested, and depending on the screen, exclude the glossary category. We'll also use the fact that in the query specification (which is stored in <tt>$wp_query->query_vars</tt>, see above), you can put a "-" sign before a category index number to exclude that category. So, here is the code: |
+ | To continue with the glossary plugin, we also want to exclude glossary entries from appearing on certain screens (home, non-category archives) and feeds. To do this, we will add a <tt>'pre_get_posts'</tt> action that will detect what type of screen was requested, and depending on the screen, exclude the glossary category. The entire WP_Query object is passed into this function "by reference", meaning that any changes you make to it inside the function will be made to the global query object. We'll also use the fact that in the query specification (which is stored in <tt>$wp_query->query_vars</tt>, see above), you can put a "-" sign before a category index number to exclude that category. So, here is the code: |
<pre> |
<pre> |
||
add_action('pre_get_posts', 'gloss_remove_glossary_cat' ); |
add_action('pre_get_posts', 'gloss_remove_glossary_cat' ); |
||
− | function gloss_remove_glossary_cat( $ |
+ | function gloss_remove_glossary_cat( $wp_query ) { |
⚫ | |||
− | { |
||
− | global $wp_query; |
||
⚫ | |||
− | + | // Figure out if we need to exclude glossary - exclude from |
|
− | + | // archives (except category archives), feeds, and home page |
|
− | + | if( is_home() || is_feed() || ( is_archive() && !is_category() )) { |
|
⚫ | |||
− | ( is_archive() && !is_category() )) { |
||
+ | //which is merely the more elegant way to write: |
||
⚫ | |||
+ | //$wp_query->set('cat', '-' . $gloss_category); |
||
− | } |
||
+ | } |
||
} |
} |
||
Line 122: | Line 136: | ||
$where = preg_replace( |
$where = preg_replace( |
||
"/\(\s*post_title\s+LIKE\s*(\'[^\']+\')\s*\)/", |
"/\(\s*post_title\s+LIKE\s*(\'[^\']+\')\s*\)/", |
||
− | "(post_title LIKE |
+ | "(post_title LIKE $1) OR (geotag_city LIKE $1) OR (geotag_state LIKE $1) OR (geotag_country LIKE $1)", $where ); |
} |
} |
||
Line 154: | Line 168: | ||
} |
} |
||
</pre> |
</pre> |
||
− | |||
=== Custom Archives === |
=== Custom Archives === |
||
Line 172: | Line 185: | ||
</pre> |
</pre> |
||
− | * |
+ | * Perform the appropriate query when the "geostate" query variable is set; this is similar to the custom queries discussed in the previous examples. The only difference is that instead of testing for <tt>is_search</tt> or something similar in <tt>posts_where</tt> and other database query filters, we'll instead test to see whether the "geostate" query variable has been detected. Here is the code to replace the <tt>if( is_search() )</tt> statements in the examples above: |
<pre> |
<pre> |
||
global $wp_query; |
global $wp_query; |
||
Line 180: | Line 193: | ||
</pre> |
</pre> |
||
− | * |
+ | * Our plugin could also generate the corresponding permalinks. For instance, the plugin might define the function <tt>geotags_list_states</tt> to determine which states exist in its geotag table (that we created earlier), and then return a list of states as links: |
<pre> |
<pre> |
||
function geotags_list_states( $sep = ", " ) |
function geotags_list_states( $sep = ", " ) |
||
Line 212: | Line 225: | ||
=== Permalinks for Custom Archives === |
=== Permalinks for Custom Archives === |
||
− | If the blog |
+ | If the blog administrator has non-default [[Using Permalinks|permalinks]] enabled, we can further refine our previous custom archives example, and rewrite the URL <tt>example.com/blog?geostate=oregon</tt> as <tt>example.com/blog/geostate/oregon</tt>. WordPress's "rewrite rules" are used to tell WordPress how to interpret permalink-style URLs. (See [[Query Overview]] for more information on the rewrite process.) |
− | In practice, to |
+ | In practice, there are two steps to defining a new rewrite rule : (1) "flush" the cached rewrite rules using the <tt>flush_rewrite_rules()</tt> or by going to settings > permalinks and clicking "save" from within the wp-admin. Both methods force WordPress to refresh the stored rewrite rules. (note: To save resources, one should avoid using the <tt>flush_rules</tt> any more than it is necessary) (2) use the <tt>generate_rewrite_rules</tt> action to add a new rule when they are calculated. Here's the sample "flush" code: |
⚫ | |||
− | add_action('init', 'geotags_flush_rewrite_rules'); |
||
+ | <pre> |
||
− | function geotags_flush_rewrite_rules() |
||
+ | add_action('admin_init', 'flush_rewrite_rules'); |
||
− | { |
||
− | global $wp_rewrite; |
||
− | $wp_rewrite->flush_rules(); |
||
⚫ | |||
</pre> |
</pre> |
||
− | The rule generation is slightly more complex. Basically, the rewrite rules array is an associative array |
+ | The rule generation is slightly more complex. Basically, the rewrite rules array is an associative array with permalink URLs as regular expressions (regex) keys, and the corresponding non-permalink-style URLs as values. The following defines a rule that maps URLs like <tt>/geostate/oregon</tt> to a URL request like <tt>?geostate=oregon</tt> : |
<pre> |
<pre> |
||
add_action('generate_rewrite_rules', 'geotags_add_rewrite_rules'); |
add_action('generate_rewrite_rules', 'geotags_add_rewrite_rules'); |
||
Line 235: | Line 243: | ||
$wp_rewrite->preg_index(1) ); |
$wp_rewrite->preg_index(1) ); |
||
+ | // Add the new rewrite rule into the top of the global rules array |
||
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules; |
$wp_rewrite->rules = $new_rules + $wp_rewrite->rules; |
||
} |
} |
||
</pre> |
</pre> |
||
+ | |||
+ | == Related == |
||
+ | |||
+ | {{Query Tags}} |
||
[[Category:Plugins]] |
[[Category:Plugins]] |
Languages: English • 日本語 (Add your language)
NOTICE: This article discusses how to modify queries using hooks. To find out how to define create queries from scratch, see Using WP_Query or Displaying Posts Using a Custom SQL Query
Plugins generally extend the functionality of WordPress by adding Hooks (Actions and Filters) that change the way WordPress behaves. But sometimes a plugin needs to go beyond basic hooks by doing a custom query, and it's not as simple as just adding one filter or action to WordPress. This article describes what custom queries are, and then explains how a plugin author can implement them.
A few notes:
In the context of this article, query refers to the database query that WordPress uses in the Loop to find the list of posts that are to be displayed on the screen ("database query" will be used in this article to refer to generic database queries). By default, the WordPress query searches for posts that belong on the currently-requested page, whether it is a single post, single static page, category archive, date archive, search results, feed, or the main list of blog posts; the query is limited to a certain maximum number of posts (set in the Options admin screens), and the posts are retrieved in reverse-date order (most recent post first). A plugin can use a custom query to override this behavior. Examples:
Before you try to change the default behavior of queries in WordPress, it is important to understand what WordPress does by default. There is an overview of the process WordPress uses to build your blog pages, and what a plugin can do to modify this behavior, in Query Overview.
Now we're ready to start actually doing some custom queries! This section of the article will use several examples to demonstrate how query modification can be implemented. We'll start with a simple example, and move on to more complex ones.
For our first example, let's consider a glossary plugin that will let the site owner put posts in a specific "glossary" category (saved by the plugin in global variable $gloss_category). When someone is viewing the glossary category, we want to see the entries alphabetically rather than by date, and we want to see all the glossary entries, rather than the number chosen by the site owner in the options.
So, we need to modify the query in two ways:
In both cases, the filter function will only make these modifications if we're viewing the glossary category (function is_category is used for that logic). So, here's what we need to do:
add_filter('posts_orderby', 'gloss_alphabetical' ); add_filter('post_limits', 'gloss_limits' ); function gloss_alphabetical( $orderby ) { global $gloss_category; if( is_category( $gloss_category )) { // alphabetical order by post title return "post_title ASC"; } // not in glossary category, return default order by return $orderby; } function gloss_limits( $limits ) { global $gloss_category; if( is_category( $gloss_category )) { // remove limits return ""; } // not in glossary category, return default limits return $limits; }
Note: If you need to trigger the filter for an archive instead of a category, you need to also make sure that you are on the front end. Otherwise the filters will interfere with the entry listings and prevent the column sort order from working. In that case you need to modify the function conditionals like this:
if( !is_admin() && is_archive( $your_archive ) ){ ... }
To continue with the glossary plugin, we also want to exclude glossary entries from appearing on certain screens (home, non-category archives) and feeds. To do this, we will add a 'pre_get_posts' action that will detect what type of screen was requested, and depending on the screen, exclude the glossary category. The entire WP_Query object is passed into this function "by reference", meaning that any changes you make to it inside the function will be made to the global query object. We'll also use the fact that in the query specification (which is stored in $wp_query->query_vars, see above), you can put a "-" sign before a category index number to exclude that category. So, here is the code:
add_action('pre_get_posts', 'gloss_remove_glossary_cat' ); function gloss_remove_glossary_cat( $wp_query ) { global $gloss_category; // Figure out if we need to exclude glossary - exclude from // archives (except category archives), feeds, and home page if( is_home() || is_feed() || ( is_archive() && !is_category() )) { set_query_var('cat', '-' . $gloss_category); //which is merely the more elegant way to write: //$wp_query->set('cat', '-' . $gloss_category); } }
For our next example, let's consider a geographical tagging plugin that tags each post with one or more cities, states, and countries. The plugin stores them in its own database table; we'll assume the table name is in global variable $geotag_table, and that it has fields geotag_post_id, geotag_city, geotag_state, geotag_country. For this example, the idea is that if someone does a keyword search (which normally searches the post title and post content), we also want to find posts where the keyword appears in the city, state, or country fields of our plugin's table.
So, we are going to need to modify the SQL query used to find posts in several ways (but only if we're on a search screen):
With those ideas in mind, here is the code:
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; } // we need to group on post ID $mygroupby = "{$wpdb->posts}.ID"; if( preg_match( "/$mygroupby/", $groupby )) { // grouping we need is already there return $groupby; } if( !strlen(trim($groupby))) { // groupby was empty, use ours return $mygroupby; } // wasn't empty, append ours return $groupby . ", " . $mygroupby; }
To continue with the geo-tagging plugin from the last example, let's assume we want the plugin to enable custom permalinks of the form www.example.com/blog?geostate=oregon to tell WordPress to find posts whose state matches "oregon" and display them.
To get this to work, the plugin must do the following:
add_filter('query_vars', 'geotag_queryvars' ); function geotag_queryvars( $qvars ) { $qvars[] = 'geostate'; return $qvars; }
global $wp_query; if( isset( $wp_query->query_vars['geostate'] )) { // modify the where/join/groupby similar to above examples }
function geotags_list_states( $sep = ", " ) { global $geotag_table, $wpdb; // find list of states in DB $qry = "SELECT geotag_state FROM $geotag_table " . " GROUP BY geotag_state ORDER BY geotag_state"; $states = $wpdb->get_results( $qry ); // make list of links $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; // after the first time, we need separator $cur_sep = $sep; } }
If the blog administrator has non-default permalinks enabled, we can further refine our previous custom archives example, and rewrite the URL example.com/blog?geostate=oregon as example.com/blog/geostate/oregon. WordPress's "rewrite rules" are used to tell WordPress how to interpret permalink-style URLs. (See Query Overview for more information on the rewrite process.)
In practice, there are two steps to defining a new rewrite rule : (1) "flush" the cached rewrite rules using the flush_rewrite_rules() or by going to settings > permalinks and clicking "save" from within the wp-admin. Both methods force WordPress to refresh the stored rewrite rules. (note: To save resources, one should avoid using the flush_rules any more than it is necessary) (2) use the generate_rewrite_rules action to add a new rule when they are calculated. Here's the sample "flush" code:
add_action('admin_init', 'flush_rewrite_rules');
The rule generation is slightly more complex. Basically, the rewrite rules array is an associative array with permalink URLs as regular expressions (regex) keys, and the corresponding non-permalink-style URLs as values. The following defines a rule that maps URLs like /geostate/oregon to a URL request like ?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) ); // Add the new rewrite rule into the top of the global rules array $wp_rewrite->rules = $new_rules + $wp_rewrite->rules; }