WordPress:Custom Queries

来自站长百科
跳转至: 导航、​ 搜索


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.

插件一般通过添加Hooks (Actions and Filters)扩展WordPress的功能,hooks更改了WordPress的行为方式。但是,有时,插件通过自定义查询,需要进入基本的hooks之外的hooks,而且这个操作并不像将一个filter或者action添加到WordPress那样简单。这篇文章描述了自定义查询是什么,然后解释了插件作者可以怎样地执行查询。

A few notes:

一些注意事项:

  • 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.
  • 所有提及的文件名与WordPress根目录相关。

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: 根据这篇文章的语境,查询指的是WordPress在Loop中使用的数据库查询,查找界面上显示的文章列表("数据库查询" 在这篇文章中用作一篇的数据库查询)。默认情况下,WordPress查询搜索当前请求的网页上的文章,不管是单篇文章,还是单个的静态网页,类别归档,日期归档,搜索结果,feed,或者博客文章的主要列表;查询限制为最多数目的文章(设置在选项管理界面),文章以日期相反顺序返回(首先是最近的文章)。插件可以使用自定义查询取消这个操作。例如:

  • 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.
  • 扩展WordPress默认关键词搜索(一般只在文章标题和内容中搜索),在其它栏中搜索,如地理标题插件中对城市,州,区的搜索。
  • 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.
  • 允许自定义URLs,如example.com/blog?geostate=oregon 或者example.com/blog/geostate/oregon指代已标记的文章的归档,标记为俄勒冈州。

Default WordPress Behavior[ ]

WordPress 默认操作[ ]

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 WordPress:Query Overview.

在你尝试更改WordPress中的查询的默认操作之前,了解WordPress有那些默认操作,很重要。在查询概述中有个概述,关于WordPress创建你的博客页面的过程,以及可以使用哪个插件来更改这个操作。

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.

在我们第一个例子中,加入术语插件能够使得站点所有者将文章放入特别的"术语" 类别(有插件在全局变数$gloss_category保存)。有人浏览术语类别的时候,我们想要根据字母表顺序查看文章而不是根据日期先后,而且我们希望查看术语的所有文章,而不是站点所有者从选项中选择的文章数目。

So, we need to modify the query in two ways:

因此,我们需要以两种方式更改查询:

  1. 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.
  1. 如果我们正在阅读术语类别,将filter添加到查询的ORDER BY clause,更改为字母表顺序。Filter的名称是'posts_orderby',在SQL声明中根据ORDER BY,过滤文本。
  1. 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.
  1. 将filter添加到查询的LIMIT clause,移除限制。这个filter称为'post_limits',过滤了SQL文本限制,包含LIMIT关键词。

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:

在两种情况中,只有我们访问术语类别的时候,filter函数才会做出更改(函数 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 )) {
     // 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;
}


add_filter('posts_orderby', 'gloss_alphabetical' );
add_filter('post_limits', 'gloss_limits' );

function gloss_alphabetical( $orderby )
{
  global $gloss_category;

  if( is_category( $gloss_category )) {
     // 根据文章标题的字母顺序
     返回 "post_title ASC";
  }

  // 不要术语类别中,返回默认顺序
返回$orderby;
}

function gloss_limits( $limits )
{
  global $gloss_category;

  if( is_category( $gloss_category )) {
     // 移除限制
    返回 "";
  }

  // 不再术语类别,返回默认限制
  返回 $limits;
}


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 'pre_get_posts' 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 $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:

继续使用术语插件,我们也希望从某些界面上删除术语文章(主页,非类别归档)和feeds。这样,我们需要添加'pre_get_posts' action,这个action会查看需要哪种类型的界面,根据界面,删除术语类别。我们也会利用这个事实,在查询规范(储存在$wp_query->query_vars, 请看看上面的),在类别索引数字执行类别的时候,你可以先放置"-"符号。下面是代码:

add_action('pre_get_posts', 'gloss_remove_glossary_cat' );

function gloss_remove_glossary_cat( $notused )
{
  global $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() )) {
     $wp_query->query_vars['cat'] = '-' . $gloss_category;
  }
}









add_action('pre_get_posts', 'gloss_remove_glossary_cat' );

function gloss_remove_glossary_cat( $notused )
{
  global $wp_query;
  global $gloss_category;

  // 了解我们是否需要删除术语-删除
  //归档(类别归档除外), feeds, 和主页
  if( is_home() || is_feed() ||
      ( is_archive() && !is_category() )) {
     $wp_query->query_vars['cat'] = '-' . $gloss_category;
  }
}


Keyword Search in Plugin Table[ ]

插件表格中的关键词搜索[ ]

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.

在下一个例子中,假定地理标签插件,将每篇文章标上一个或者多个城市,州,和区。插件将其储存在各自的数据库表格中;我们假定表格名是全局变量$geotag_table,而且拥有栏geotag_post_id, geotag_city, geotag_state, geotag_country。在这个例子中,如果有人进行了关键词搜索(一般搜索文章标题和文章内容),我们也希望搜索到我们插件表格中的城市,州,区栏中的关键词的文章。

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):

因此,我们需要更改SQL查询,以不同的方式搜索到文章(我们在搜索界面的时候):

  • Join the plugin's table to the post table. This is done with the 'posts_join' filter, which acts on the SQL JOIN clause(s).
  • 可以使用在SQL JOIN clause(s)上运行的'posts_join' filter,将插件表格加入到文章表格中。
  • Expand the WHERE clause of the query to look in the plugin table fields. This is done with the 'posts_where' filter, and we'll use the idea that whatever WordPress did to search the post title field, we'll do the same thing with our custom table fields (rather than trying to duplicate WordPress's rather complex logic). WordPress adds clauses like this: (post_title LIKE 'xyz').
  • 可以使用'posts_where' filter,扩展查询的WHERE clause,查看插件表格栏。不管WordPress是否搜索了文章标题栏,我们处理自定义表格栏的方式不变(不会试着复制WordPress相当复杂的逻辑)。WordPress添加clauses,如:(post_title LIKE 'xyz')
  • Add a GROUP BY clause to the query, so that, for instance, if a post is tagged with both Portland, Oregon, and Salem, Oregon, and the viewer searches on "Oregon", we won't end up returning the same post twice. This is done with the 'posts_groupby' filter, which acts on the text after the words GROUP BY in the SQL statement.
  • 将GROUP BY clause添加到查询,这样,如,如果一篇文章标记为波特兰,俄勒冈州,和塞伦,俄勒冈州,访客搜索"俄勒冈州",我们不会两次返回同一篇文章。可以在SQL声明上,GROUP BY文本后的文本上'posts_groupby' filter操作。

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;
}









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 是空的,使用我们的
    return $mygroupby;
  }

  // 不是空的,附加我们的
  return $groupby . ", " . $mygroupby;
}


Custom Archives[ ]

自定义归档[ ]

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.

继续前一个例子中的geo-tagging插件,加入我们希望插件允许形式为www.example.com/blog?geostate=oregon的自定义permalinks,指示WordPress查找州名为"俄勒冈州"的文章并且显示这些文章。

To get this to work, the plugin must do the following:

要实现这个操作,插件必须执行:

  • Ensure that when WordPress parses the URL, the state gets saved in the query variables; to do this, we have to add "geostate" to the list of query variables WordPress understands (query_vars filter). Here's how to do that:
  • 确定WordPress解析URL的时候,州名在查询变数中保存了;要达到这个目的,我们需要将"geostate"添加到WordPress识别的查询变数列表上(query_vars filter)。下面是操作方法:
add_filter('query_vars', 'geotag_queryvars' );

function geotag_queryvars( $qvars )
{
  $qvars[] = 'geostate';
  return $qvars;
}


add_filter('query_vars', 'geotag_queryvars' );

function geotag_queryvars( $qvars )
{
  $qvars[] = 'geostate';
  return $qvars;
}


  • Do the right query when the "geostate" query variable is found; this is similar to the custom queries discussed in the previous examples. The only difference is that instead of testing for is_search or something similar in posts_where 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 if( is_search() ) statements in the examples above:
  • 找到"geostate"查询变数的时候,正确地查询;这与上述例子中讨论的自定义查询相似。唯一的不同在于,我们没有测试is_search或者posts_where和其它数据库查询filters中相似的内容,而是测试是否查看到"geostate"查询变数。下面是替换上面的例子中if( is_search() )声明的代码:
global $wp_query;
if( isset( $wp_query->query_vars['geostate'] )) {
   // modify the where/join/groupby similar to above examples
}


global $wp_query;
if( isset( $wp_query->query_vars['geostate'] )) {
   // modify the where/join/groupby similar to above examples
}
  • Probably the plugin also needs to generate these permalinks. For instance, the plugin might have a function called geotags_list_states that would find out which states exist in its geotag table, and make links to them:
  • 也许插件也需要创建这些permalinks。例如,插件可能有个称为geotags_list_states的函数,这个函数会查找geotag表格中有哪些州,并且给这些州创建链接:
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;
  }

}


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;
  }

}


Permalinks for Custom Archives[ ]

自定义归档的Permalinks[ ]

If the blog user has non-default permalinks enabled, we can go one step further in the previous custom archives example, and enable the URL example.com/blog/geostate/oregon to also list all posts tagged with the state of Oregon. To do this, we add to WordPress's "rewrite rules", which basically tell WordPress how to interpret permalink-style URLs. Specifically, we add a rewrite rule that tells WordPress to interpret /geostate/oregon URLs the same as ?geostate=oregon. (See WordPress:Query Overview for more information on the rewrite process.)

如果博客用户拥有非默认的permalinks,我们可以更深入地处理上述自定义归档例子,并且使得URLexample.com/blog/geostate/oregon也列出所有标记为俄勒冈州的文章。这样,我们需要添加到WordPress的"rewrite rules",指示WordPress怎样解译permalinks样式的URLs。我们会添加rewrite规则,指示WordPress将/geostate/oregon URLs与?geostate=oregon一样解译。(关于rewrite过程更多的信息,请看看查询概述。)

In practice, to define a new rewrite rule, there are two steps: (1) "flush" the cached rewrite rules using an init filter, to force WordPress to recalculate the rewrite rules, and (2) use the generate_rewrite_rules action to add a new rule when they are calculated. Here's the "flush" code:

实际上,定义新的rewrite规则,有两个步骤:(1)使用init filter,"flush"缓存的rewrite规则,迫使WordPress重新考虑rewrite规则,(2)新规则得到认可后,使用generate_rewrite_rules action添加新规则。下面是"flush"代码:

add_action('init', 'geotags_flush_rewrite_rules');

function geotags_flush_rewrite_rules() 
{
   global $wp_rewrite;
   $wp_rewrite->flush_rules();
}


add_action('init', 'geotags_flush_rewrite_rules');

function geotags_flush_rewrite_rules() 
{
   global $wp_rewrite;
   $wp_rewrite->flush_rules();
}

The rule generation is slightly more complex. Basically, the rewrite rules array is an associative array whose keys are regular expressions that match potential permalink URLs, and whose values are the corresponding non-permalink-style URLs they correspond to. So, to define a rewrite rule that matches URLs like /geostate/oregon (with arbitrary states), and tells WordPress it should correspond to ?geostate=oregon, we do the following:

创建规则要稍微复杂。一般来说,rewrite规则数组是相关数组,相关数组的keys是匹配潜在的permalink URIs 的规则表述,相关数组的参数值是相应的非permalink样式的URLs。因此,要定义匹配URLs,如/geostate/oregon(带有任意的州名)的rewrite 规则,并且指示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;
}


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;
}