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:
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).
All file names mentioned are relative to the root WordPress directory.
Background Information
Definitions
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:
Display posts in a different order, such as alphabetically for a "glossary" category.
Override the default number of posts to be displayed on the page; for example, the glossary plugin might want to have a higher post limit when displaying the glossary category.
Exclude certain posts from certain pages; for example, posts from the glossary category might be excluded from the home page and archive pages, and appear only on their own category page.
Expand the default WordPress keyword search (which normally just searches in post title and content) to search in other fields, such as the city, state, and country fields of a geographical tagging plugin.
Allow custom URLs such as example.com/blog?geostate=oregon or example.com/blog/geostate/oregon to refer to the archive of posts tagged with the state of Oregon.
Default WordPress Behavior
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.
Implementing Custom Queries
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.
Display Order and Post Count Limit
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:
Add a filter to the ORDER BY clause of query to change it to alphabetical order if we are viewing the glossary category. The name of the filter is 'posts_orderby', and it filters the text after the words ORDER BY in the SQL statement.
Add a filter to the LIMIT clause of the query to remove the limit. This filter is called 'post_limits', and it filters the SQL text for limits, including the LIMIT key word.
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:
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()