WordPress 过滤器 Filters

过滤器钩子和动作钩子有很大的区别。它让你可以控制代码的输出。动作钩子是让你插入代码,而过滤器钩子让你重写 WordPress 传递给钩子的代码。你的函数会对输出进行”过滤”。

要掌握过滤器钩子的概念,必须首先明白 WordPress 的 apply_filters() 函数是如何工作的。

<!--?php apply_filters( $tag, $value ); ?-->

$tag – 过滤器钩子的名字。
$value – 传递给任何添加到这个钩子的过滤器的参数。这个函数可以添加任意个额外的 $value 参数传递给过滤器。
注意:在写一个过滤器的时候 $value 必须返回给 WordPress。

下面是 WordPress 核心的一个过滤器钩子的例子:

<!--?php apply_filters( 'template_include', $template ); ?-->

这个例子中,template_include 是一个过滤器钩子的名字,$template 是一个可以通过注册到这个过滤器钩子的过滤器改变的文件名。

什么是过滤器?

一个过滤器是一个注册到过滤器钩子的函数。这个函数最少接受一个参数并在执行完代码后返回那个参数。没有过滤器的过滤器钩子没有任何作用。插件开发者可以通过过滤器钩子改变不同的变量 – 从字符串到多位数组。

当一个过滤器钩子被 apply_filters() 函数调用时,所有注册到这个钩子的过滤器都会被执行。要添加一个过滤器,使用 add_filter() 函数。

<!--?php add_filter( $tag, $function, $priority, $accepted_args ); ?-->

和动作钩子添加动作类似。$accepted_args 是过滤器函数 $function 接受的参数个数,默认是1。你的函数必须至少接受一个参数并返回。

可以给一个过滤器钩子添加多个过滤器。同样其他 WordPress 插件也可以给这个钩子添加过滤器。过滤器钩子并不限制给一个钩子。注意:因为每个过滤器都必须返回一个值供其他过滤器使用。如果你的函数没有返回值,那就可能会破坏整个 WordPress 或者其他的插件。

下面看看 wp_title 过滤器钩子,它是负责页面的元素的过滤器钩子。

<!--?php apply_filters( 'wp_title', $title, $sep, $seplocation ); ?-->

wp_title – 钩子名。 $title – 一个字符串,要过滤并返回给 WordPress 的值。 $sep – 说明元素之间的分隔符是什么。 $seplocation – 分隔符的位置。下一个例子中要用到。 现在写一个函数来过滤 $title 的输出 – 在页面的 title 后面附加站点的名字:

<!--?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() 函数。

<!--?php apply_filters_ref_array( $tag, $args ); ?-->

假设你要执行一个一般的 WordPress 没有的复杂的数据库查询来加载首页的 posts。WordPress 提供了一个叫做 posts_results 的过滤器钩子使得你可以改变它。下面是 WordPress 核心中的代码:

<!--?php $this ---> posts = apply_filters_ref_array(
'posts_results', array( $this -> posts, & $this )
);
?>

这个过滤器钩子向所有注册到它的过滤器传递一个 post 对象的数组。下面的例子,你完全重写这个 post 对象的数组并用自定义的内容代替。默认情况下,WordPress 查询 post 类型的 posts。下面改成查询 page 类型的 psots 并显示在首页。

这段代码使用了 wpdb 类,在 part-6 “插件安全” 中将详细介绍。

<!--?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

 

<!--?php remove_filter( $tag, $function_to_remove, $priority, $accepted_args ); ?-->

这和前面的 remove_action 类似。

下面看看 WordPress 定义在 wp-includes/default-filters.php 页面中的默认的过滤器。其中一个有意思的过滤器叫做 wpautop(),它将双换行转换成 HTML 的

&lt/p>。这也就是我们在 HTML 发布内容时,直接回车便可在最终前端显示的时候换行的原因。它在核心代码中的几个钩子中都执行。下面是其中一个实例:

<!--?php add_filter( 'the_content', 'wpautop' ); ?-->

这将 wpautop() 过滤器应用到 post 的内容中,把每个换行都转换成段落(

)。但是也许有的客户不希望他的内容自动变成段落。那么你就可以使用 remove_filter() 函数从 the_content 钩子中删除这个过滤器。

<!--?php remove_filter( 'the_content', 'wpautop' ); ?-->

因为在 add_filter 的时候没有定义优先级,所以这里也不需要。

remove_all_filters

和前面的remove_all_actions类似。

<!--?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替换成**。

<!--?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 的个人用户管理页面中的一系列 。要禁用这些表单项,你需要返回一个空数组。通常,你必须添加过滤器钩子调用和函数。

<!--?php add_filter( 'user_contactmethods', 'boj_return_empty_array' ); function boj_return_empty_array() { return array(); } ?-->

写这样的代码一两次并没什么。但是写一个返回空数组的函数太傻了。WordPress 使之简单化了。因为要禁用这些表单项,你只需要使用 WordPress 的 __return_empty_array() 函数作为过滤器来快速返回一个空数组。如下:

add_filter( 'user_contactmethods', '__return_empty_array' );
还有几个类似的快速返回函数:

__return_false
__return_true
__return_zero

如果上面的函数不符合你的要求,你还可以创建自己的快速返回函数。

常用的过滤器钩子

WordPress 上百种过滤去钩子提供给插件开发者。下面介绍一些常用的过滤器钩子。

the_content

the_content 可以说是用的最多的过滤器钩子了。没有内容,一个网站一点用处也没有。内容是一个网站上最重要的东子,插件使用这个钩子为网站添加许多特色。

the_content 钩子向所有注册给它的过滤器传递一个 post 的内容。之后由过滤器来控制内容,通常添加一些格式化或者附加而外的一些信息。下面的例子根据 post 分类,当用户阅读一篇 post 时显示一个附加的相关 post 列表到 the_content。

<!--?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 .= '</pre>
<ul>
<ul>';</ul>
</ul>
&nbsp;
<ul>
<ul>while( $loop -> have_posts()) {</ul>
</ul>
&nbsp;
<ul>
<ul>$loop -> the_post();</ul>
</ul>
&nbsp;
<ul>
<ul>/* 添加 post 标题 */</ul>
</ul>
&nbsp;
<ul>
<ul>$content .= the_title (</ul>
</ul>
&nbsp;
<ul>
<ul>'
	<li><a href="'.get_permalink().'">',
'</a></li>
</ul>
</ul>
&nbsp;
<ul>
<ul>',</ul>
</ul>
&nbsp;
<ul>
<ul>false</ul>
</ul>
&nbsp;
<ul>
<ul>);</ul>
</ul>
&nbsp;
<ul>
<ul>}</ul>
</ul>
&nbsp;
<ul>
<ul>/* 结束 ul */</ul>
</ul>
&nbsp;
<ul>$content .= '</ul>
<pre>
';
/* 重置 query */
wp_reset_query();
}
/* 返回 content */
return $content;
}
?>

the_title

文章的标题几乎和内容一样重要,所以 the_title 也是一个常用的过滤器钩子。你可以使用这个钩子添加信息,或者直接重写。

应用给 the_title 钩子的一个有用的过滤器就是去除标题中 HTML 标签的过滤器函数。用户有时会添加一些标签到标题中,这可能会破坏正常的格式。使用下面的代码,你可以过滤掉所有用户可能添加到标签。

<!--?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,”用户” )

<!--?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 .= '
User Role: ' .$user -> roles[0]. '

';
}
return $text;
}
?>

template_include

template_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。

<!--?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() 略微有些不同。

一般来说,插件使用函数而不是类中的方法作为动作或者过滤器。但是,可能有些时候使用类更适合,所以你要知道如何类在类中将方法注册到钩子。

前面已经提到的注册函数到一个动作钩子的方法:

<!--?php add_action( $tag, $function_to_add ); ?-->

当在类中将方法作为 $function_to_add 参数时,你必须把 $function_to_add 变成一个数组,其中 & $this 作为第一个参数,方法名作为第二个参数:

<!--?php add_action( $tag, array( & $this, $method_to_add ) ); ?-->

对于过滤器钩子也是一样。一般的将函数添加到一个过滤器钩子类似于:

<!--?php add_filter( $tag, $function_to_add ); ?-->

当使用类的方法的时候,要改成:

<!--?php add_filter( $tag, array( & $this, $method_to_add ) ); ?-->

下面的例子中,创建了一个类,包含一个构造函数,一个作为动作的方法,和一个作为过滤器的方法。add_filters() 方法检查用户是否在浏览单篇 post。如果是 content() 方法附加最后的修改时间到 post 的内容中。

<!--?php <br ?-->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 .= '
 最后修改于:' .$date. '

';
return $content;
}
}

$boj_myplugin_loader = new BOJ_My_Plugin_Loader();
?>

Jason.wang

When you find your talent can't afford to be ambitious, be quiet and study !

You may also like...

发表评论

电子邮件地址不会被公开。