wordpress自定义url路由规则 — WordPress 路由浅析

本文将阐述WordPress (WordPress Router)如何解析请求URL以及根据URL来决定显示 什么内容或者做什么。不同于其他框架比如 Drupal 或者 Kohana ,你基本上只需要定义一些”URL-pattern” -> “Function call”的映射,在 WordPress 中这整个过程复杂的多同时缺少灵活性。所幸的是,有一款优秀的插件 – WP Router ,它可以帮你简化这一过程。 但是,不用插件,让我们看看 WordPress 能做些什么。

Module “Except”

我们来做一个测试模块 – “Except”,通过完成该模块来讲解 WordPress 的URL路由原理。默认情况下,当你访问以下URL时http://wp.example.com/category/ABCDE/ ,你将会看到该分类下的所有文章。而这个模块要达到的效果是,当访问如下的URL时 http://wp.example.com/except/category/ABCDE/ ,显示除了分类”ABCDE”下的所有文章。

你可以从 这里 下载最终完成的模块然后照着例子学习。或者你可以跟着我学习如何一步一步的完成这个模块。

首先,让我们在WordPress站点键入一些URL。就拿Groupon Blog来举个栗子:

https://blog.groupon.com/category/cities/ — 有效,可以访问。

https://blog.groupon.com/except/category/cities/ — Page Not Found。 意料之中,WordPress不知道你想要干什么。

步骤 0: 整体框架

从一个默认的模板来构建模块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
}
$Except = new Except();

这里只定义了3件事:

  1. 一个常量EXCEPT_PATH – 定义了模块的根路径,方便引用模块里的其他文件(虽然此处没有用到该常量,但是作为一个插件作者,这个很有用)。
  2. 一个类 – Except
  3. 一个类的实例对象 $Except

步骤 1: 注册钩子(Hook)

第一件事就是告诉WordPress当模块第一次被激活时,WordPress 应该添加新的重写规则到默认序列并且刷新它。 为了优雅干净,当模块被停用时,应该删除这些规则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*
Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
  function __construct() {
      register_activation_hook( __FILE__, array($this, 'activate') );
      register_deactivation_hook( __FILE__, array($this, 'deactivate') );
  }
  function activate() {
    add_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
    global $wp_rewrite; $wp_rewrite->flush_rules(); // force call to generate_rewrite_rules()
  }
  function deactivate() {
    global $wp_rewrite; $wp_rewrite->flush_rules();
  }
  function add_rewrite_rules(){
    // ???
  }
}
$Except = new Except();

我们先把关键函数add_rewrite_rules()留空,因为我要先解释一些东西。

WordPress重写规则是如何工作的

要想知道答案,我们必须知道什么是重写规则?它们从哪来?为什么要钩进去(hook into)?

首先,关于什么是“重写规则”,他们是一个类似下面的数组:

array = 
  category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$: string = index.php?category_name=$matches[1]&feed=$matches[2]
  category/(.+?)/(feed|rdf|rss|rss2|atom)/?$: string = index.php?category_name=$matches[1]&feed=$matches[2]
  category/(.+?)/page/?([0-9]{1,})/?$: string = index.php?category_name=$matches[1]&paged=$matches[2]
  category/(.+?)/?$: string = index.php?category_name=$matches[1]
  tag/([^/]+)/feed/(feed|rdf|rss|rss2|atom)/?$: string = index.php?tag=$matches[1]&feed=$matches[2]
  tag/([^/]+)/(feed|rdf|rss|rss2|atom)/?$: string = index.php?tag=$matches[1]&feed=$matches[2]
  tag/([^/]+)/page/?([0-9]{1,})/?$: string = index.php?tag=$matches[1]&paged=$matches[2]
  tag/([^/]+)/?$: string = index.php?tag=$matches[1]
  ...

这个数组存储在数据库表wp_options,option_name为rewrite_rules的数据行里。 在调用方法WP_Rewrite::wp_rewrite_rules()时加载进了WordPress。从上面的例子中你也许发现了,rewrite_rules包含了将地址栏里的URL转换成通常的GET字符串的指令。它必须是人类可读的URL,也就是说,当两个不同的人分别访问http://wp.example.com/category/carolhttp://wp.example.com/index.php?category_name=carol时,必须指向同一个页面。这就解释了为什么我们如此关心它 — 因为我们想要一个简短漂亮的URL。这同样回答了为什么它要存在数据库里而不是在每个页面加载时生成它 — 因为通常它不会改变。

步骤 2: 创建我们自己的规则

现在,我们知道了在add_rewrite_rules()方法里该加些什么内容了。我们可能需要这样的规则:

array = 
  except/category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$: string = index.php?except_category_name=$matches[1]&feed=$matches[2]
  except/category/(.+?)/(feed|rdf|rss|rss2|atom)/?$: string = index.php?except_category_name=$matches[1]&feed=$matches[2]
  except/category/(.+?)/page/?([0-9]{1,})/?$: string = index.php?except_category_name=$matches[1]&paged=$matches[2]
  except/category/(.+?)/?$: string = index.php?except_category_name=$matches[1]

试着加一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
  function __construct() {
      register_activation_hook( __FILE__, array($this, 'activate') );
      register_deactivation_hook( __FILE__, array($this, 'deactivate') );
  }
  function activate() {
    add_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
    global $wp_rewrite; $wp_rewrite->flush_rules(); // force call to generate_rewrite_rules()
  }
  function deactivate() {
    global $wp_rewrite; $wp_rewrite->flush_rules();
  }
  function add_rewrite_rules($wp_rewrite){
    $rules = array(
      'except/category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$'  => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/(feed|rdf|rss|rss2|atom)/?$'       => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/page/?([0-9]{1,})/?$'              => 'index.php?except_category_name=$matches[1]&paged=$matches[2]',
      'except/category/(.+?)/?$'                                => 'index.php?except_category_name=$matches[1]',
      );
    $wp_rewrite->rules = $rules + (array)$wp_rewrite->rules;
  }
}
$Except = new Except();

不错,现在启用模块,尝试访问如下的地址 http://wp.example.com/except/category/enter_some_gibberish_here/. 不同于404页面,你将会看到你网站的主页。这好多了。WordPress知道这个URL是有效的,但还不知道该怎么去处理请求,所以显示了所有文章。

步骤 3: 过滤文章

现在,我们需要插入到 WordPress 查询(query)来过滤文章。幸运的是有一个绝好的地方 — pre_get_posts 钩子。 我们来试一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/*
Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
  function __construct() {
      register_activation_hook( __FILE__, array($this, 'activate') );
      register_deactivation_hook( __FILE__, array($this, 'deactivate') );
      add_action( 'pre_get_posts', array($this, 'pre_get_posts') );
  }
  function activate() {
    add_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
    global $wp_rewrite; $wp_rewrite->flush_rules(); // force call to generate_rewrite_rules()
  }
  function deactivate() {
    global $wp_rewrite; $wp_rewrite->flush_rules();
  }
  function add_rewrite_rules($wp_rewrite){
    $rules = array(
      'except/category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$'  => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/(feed|rdf|rss|rss2|atom)/?$'       => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/page/?([0-9]{1,})/?$'              => 'index.php?except_category_name=$matches[1]&paged=$matches[2]',
      'except/category/(.+?)/?$'                                => 'index.php?except_category_name=$matches[1]',
      );
    $wp_rewrite->rules = $rules + (array)$wp_rewrite->rules;
  }
  function pre_get_posts( $query ) {
      if ( ! is_admin() && $query->is_main_query() && $query->get( 'except_category_name' ) ) { // check if user asked for a non-admin page and that query contains except_category_name var
          $category = get_category_by_slug( $query->get( 'except_category_name' ) ); // get the category object
          $query->set( 'cat', '-' . $category->term_id ); // set the condition - everything except that category
          $query->is_home = FALSE; // Tell WordPress we are not at home page
      }
  }
}
$Except = new Except();

试着输入一些真实的分类名称 http://wp.example.com/except/category/news/

噢!似乎没有效。

这是为什么呢?

问题在于$query->get( ‘except_category_name’ )。WordPress的确把你的请求转换成了index.php?except_category_name=news,但是它并没有把值从查询字符串(query string)中提取出来。它只提取你明确要求的变量。这可以说是整个过程中最愚蠢的事。很显然,如果定义了一个重写规则some_var = some_value,那么我肯定是要获取some_value。不然我定义他干啥?我不知道为什么WordPress的开发者决定用其他方法,为什么要增加额外的步骤使事情变复杂。

(译者注)个人不同的看法:作者此处是从$query对象里来获取except_category_name的值的,但是默认的,WordPress只会将预留的querystring关键变量加载到$query里,列表如下Query Vars,而自定义的变量是无法从$query里获取到值的,因此需要手动注册该变量。如果不注册的话,可以从$_GET全局数组获取到值。

步骤 4: 遗漏的步骤 – Query Var

好了,我们可以再来抱怨,但工作终归是工作,我们必须完成它。我们需要更新的数组名叫$public_query_vars

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/*
Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
  function __construct() {
      register_activation_hook( __FILE__, array($this, 'activate') );
      register_deactivation_hook( __FILE__, array($this, 'deactivate') );
      add_action( 'pre_get_posts', array($this, 'pre_get_posts') );
      add_filter( 'query_vars', array($this, 'query_vars') );
  }
  function activate() {
    add_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
    global $wp_rewrite; $wp_rewrite->flush_rules(); // force call to generate_rewrite_rules()
  }
  function deactivate() {
    global $wp_rewrite; $wp_rewrite->flush_rules();
  }
  function add_rewrite_rules($wp_rewrite){
    $rules = array(
      'except/category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$'  => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/(feed|rdf|rss|rss2|atom)/?$'       => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/page/?([0-9]{1,})/?$'              => 'index.php?except_category_name=$matches[1]&paged=$matches[2]',
      'except/category/(.+?)/?$'                                => 'index.php?except_category_name=$matches[1]',
      );
    $wp_rewrite->rules = $rules + (array)$wp_rewrite->rules;
  }
  function pre_get_posts( $query ) {
      if ( ! is_admin() && $query->is_main_query() && $query->get( 'except_category_name' ) ) { // check if user asked for a non-admin page and that query contains except_category_name var
          $category = get_category_by_slug( $query->get( 'except_category_name' ) ); // get the category object
          $query->set( 'cat', '-' . $category->term_id ); // set the condition - everything except that category
          $query->is_home = FALSE; // Tell WordPress we are not at home page
      }
  }
  function query_vars($public_query_vars){
    array_push($public_query_vars, 'except_category_name');
    return $public_query_vars;
  }
}
$Except = new Except();

现在,你可以看到它把分类下的文章过滤掉了。但这还没有结束。

步骤 5: 还没完?开玩笑吧

老实说,在我们定义重写规则的那一步中,有点小问题。为了方便描述,想象一下这个场景:你有一个 Except_Category 模块,同时你还有另外一个模块 – Except_Tag ,功能一样,不过对象是标签而不是分类。现在你一个一个的启用他们,那么将会发现下面的事情

20131011000202_90002

第一个启用的模块的规则没有被保存,为什么呢?原因就是当你启用 Except_Tag 模块时,它销毁了$rewrite_rules数组,然后触发动作(action)generate_rewrite_rules。当这个动作被触发时,它发现了模块 Except_Tag 里的hook但是忽略了 Except_Category 里的,因为我们把add_action(‘generate_rewrite_rules’, … )放错了地方。幸运的是如果我们重新思考一下何时添加重写规则,我们便可以轻松的修复它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/*
Plugin Name: Except
Plugin URI: http://ruslanbes.com/projects/except
Description: Make urls like http://wp.example.com/except/category/ABCDE/ display all posts except from category ABCDE
Version: 2013.03.26
Author: Ruslan Bes < me@ruslanbes.com >
Author URI: http://ruslanbes.com/devblog
*/
define('EXCEPT_PATH', dirname(__FILE__) .'/');
class Except {
  function __construct() {
      register_activation_hook( __FILE__, array($this, 'activate') );
      register_deactivation_hook( __FILE__, array($this, 'deactivate') );
      add_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
      add_action( 'pre_get_posts', array($this, 'pre_get_posts') );
      add_filter( 'query_vars', array($this, 'query_vars') );
  }
  function activate() {
    global $wp_rewrite; $wp_rewrite->flush_rules(); // force call to generate_rewrite_rules()
  }
  function deactivate() {
    remove_action( 'generate_rewrite_rules', array($this, 'add_rewrite_rules') );
    global $wp_rewrite; $wp_rewrite->flush_rules();
  }
  function add_rewrite_rules($wp_rewrite){
    $rules = array(
      'except/category/(.+?)/feed/(feed|rdf|rss|rss2|atom)/?$'  => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/(feed|rdf|rss|rss2|atom)/?$'       => 'index.php?except_category_name=$matches[1]&feed=$matches[2]',
      'except/category/(.+?)/page/?([0-9]{1,})/?$'              => 'index.php?except_category_name=$matches[1]&paged=$matches[2]',
      'except/category/(.+?)/?$'                                => 'index.php?except_category_name=$matches[1]',
      );
    $wp_rewrite->rules = $rules + (array)$wp_rewrite->rules;
  }
  function pre_get_posts( $query ) {
      if ( ! is_admin() && $query->is_main_query() && $query->get( 'except_category_name' ) ) { // check if user asked for a non-admin page and that query contains except_category_name var
          $category = get_category_by_slug( $query->get( 'except_category_name' ) ); // get the category object
          $query->set( 'cat', '-' . $category->term_id ); // set the condition - everything except that category
          $query->is_home = FALSE; // Tell WordPress we are not at home page
      }
  }
  function query_vars($public_query_vars){
    array_push($public_query_vars, 'except_category_name');
    return $public_query_vars;
  }
}
$Except = new Except();

现在,所有准备都已就绪。一旦我们启用模块,重写规则就会生成,当然,停用模块时,规则也会被立即删除。

PS:function pre_get_posts( $query ) {

      if ( ! is_admin() && $query->is_main_query() && $query->get( 'except_category_name' ) ) { // check if user asked for a non-admin page and that query contains except_category_name var
          $category = get_category_by_slug( $query->get( 'except_category_name' ) ); // get the category object
          $query->set( 'cat', '-' . $category->term_id ); // set the condition - everything except that category
          $query->is_home = FALSE; // Tell WordPress we are not at home page
      }
  }
这个函数 中$query->set( 'cat', '-' . $category->term_id );可以有对各条件,对应的是sql语句中的  where。可以删改此语句在前台中输出:print_r($wp_query);查看

就这么多。如果你需要代码,你可以从 这里 获取。

发布在Wordpress, 主题, 二次开发, 插件. 将该链接存入书签发表评论或留个互链:互链地址.

添加一条评论

你的电子邮件不会被公开或用作其他用途。 标记*的项为必填项。

*
*

*

你可以使用以下HTML标签和属性。 <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>