您的位置:首页 > 其它

WordPress 插件开发教程 Part 3 – 钩子( Hooks )

2018-02-25 23:24 387 查看
本文是《WordPress 插件开发教程》系列教程的第 3 部分,该系列共包含以下 4 个部分:WordPress 插件开发教程 Part 1 – WordPress 插件简介
WordPress 插件开发教程 Part 2 – WordPress 插件基础
WordPress 插件开发教程 Part 3 – 钩子( Hooks )
WordPress 插件开发教程 Part 4 – 与WordPress整合
本节内容为动作钩子创建动作
为过滤器钩子创建过滤器
使用 PHP 类里面的钩子
为插件添加自定义钩子
寻找 WordPress 中的钩子
钩子是 WordPress 的精髓。他们允许插件开发人员钩进 WordPress 工作流程内部来改变它的工作,而不用直接修改核心代码。这就使得用户可以方便的升级到 WordPress 的新版本而不需要修改一行代码。如果一个开发人员修改了核心代码,这些改动在 WordPress 下一次升级的时候就会消失。升级会覆盖这些改动。使用钩子让你能够在核心以外的目录中单独开发插件,这就在升级时保证了插件代码的安全。没有钩子,插件就没办法改变 WordPress 的功能了。本节介绍的钩子系统会贯穿整个教程,同时也会在几乎每一个插件的开发中用到。在你学会了钩子的用法后,你就会明白为什么 WordPress 这个平台这么强大,并有上千个插件供几百万用户使用了。WordPress 主要有两种类型的钩子:动作钩子和过滤器钩子(action hooks and filter hooks )。第一个使得你可以在一个特定时刻上执行一个函数,第二个使得你可以操作通过钩子的输出。钩子不是仅仅针对插件的。WordPress 内部也使用钩子。如果你浏览核心代码,你就能见到很多 WordPress 自己钩自己的例子了。

动作钩子 ( actions )

动作钩子让你可以在 WordPress 加载过程中或者当某个事件发生的特定时刻触发一个函数。例如:你可能希望当 WordPress 第一次加载一个页面或者保存一篇文章时执行一个函数。你需要理解 do_action() 函数。当钩进 WordPress 中时,你的插件不会直接调用这个函数;但是你的插件几乎都会间接的使用它。
1
2
3
<?php
do_action( $tag, $arg = '' );
?>
$tag — 动作钩子的名称
$arg — 传递给已注册的动作的值。它看起来像一个单独的参数,但通常都不是这样的。动作钩子可以传递任何个数的参数,或者根本不传参数。对特殊的钩子,你需要查看 WordPress 的源码,因为参数个数在每个钩子的基础改变。
下面是一个具有多参数的动作钩子的例子:
1
2
3
<?php
do_action( $tag, $arg_1, $arg_2, $arg_3 );
?>
下面看看一个叫做 wp_head 的钩子是怎么出现在 WordPress 中的。这个钩子出现在前台的 <head> 里面。 WordPress 和插件经常用这个钩子来添加 meta 信息,样式表,和 js 脚本。
1
2
3
<?php
do_action('wp_head');
?>
当这段代码在 WordPress 中执行时,它会寻找任何为 wp_head 动作钩子注册的动作。然后按照特殊顺序执行它们。如你所见,它名叫 wp_head 但是没有传递额外的参数。动作钩子通常都这样。下面是有两个额外参数的动作钩子的例子:
1
2
3
<?php
do_action( 'save_post', $post_ID, $post );
?>
这个 save_post 的钩子传递两个参数,一个 $post_ID,一个 $post。

什么是动作?

从技术上讲,一个动作就是一个 PHP 函数。一个函数要成为一个动作,它需要被注册成一个动作钩子。在上面的部分可以看到什么是动作钩子,但是动作钩子要用作任何目的,需要有个动作为它们注册。这就是插件的来源。你开发的自定义的函数(动作)当动作钩子出发之后就可以执行一个特定任务了。要实现这个,就要使用 add_action() 函数。
1
2
3
<?php
add_action( $tag, $function, $priority, $accepted_args );
?>
$tag – 你的函数执行时代动作钩子的名称。
$function – WordPress 要调用的函数名。
$priority – 一个表示动作调用顺序的整数,默认是10。数字越小,这个函数越早被调用。
$accepted_args – 动作钩子要传递给你的函数的参数个数。默认只有一个参数。
动作钩子并不局限于单个动作。你的插件可以将多个函数添加到一个动作钩子上。其他插件,甚至是 WordPress 核心,经常将多个函数添加到同一个钩子上。现在是使用动作钩子的时候了。一个常见的动作钩子是 wp_footer 。它提供给前端用户的 WordPress 模板使用。通常它刚好在</body>前调用。在下面的例子中,将要为 wp_footer 注册一个动作并添加一条自定义信息到 footer。
1
2
34
5
6
<?php
add_action( 'wp_footer', 'boj_example_footer_message', 100 );
function boj_example_footer_message() {
echo "基于 <a href="http://wordpress.org" >WordPress </a>架设。;
}
?>
仔细看看上面的代码中如何使用 add_action() 函数的。
1
2
3
<?php
add_action( 'wp_footer', 'boj_example_footer_message', 100 );
?>
第一个参数是钩子的名字( wp_footer )。第二个参数是要回调的函数 ( boj_example_footer_message )。第三个参数是优先级 ( 100 )。这个函数比起其他钩到 wp_footer 中的函数,会在比较靠后的次序执行。如果设置成1,就较先执行。要说明,钩子可能会因为许多原因在 WordPress 执行过程中多次触发。任何添加到这个钩子中的动作每当钩子触发时都会执行一次。

动作钩子函数

你已经知道了最基本的两个动作钩子函数怎么使用;do_action() 和 add_action()。WordPress 还有其他类型的与动作钩子相关的函数用于插件开发。do_action_ref_arraydo_action_ref_array() 函数和 do_action() 函数完成同样的工作,只不过参数传递的方式不同。它并不传递通过附加参数的值来确定的多个参数,而是传递一个包含参数的数组。这个参数数组也是一个必须的参数。这个函数的目的是通过引用传递一个对象给添加到特定钩子的动作(函数)。这意味着这个函数可以不用返回就改变对象本身。
1
2
3
<?php
do_action_ref_arrray( $tag, $args );
?>
$tag – 动作钩子的名字。
$args – 要传递给注册到这个钩子的函数的参数的数组。通常,这是一个动作可以改变的对象。
下面看一个 WordPress 如何调用 do_action_ref_array() 的实例。下面的代码展示了 pre_get_posts 动作钩子。WordPress 在从数据库取得 posts 之前执行这个钩子,使得插件可以改变查询 posts 的方式。
1
2
3
<?php
do_action_ref_array( 'pre_get_posts', array( & $this ) );
?>
第一个参数 pre_get_posts 是钩子的名字。第二个参数是从数据库中查询 posts 的参数的数组。这个钩子使你可以执行基于那个参数数组的代码。假如你想安装随机的顺序来得到首页的 blog,而不是默认的通过发布时间来得到。你就需要注册一个动作到这个钩子,并改变排序顺序。
1
2
34
5
6
7
<?php
add_action( 'pre_get_posts', 'boj_randomly_order_blog_posts' );
function boj_randomly_order_blog_posts( $query ) {
if( $query -> is_home && empty( $query -> query_vars['suppress_filters']))
$query -> set( 'rderby', 'rand' );
}
?>
remove_actionremove_action() 可以删除先前添加到一个钩子的动作。代表性的,你可以删除 WordPress 默认添加的动作。要删除一个动作,这个动作必须已经用 add_action() 函数添加了。如果你在动作注册之前执行 remove_action(),那么动作并不会被从钩子中删除。如果动作被成功删除,则函数返回 true,否则返回 false。
1
2
3
<?php
remove_action( $tag, $function_to_remove, $priority, $accepted_args );
?>
参数类似于 do_action()。要成功的从一个钩子中删除一个动作, $tag, $function_to_remove, 和 $priority 必须完全的复合 do_action() 中使用的参数。否则动作不会被溢出,同时 remove_action() 返回 flase。我们看一个叫做 rel_canonical 的 WordPress 默认动作。这个动作在 <head> 和 </head> 元素之间添加一个 canonical 链接。
1
2
3
<?php
add_action( 'wp_head', 'rel_canonical' );
?>
要删除这个动作,就要使用 remove_action() 函数。你需要定义 $tag 和 $function_to_rmove 参数。这里你不用添加 $priority 因为先前定义动作的时候没有明确指定优先级。
1
2
3
<?php
remove_action( 'wp_head', 'rel_canonical' );
?>
WordPress、plugin 或者 theme 添加的任何动作都可以在插件中删除。通常只删除 WordPress 添加的动作。许多默认的动作都定义在 wp-includes/default-filters.php 文件中。通过浏览这个文件你就会明白 WordPress 是如何使用动作钩子的。remove_all_actions在有些插件中,可能需要删除所有特定 tag 或者 特定 tag + 特定优先级的所有钩子。使用 remove_all_actions() 可以一次删除所有符合条件的动作。
1
2
3
<?php
remove_all_actions( $tag, $priority );
?>
$priority 参数是可选的,默认是 false。如果你设置了这个参数,那么只有这个优先级的动作会被删除。下面的例子从 wp_head 动作钩子中删除不管任何优先级的动作。
1
2
3
<?php
remove_all_actions( 'wp_head' );
?>
在使用这个函数的时候必须要小心。其他 plugin 或者 theme 可能添加了你不知道动作。这就可能破坏插件应有的功能。通常应该保持你的代码尽可能的特殊。在大多数情况下,你应该使用 remove_action() 函数来代替。has_action有的时候需要确定一个钩子是否包含一些动作,或者一个特定的动作是否已经添加到了钩子里面。has_action() 提供了这些功能。
1
2
3
<?php
has_action( $tag, $function_to_check );
?>
has_action() 函数的返回值是 Boolean 或者 一个整型值。如果 $function_to_check 参数为空,那么如果有动作已经添加到了钩子中就返回 true,反之,返回 false。而如果 $function_to_check 设置了,而且这个函数已经添加到了钩子里面,则返回该动作的优先级,否则返回 false。下面的例子中,根据 wp_footer 动作钩子中是否有注册的动作来确定显示的信息。
1
2
34
5
6
<?php
if( has_action( 'wp_footer' ) )
echo '<p> footer 中已经注册有动作了。</p>';
else
echo '<p> footer 中还没有注册动作。</p>';
?>
下面看一个 WordPress 核心添加到 wp_footer 中的动作。wp_print_footer_script() 默认注册给这个钩子。
1
2
3
<?php
add_action( 'wp_footer', 'wp_print_footer_scripts' );
?>
did_actiondid_action() 使你的插件可以检查一个动作钩子是否已经被执行,或者记录执行的次数。这也意味着这一次页面的加载过程中有些动作被执行了多次。
1
2
3
<?php
did_action( $tag );
?>
这个参数返回动作已经执行的次数,如果还未执行,返回 false。这个函数的一般用途是判断一个动作钩子是否已经被触发,并执行基于 did_action() 的返回值的代码。下面的例子中,如果 plugins_loaded 动作钩子已经被触发,就定义一个 PHP 常量。
1
2
34
<?php
if ( did_action( 'plugins_loaded' ) )
define( 'BOJ_MYPLUGIN_READY' ,true );
?>
register_activation_hook 和 register_deactivation_hook在第二章中已经介绍了这两个函数。

常用的动作钩子

WordPress 有许多动作钩子,有一些是很常用的。plugins_loaded对插件开发者来说,plugins_loaded 动作钩子也许是最重要的动作钩子了。它在大多数 WordPress 文件加载完成之后,并在pluggable 函数和 WordPress 开始执行任何东西之前触发。在大多数的插件中,在这个钩子触发之前,不应该执行其他的代码。plugins_loaded 在所有用户启用的插件都被 WordPress 加载之后执行。这也是在加载过程中插件开发这最早能用到的钩子。WordPress 的插件应该在这个钩子中执行安装。其他动作也应该添加到这个钩子的回调函数中。下面的例子中,使用前面部分创建的 boj_example_footer_message 动作。要把它添加到钩到 plugins_loaded 钩子中的安装动作中,而不是单独调用它。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
<?php
 
add_action( 'plugins_loaded', 'boj_footer_message_plugin_setup' );
 
function boj_footer_message_plugin_setup() {
 
/* 添加 footer 信息动作 */
 
add_action( 'wp_footer', 'boj_example_footer_message', 100 );
 
}
 
function boj_example_footer_message() {
 
echo "基于 <a href="http://wordpress.org" >WordPress </a>架设。;
 
}
 
?>
创建一个安装函数并把它钩到 plugins_loaded 中。这样做就可以确保不会由于特定的 WordPress 函数还没有加载而触发错误。initinit 钩子在大多数的 WordPress 都建立之后。WordPress 同样添加许多内部的功能到这个钩子中,例如 post types 和 taxonomies 的厨厕以及默认 widgets 的初始化。因为这时几乎 WordPress 中的所有内容都就绪了,当 WordPress 的所有信息都可用时,你的插件使用这个钩子差不多可以做任何需要的事情了。下面的例子中,为用户添加了给 pages 写摘要的功能。这应该在 init 中执行,因为 “page” post type 在这时使用 add_post_type_support() 函数来创建。( 详见 Part-11, “扩展 posts”)
1
2
34
5
6
7
8
9
10
11
<?php
 
add_action( 'init', 'boj_add_excerpts_to_pages' );
 
function boj_add_excerpts_to_pages() {
 
add_post_type_support( 'page', array( 'excerpt' ) );
 
}
 
?>
admin_menuadmin_menu 钩子在管理员页面加载的时候调用。无论何时你的插件直接在管理页面下工作,你都要用这个钩子来执行你的代码。下面的例子添加了一个内容是 BOJ Settings 的 sub-menu 项到 WordPress 管理页面的设置菜单。(详见:Part-7,”插件设置”)
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
<?php
 
add_action( 'admin_menu', 'boj_admin_settings_page' );
 
function boj_admin_settings_page() {
 
add_options_page(
 
'BOJ Settings',
 
'BOJ Settings',
 
'manage_options',
 
'boj_admin_settings',
 
'boj_admin_settings_page'
 
);
 
}
 
?>
template_redirecttemplate_redirect 动作钩子很有用,因为它是 WordPress 知道用户正在浏览的页面的关键。它在特定的页面选择 theme template 之前执行。在只在网站的前端触发,并不在管理员页面触发。当你需要为特定的页面加载代码的时候,这个钩子很有用。下面的例子中,仅仅为 singular post 加载一个样式表文件。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
<?php
 
add_action( 'template_redirect', 'boj_singular_post_css' );
 
function boj_singular_post_css() {
 
if( is_singular( 'post' ) ) {
 
wp_enqueue_style (
 
'boj-singular-post',
 
'boj-example.css',
 
false,
 
0.1,
 
'screen'
 
);
 
}
 
}
 
?>
wp_head在网站的前端,WordPress 的模板调用 wp_head() 函数,会触发 wp_head 钩子。插件使用这个钩子在 <head> 和 </head> 标签之间添加 HTML。下面的例子中在前端添加一个 meta description。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
<?php
 
add_action( 'wp_head', 'boj_front_page_meta_description' );
 
function boj_front_page_meta_description() {
 
/* 得到站点描述 */
 
$description = esc_attr( get_bloginfo( 'description' ) );
 
/* 如果 description 设置了,显示 meta 元素 */
 
if ( !empty( $description ) )
 
echo '<meta name="description" content="'. $description. '"/>';
 
}
 
?>
有些插件错误的使用了 wp_head 动作钩子来添加 JavaScript 代码,实际上应该使用 wp_enqueue_script() 函数的。( 详见:Part-12,”JavaScript 和 AJAX “)。唯一一种使用这个钩子来添加 JavaScript 的情形是当 JavaScript 代码不在一个单独的文件中时。

过滤器 Filters

过滤器钩子和动作钩子有很大的区别。它让你可以控制代码的输出。动作钩子是让你插入代码,而过滤器钩子让你重写 WordPress 传递给钩子的代码。你的函数会对输出进行”过滤”。要掌握过滤器钩子的概念,必须首先明白 WordPress 的 apply_filters() 函数是如何工作的。
1
2
34
5
<?php
 
apply_filters( $tag, $value );
 
?>
$tag – 过滤器钩子的名字。
$value – 传递给任何添加到这个钩子的过滤器的参数。这个函数可以添加任意个额外的 $value 参数传递给过滤器。
注意:在写一个过滤器的时候 $value 必须返回给 WordPress。下面是 WordPress 核心的一个过滤器钩子的例子:
1
2
34
5
<?php
 
apply_filters( 'template_include', $template );
 
?>
这个例子中,template_include 是一个过滤器钩子的名字,$template 是一个可以通过注册到这个过滤器钩子的过滤器改变的文件名。

什么是过滤器?

一个过滤器是一个注册到过滤器钩子的函数。这个函数最少接受一个参数并在执行完代码后返回那个参数。没有过滤器的过滤器钩子没有任何作用。插件开发者可以通过过滤器钩子改变不同的变量 – 从字符串到多位数组。当一个过滤器钩子被 apply_filters() 函数调用时,所有注册到这个钩子的过滤器都会被执行。要添加一个过滤器,使用 add_filter() 函数。
1
2
34
5
<?php
 
add_filter( $tag, $function, $priority, $accepted_args );
 
?>
和动作钩子添加动作类似。$accepted_args 是过滤器函数 $function 接受的参数个数,默认是1。你的函数必须至少接受一个参数并返回。可以给一个过滤器钩子添加多个过滤器。同样其他 WordPress 插件也可以给这个钩子添加过滤器。过滤器钩子并不限制给一个钩子。注意:因为每个过滤器都必须返回一个值供其他过滤器使用。如果你的函数没有返回值,那就可能会破坏整个 WordPress 或者其他的插件。下面看看 wp_title 过滤器钩子,它是负责页面的 <title> 元素的过滤器钩子。
1
2
34
5
<?php
 
apply_filters( 'wp_title', $title, $sep, $seplocation );
 
?>
wp_title – 钩子名。
$title – 一个字符串,要过滤并返回给 WordPress 的值。
$sep – 说明 <title> 元素之间的分隔符是什么。
$seplocation – 分隔符的位置。下一个例子中要用到。
现在写一个函数来过滤 $title 的输出 – 在页面的 title 后面附加站点的名字:
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
21
<?php
 
add_filter( 'wp_title', 'boj_add_site_name_to_title', 10, 2 );
 
function boj_add_site_name_to_title( $title, $seq ) {
 
/* 得到网站名称 */
 
$name = get_bloginfo( 'name' );
 
/* 附加到 $title 变量。 */
 
$title .= $sqp.' '.$name;
 
/* 返回 $title */
 
return $title;
 
}
 
?>
boj_add_site_name_to_title() 函数修改 $title 参数并返回给 WordPress。$sep 参数在函数中使用,但没有返回。过滤器钩子函数除了前面提到的 apply_filters() 和 add_filter() 函数,WordPress 还提供其他的操作过滤器钩子的函数。apply_filters_ref_array类似于动作钩子里面的 do_action_ref_array() 函数。
1
2
34
5
<?php
 
apply_filters_ref_array( $tag, $args );
 
?>
假设你要执行一个一般的 WordPress 没有的复杂的数据库查询来加载首页的 posts。WordPress 提供了一个叫做 posts_results 的过滤器钩子使得你可以改变它。下面是 WordPress 核心中的代码:
1
2
34
5
6
7
8
9
<?php
 
$this -> posts = apply_filters_ref_array(
 
'posts_results', array( $this -> posts, & $this )
 
);
 
?>
这个过滤器钩子向所有注册到它的过滤器传递一个 post 对象的数组。下面的例子,你完全重写这个 post 对象的数组并用自定义的内容代替。默认情况下,WordPress 查询 post 类型的 posts。下面改成查询 page 类型的 psots 并显示在首页。这段代码使用了 wpdb 类,在 part-6 “插件安全” 中将详细介绍。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
48
49
50
5152
53
54
55
56
57
<?php
 
add_filter( 'posts_result', 'boj_custom_home_page_posts' );
 
function boj_cumstom_home_page_posts( $result ) {
 
global $wpdb, $wp_query;
 
/* 检查是否在首页 */
 
if ( is_home() ) {
 
/* 每页的 post 个数 */
 
$per_page = get_option( 'posts_per_page' );
 
/* 得到当前页 */
 
$paged = get_query_var( 'paged' );
 
/* 设置 $page 变量 */
 
$page = ( ( 0 == $paged || 1 == $paged ) ? 1 : absint( $paged ) );
 
/* 设置偏移的 posts 的个数 */
 
$offset = ( $page - 1 ) * $per_page. ',';
 
/* 通过 $offset 和 要显示的 posts 数量来设置 limit */
 
$limits = 'LIMIT'. $offset . $per_page;
 
/* 从数据库查询结果 */
 
$result = $wpdb -> get_results("
 
SELECT SQL_CALC_FOUND_ROWS $wpdb -> posts. *
 
FROM $wpdb -> posts
 
WHERE 1 = 1
 
AND post_type = 'page'
 
AND post_status = 'publish'
 
ORDER BY post_title ASC $limits "
 
);
 
}
 
return $result;
 
}
 
?>
remove_filter
1
2
34
5
<?php
 
remove_filter( $tag, $function_to_remove, $priority, $accepted_args );
 
?>
这和前面的 remove_action 类似。下面看看 WordPress 定义在 wp-includes/default-filters.php 页面中的默认的过滤器。其中一个有意思的过滤器叫做 wpautop(),它将双换行转换成 HTML 的 <p> </p>。这也就是我们在 HTML 发布内容时,直接回车便可在最终前端显示的时候换行的原因。它在核心代码中的几个钩子中都执行。下面是其中一个实例:
1
2
34
5
<?php
 
add_filter( 'the_content', 'wpautop' );
 
?>
这将 wpautop() 过滤器应用到 post 的内容中,把每个换行都转换成段落( <p> )。但是也许有的客户不希望他的内容自动变成段落。那么你就可以使用 remove_filter() 函数从 the_content 钩子中删除这个过滤器。
1
2
34
5
<?php
 
remove_filter( 'the_content', 'wpautop' );
 
?>
因为在 add_filter 的时候没有定义优先级,所以这里也不需要。remove_all_filters和前面的remove_all_actions类似。
1
2
34
5
<?php
 
remove_all_filters( $tag, $priority );
 
?>
has_filter和前面的 has_action 类似。current_filter同样类似于 did_action。不过它不仅仅对过滤器钩子有效,同样对动作钩子也有效,所以它返回的是当前的 action 或者 filter 钩子。这个函数在你对多个钩子使用单个函数,但是需要依赖不同的钩子执行不同的内容的时候非常的有用。例如,客户希望在 post 标题 和内容中限制一些内容,但是这两个限制的minganci的集合是不同的。使用 current_filter() 函数来根据钩子设置不同的minganci表就可以实现用一个函数同时过滤 the_content 和 the_title。使用下面的代码,你可以把minganci替换成**。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
<?php
 
add_filter( 'the_content', 'boj_replace_unwanted_words' );
 
add_filter( 'the_title', 'boj_replace_unwanted_words' );
 
function boj_replace_unwanted_words( $text ) {
 
/* 如果过滤器钩子是 the_content */
 
if( 'the_content' == current_filter() )
 
$words = array( 'min', 'gan', 'ci' );
 
/* 如果钩子是 the_title */
 
elseif( 'the_title' == current_filter() )
 
$words = array( 'zhen', 'de', 'hen', 'min', 'gan' );
 
/* 替换minganci */
 
$text = str_replace( $words, '**', $text );
 
return $text;
 
}
 
?>

快速返回函数

你经常需要写一个函数返回一个常用的值给一个过滤器钩子,例如 true,false,或者一个空数组。你甚至尝试使用 PHP 的 create_function() 函数来快速返回一个值。WordPress 提供几个函数处理这种情况。下面是例子禁用了 user contact 方法 – 在 WordPress 的个人用户管理页面中的一系列 <input>。要禁用这些表单项,你需要返回一个空数组。通常,你必须添加过滤器钩子调用和函数。
1
2
34
5
6
7
8
9
10
11
<?php
 
add_filter( 'user_contactmethods', 'boj_return_empty_array' );
 
function boj_return_empty_array() {
 
return array();
 
}
 
?>
写这样的代码一两次并没什么。但是写一个返回空数组的函数太傻了。WordPress 使之简单化了。因为要禁用这些表单项,你只需要使用 WordPress 的 __return_empty_array() 函数作为过滤器来快速返回一个空数组。如下:
1
add_filter( 'user_contactmethods', '__return_empty_array' );
还有几个类似的快速返回函数:__return_false
__return_true
__return_zero
如果上面的函数不符合你的要求,你还可以创建自己的快速返回函数。

常用的过滤器钩子

WordPress 上百种过滤去钩子提供给插件开发者。下面介绍一些常用的过滤器钩子。the_contentthe_content 可以说是用的最多的过滤器钩子了。没有内容,一个网站一点用处也没有。内容是一个网站上最重要的东子,插件使用这个钩子为网站添加许多特色。the_content 钩子向所有注册给它的过滤器传递一个 post 的内容。之后由过滤器来控制内容,通常添加一些格式化或者附加而外的一些信息。下面的例子根据 post 分类,当用户阅读一篇 post 时显示一个附加的相关 post 列表到 the_content。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
48
49
50
5152
53
54
55
56
57
58
59
60
6162
63
64
65
66
67
68
69
70
7172
73
74
75
76
77
78
79
80
8182
83
84
85
86
<?php
 
add_filter( 'the_content', 'boj_add_related_posts_to_content' );
 
function boj_add_related_posts_to_content( $content ) {
 
/* 如果不是单篇文章,直接返回 content */
 
if ( !is_singular( 'post' ) )
 
return $content;
 
/* 得到当前 post 的分类 */
 
$terms = get_the_terms( get_the_ID(), 'category' );
 
/* 循环分类,并将它们的 ID 放到一个数组中 */
 
$categories = array();
 
foreach ( $terms as $term )
 
$categories[] = $term -> term_id;
 
/* 从数据库查询相同分类的 posts */
 
$loop = new WP_Query(
 
array(
 
'cat__in' => $categories,
 
'posts_per_page' => 5,
 
'post__not_in' => array( get_the_ID() ),
 
'orderby' => 'rand'
 
)
 
);
 
 
/* 是否有相关 posts 存在 */
 
if( $loop -> have_posts() ) {
 
/* 开始 ul */
 
$content .= '<ul class="related-posts">';
 
while( $loop -> have_posts() {
 
$loop -> the_post();
 
/* 添加 post 标题 */
 
$content .= the_title (
 
'<li><a href="'.get_permalink().'">',
 
'</a></li>',
 
false
 
);
 
}
 
/* 结束 ul */
 
$content .= '</ul>';
 
/* 重置 query */
 
wp_reset_query();
 
}
 
/* 返回 content */
 
$return $content;
 
}
 
?>
the_title文章的标题几乎和内容一样重要,所以 the_title 也是一个常用的过滤器钩子。你可以使用这个钩子添加信息,或者直接重写。应用给 the_title 钩子的一个有用的过滤器就是去除标题中 HTML 标签的过滤器函数。用户有时会添加一些标签到标题中,这可能会破坏正常的格式。使用下面的代码,你可以过滤掉所有用户可能添加到标签。
1
2
34
5
6
7
8
9
10
1112
13
<?php
 
add_filter( 'the_title', 'boj_strip_tags_from_titles' );
 
function boj_strip_tags_from_title( $title ) {
 
$title = strip_tags( $title );
 
$return $title;
 
}
 
?>
同样 comment_text 也很常用下面的例子中,检查一条评论是否是网站的注册用户发表的。如果是注册用户,你可以附加一段用户信息的说明( 详见:Part-8,”用户” )
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
<?php
 
add_filter( 'comment_text', 'boj_add_role_to_comment_text' );
 
function boj_add_role_to_comment_text( $text ) {
 
global $comment;
 
/* 检查是否是注册用户 */
 
if( $comment -> user_id > 0 ) {
 
/* 新建一个用户对象 */
 
$user = new WP_User( $comment -> user_id );
 
/* 如果用户有一个角色,添加到评论内容中 */
 
if( is_array( $user -> roles ) )
 
$text .= '<p>User Role: ' .$user -> roles[0]. '</p>';
 
}
 
return $text;
 
}
 
?>
template_includetemplate_include 是其他一些更特殊的过滤器钩子的一类”杂物包”( catchall )过滤器钩子。front_page_template
home_template
single_template
page_template
attachment_template
archive_template
category_template
tag_template
author_template
date_template
search_template
404_template
index_template
它用在 theme template 过滤器后面,当当前页被选中后。WordPress 根据读者当前浏览的页面来选择一个模板。你可以为每一个独立的过滤器钩子添加一个过滤器,也可以在最后使用 template_include 钩子一起过滤他们。比如你想按照你的插件的标准构造一个模板层级结构,而不是使用 WordPress 默认的模板层级。template_include 和上面列表中的钩子可以满足你。下面的例子中,根据分类判断一个 posts 的模板是否存在。默认情况下,WordPress 先检查 single.php,如果不存在,再检查 index.php文件。而你的函数查找一个叫做 single-category-$slug.php ( $slug 是分类的别名 )的文件。所以如果用户有一个叫 art 的分类,同时一个模板的名字叫做 single-category-art.php,那么这个文件会被用来代替 single.php。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
<?php
 
add_filter( 'single_template', 'boj_single_template' );
 
function boj_single_template( $template ) {
 
global $wp_query;
 
/* 检查是否在浏览单个 post */
 
if( is_singular( 'post' ) ) {
 
/* 获得 post ID */
 
$post_id = $wp_query -> get_queried_object_id();
 
/* 获得 post 的分类 */
 
$terms = get_the_terms( $post_id, 'category' );
 
/* 循环分类,添加别名作为文件名的一部分 */
 
$template = array();
 
foreach ( $terms as $term )
 
$templates[] = "single-category-{$term->slug}.php";
 
/* 检查模板是否存在 */
 
$locate = locate_template( $templates );
 
/* 如果找到,让它成为新模板 */
 
if( !empty ( $locate ) )
 
$template = $locate;
 
}
 
/* 换回模板文件名 */
 
return $template;
 
}
 
?>

使用一个类中的钩子

前面已经讲了许多通过 PHP 函数来使用动作钩子和过滤器钩子的例子。在类中添加一个方法作为一个动作或者过滤器的时候,格式和 add_action() 和 add_filter() 略微有些不同。一般来说,插件使用函数而不是类中的方法作为动作或者过滤器。但是,可能有些时候使用类更适合,所以你要知道如何类在类中将方法注册到钩子。前面已经提到的注册函数到一个动作钩子的方法:
1
2
34
5
<?php
 
add_action( $tag, $function_to_add );
 
?>
当在类中将方法作为 $function_to_add 参数时,你必须把 $function_to_add 变成一个数组,其中 & $this 作为第一个参数,方法名作为第二个参数:
1
2
34
5
<?php
 
add_action( $tag, array( & $this, $method_to_add ) );
 
?>
对于过滤器钩子也是一样。一般的将函数添加到一个过滤器钩子类似于:
1
2
34
5
<?php
 
add_filter( $tag, $function_to_add );
 
?>
当使用类的方法的时候,要改成:
1
2
34
5
<?php
 
add_filter( $tag, array( & $this, $method_to_add ) );
 
?>
下面的例子中,创建了一个类,包含一个构造函数,一个作为动作的方法,和一个作为过滤器的方法。add_filters() 方法检查用户是否在浏览单篇 post。如果是 content() 方法附加最后的修改时间到 post 的内容中。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
<?php
 
class BOJ_My_Plugin_Loader {
 
/* 类的构造函数 */
 
function BOJ_My_Plugin_Loader() {
 
/* 为 'template_redirect' 钩子添加 'singular_check' 方法 */
 
add_action( 'template_redirect', array( & $this, 'singular_check' ) );
 
}
 
/* 作为动作的方法 */
 
function singular_check() {
 
/* 如果在看单个文章,过滤内容 */
 
if ( is_singular() )
 
add_filter( 'the_content', array( & $this, 'content' ) );
 
}
 
/* 作为过滤器的方法 */
 
function content( $content ) {
 
/* 得到 post 的最后修改时间 */
 
$date = get_the_modified_time( get_option( 'date_format' ) );
 
/* 附加修改时间到 content */
 
$content .= '<p> 最后修改于:' .$date. '</p>';
 
return $content;
 
}
 
}
 
$boj_myplugin_loader = new BOJ_My_Plugin_Loader();
 
?>

创建自定义钩子

插件不但可以使用内核的内置钩子,他们也可以创建自定义的钩子供其他插件和模板使用。插件可以使用4个可用函数中的一个来创建自定义钩子。do_action()
do_action_ref_array()
apply_filters()
apply_filters_ref_array()
前两个创建自定义动作钩子,后两个创建自定义过滤器钩子。

创建自定义钩子的优点

自定义钩子使得你的插件更灵活,使其可以被其他插件扩展,让你可以钩到你的整个插件自己的其他执行过程中。使用自定义钩子还可以防止用户直接修改你的插件。这一点的重要性在于,当你更新你的插件时,用户不会失去他们修改的内容。

自定义动作钩子实例

在这个例子中,建立了一个插件安装函数。这个函数定义了一个可以更换的常量。别的插件也可以在这个钩子上执行任何代码。因为你在那一点上提供了钩子。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
<?php
 
add_action( 'plugins_loaded', 'boj_myplugin_setup' );
 
function boj_myplugin_setup() {
 
/* 允许动作最先触发 */
 
do_action( 'boj_myplugin_setup_pre' );
 
/* 检查 root slug 是否定义 */
 
if( !defined( 'BOJ_MYPLUGIN_ROOT_SLUG' ) )
 
define( 'BOJ_MYPLUGIN_ROOT_SLUG', 'articles' );
 
}
 
?>
其他插件或者模板可以钩到 boj_myplugin_setup_pre 来执行任何函数。比如你想把 BOJ_MYPLUGIN_ROOT_SLUG 常量从 ‘articles’ 改为 ‘papers’ ,你可以建立一个动作并添加到这个钩子中:
1
2
34
5
6
7
8
9
10
11
<?php
 
add_action( 'boj_myplugin_setup_pre', 'boj_define_myplugin_constants' );
 
function boj_define_myplugin_constants() {
 
define( 'BOJ_MYPLUGIN_ROOT_SLUG', 'papers' );
 
}
 
?>

自定义过滤器钩子实例

假设有一个函数显示一个具有一个特定阐述的文章列表。你也许希望其他人能够过滤那个参数或者过滤最终结果。下面的例子中,写一个函数根据收到的评论条数列取了前10的文章。这个函数让用户可以在从数据库获取数据前过滤这个参数,并且可以过滤最终输出的 HTML 列表。
1
2
34
5
6
7
8
9
10
1112
13
14
15
16
17
18
19
20
2122
23
24
25
26
27
28
29
30
3132
33
34
35
36
37
38
39
40
4142
43
44
45
46
47
48
49
50
5152
53
54
55
<?php
 
function boj_posts_by_comments() {
 
/* 默认参数 */
 
$args = array(
 
'post_type' => 'post',
 
'posts_per_page' => 10,
 
'order' => 'DESC',
 
'oerderby' => 'comment_count'
 
);
 
/* 应用过滤器 */
 
$args = apply_filters( 'boj_posts_by_comments_args', $args );
 
/* 设置输出变量 */
 
$out = '';
 
/* 由给定的参数从数据库查询文章 */
 
$loop = new WP_Query( $args );
 
/* 检查是否返回结果 */
 
if( $loop -> have_posts() ) {
 
$out .= '<ul class="posts-by-comments" >';
 
while( $loop -> have_posts() ) {
 
$loop -> the_post();
 
$out .= the_title( '<li>', '</li>', false );
 
}
 
$out .= '</ul>';
 
}
 
$out = apply_filters( 'boj_posts_by_comments', $out );
 
echo $out;
 
}
 
?>
要过滤参数,给 boj_posts_by_comments_args 添加一个过滤器。比如你希望把数量从默认的10变成15,添加下面的过滤器:
1
2
34
5
6
7
8
9
10
1112
13
<?php
 
add_filter( 'boj_posts_by_comments_args', 'boj_change_posts_by_comments_args' );
 
function boj_change_posts_by_comments_args( $args ) {
 
$args['posts_per_page'] = 15;
 
return $args;
 
}
 
?>
要过滤最后的 HTML 输出,添加一个过滤器到 boj_posts_by_comments。比如你想把 ul 改成 ol。
1
2
34
5
6
7
8
9
10
1112
13
14
15
<?php
 
add_filter( 'boj_posts_by_comments', 'boj_change_posts_by_comments' );
 
function boj_change_posts_by_comments( $out ) {
 
$out = str_replace( '<ul', '<ol', $out );
 
$out = str_replace( '</ul>', '</ol>', $out );
 
return $out;
 
}
 
?>

上哪找钩子?

要给出 WordPress 中所有钩子的列表几乎是不可能的。前面我们讨论了一些常用的动作和过滤器钩子,这一节仅仅讨论一小部分 WordPress 提供的钩子。WordPress 的新版本会加入新的钩子。最终查看不同版本的内核可以让你找到可以用在插件中的新钩子。

在内核中搜索钩子

作为一个插件开发这,你应该熟悉 WordPress 的内核。寻找钩子能很好的帮助你熟悉 WordPress 内核是如何工作的。没有更好的方法来搞明白 PHP 代码在内核中是如何工作的了。要寻找钩子的一个简单的方法是在编辑器中打开一个 WordPress 文件然后搜索下面的四个词:do_action
do_action_ref_array
apply_filters
apply_filters_ref_array
这四个函数,每一个都创建一个钩子。

变量钩子

在 WordPress 的内核中找钩子的时候,你会遇到变量钩子。通常钩子的名字是一个静态的字符串。但是变量钩子的名字跟着特定的变量而改变。一个很好的例子就是 load-$pagenow 动作钩子。变量 $pagenow 根据 WordPress 中当前浏览的 admin 页面而改变。这个钩子如下:
1
2
34
5
<?php
 
do_action( "load-$pagenow" );
 
?>
变量 $pagenow 会变成当前访问页面的名字。例如 new post 页面的钩子是 load-post-new.php,而编辑页面的是 load-post.php。这就使得插件仅仅对特定的 admin 页面执行代码。WordPress 有几个动作和过滤器钩子的名称里面是含有变量的。通常,这些钩子的名字变成给定的内容,使得插件开发者可以在特定的环境下执行代码。

钩子参考列表

虽然在核心里面搜索钩子有助于你增长经验,但是有时你需要一些网上的参考列表。WordPress 在 Codex 中有官方的钩子参考列表。http://codex.wordpress.org/Plugin_API/Action_Reference
http://codex.wordpress.org/Plugin_API/Filter_Reference
在 Part-18,开发人员工具箱,将介绍更多帮助插件开发者的工具。

总结

钩子是创建 WordPress 插件的最重要的一环了。每次你开始创建一个插件,你都要把你的插件钩到 WordPress 的动作钩子和过滤器钩子中。钩子是插件开发中必不可少的工具。在了解了钩子之后,就是时候开始创建插件了。注:本文出自《Professional WordPress Plugin Development》一书,由 sixpoint.me 翻译,倡萌整编。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: