過濾器 Filters
- 過濾器鉤子和動作鉤子有很大的區別。它讓你可以控制代碼的輸出。
apply_filters() 函數
<?php
apply_filters( $tag, $value );
?>
- $tag – 過濾器鉤子的名字。
- $value – 傳遞給任何添加到這個鉤子的過濾器的參數。這個函數可以添加任意個額外的 $value 參數傳遞給過濾器。
- 注意:在寫一個過濾器的時候 $value 必須返回給 WordPress。
過濾器鉤子的例子:
<?php
apply_filters( 'template_include', $template );
?>
template_include 是一個過濾器鉤子的名字,
$template 是一個可以通過注冊到這個過濾器鉤子的過濾器改變的文件名。
什么是過濾器?
一個過濾器是一個注冊到過濾器鉤子的函數。這個函數最少接受一個參數并在執行完代碼后返回那個參數。沒有過濾器的過濾器鉤子沒有任何作用。插件開發者可以通過過濾器鉤子改變不同的變量 – 從字符串到多位數組。
當一個過濾器鉤子被 apply_filters() 函數調用時,所有注冊到這個鉤子的過濾器都會被執行。要添加一個過濾器,使用
add_filter() 函數。
下面看看 wp_title 過濾器鉤子,它是負責頁面的 <title> 元素的過濾器鉤子。
<?php
apply_filters( 'wp_title', $title, $sep, $seplocation );
?>
wp_title – 鉤子名。
$title – 一個字符串,要過濾并返回給 WordPress 的值。
$sep – 說明 <title> 元素之間的分隔符是什么。
$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_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 的 <p> </p>。這也就是我們在 HTML 發布內容時,直接回車便可在最終前端顯示的時候換行的原因。它在核心代碼中的幾個鉤子中都執行。下面是其中一個實例:
<?php
add_filter( 'the_content', 'wpautop');
?>
這將 wpautop() 過濾器應用到 post 的內容中,把每個換行都轉換成段落( <p> )。但是也許有的客戶不希望他的內容自動變成段落。那么你就可以使用 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 的個人用戶管理頁面中的一系列 <input>。要禁用這些表單項,你需要返回一個空數組。通常,你必須添加過濾器鉤子調用和函數。
<?php
add_filter( 'user_contactmethods', 'boj_return_empty_array' );
function boj_return_empty_array() {
returnarray();
}
?>
寫這樣的代碼一兩次并沒什么。但是寫一個返回空數組的函數太傻了。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($termsas$term )
$categories[] =$term-> term_id;
/* 從數據庫查詢相同分類的 posts */
$loop=newWP_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 標簽的過濾器函數。用戶有時會添加一些標簽到標題中,這可能會破壞正常的格式。使用下面的代碼,你可以過濾掉所有用戶可能添加到標簽。
<?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=newWP_User( $comment-> user_id );
/* 如果用戶有一個角色,添加到評論內容中 */
if(is_array($user-> roles ) )
$text.='<p>User Role: '.$user-> roles[0]. '</p>';
}
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($termsas$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
class BOJ_My_Plugin_Loader {
/* 類的構造函數 */
functionBOJ_My_Plugin_Loader() {
/* 為 'template_redirect' 鉤子添加 'singular_check' 方法 */
add_action('template_redirect',array( & $this,'singular_check') );
}
/* 作為動作的方法 */
functionsingular_check() {
/* 如果在看單個文章,過濾內容 */
if( is_singular() )
add_filter('the_content',array( & $this,'content') );
}
/* 作為過濾器的方法 */
functioncontent($content) {
/* 得到 post 的最后修改時間 */
$date= get_the_modified_time( get_option('date_format') );
/* 附加修改時間到 content */
$content.='<p> 最后修改于:'.$date.'</p>';
return$content;
}
}
$boj_myplugin_loader=newBOJ_My_Plugin_Loader();
?>
創建自定義鉤子
插件不但可以使用內核的內置鉤子,他們也可以創建自定義的鉤子供其他插件和模板使用。
插件可以使用4個可用函數中的一個來創建自定義鉤子。
- do_action()
- do_action_ref_array()
- apply_filters()
- apply_filters_ref_array()
前兩個創建自定義動作鉤子,后兩個創建自定義過濾器鉤子。
創建自定義鉤子的優點
自定義鉤子使得你的插件更靈活,使其可以被其他插件擴展,讓你可以鉤到你的整個插件自己的其他執行過程中。
使用自定義鉤子還可以防止用戶直接修改你的插件。這一點的重要性在于,當你更新你的插件時,用戶不會失去他們修改的內容。
自定義動作鉤子實例
在這個例子中,建立了一個插件安裝函數。這個函數定義了一個可以更換的常量。別的插件也可以在這個鉤子上執行任何代碼。因為你在那一點上提供了鉤子。
<?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’ ,你可以建立一個動作并添加到這個鉤子中:
<?php
add_action( 'boj_myplugin_setup_pre','boj_define_myplugin_constants');
function boj_define_myplugin_constants() {
define('BOJ_MYPLUGIN_ROOT_SLUG','papers');
}
?>
自定義過濾器鉤子實例
假設有一個函數顯示一個具有一個特定闡述的文章列表。你也許希望其他人能夠過濾那個參數或者過濾最終結果。
下面的例子中,寫一個函數根據收到的評論條數列取了前10的文章。這個函數讓用戶可以在從數據庫獲取數據前過濾這個參數,并且可以過濾最終輸出的 HTML 列表
<?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 = newWP_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,添加下面的過濾器:
<?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。
<?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 頁面而改變。這個鉤子如下:
<?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 的動作鉤子和過濾器鉤子中。鉤子是插件開發中必不可少的工具。在了解了鉤子之后,就是時候開始創建插件了。