前言

最近在复现 wordpress 以前的洞,看到的第一个就是和插件相关的。所以学习整理了一下 wordpress 插件的实现机制,希望能给之后的审计带来帮助 : )

hook

什么是 hook 机制(钩子机制)? 在 windows 中,hook 是一种用来劫持应用程序或者进程的消息的一种技术。对于 php 来说,我们也可以实现 hook 机制,用来添加自定义的内容。其主要的思想是,在需要自定义内容或添加功能的地方提前设置一个 hook,这个 hook 并不影响原本的内容。而当你需要添加或者修改该部分内容的时候,可以将扩展的类或者方法钩到该 hook 上。

hook 机制的组成:

  1. 一个存放所有已注册的 hook 的对象或数组。
  2. hook 类,里面存放了对应 hook 的相应信息,包括钩到这个 hook 上的所有函数等。

接着,我们可以将插件中的函数钩到指定的 hook 中,在该 hook 被触发时,插件的功能将会实现。这里我简单实现了一个 hook 机制,我网站目录如下:

01

具体代码如下:

02

我先定义了一个全局数组 hook_arr,该数组用来存放所有注册的 hook,然后,我注册了一个叫作 p 的 hook,并将 hello 插件中的函数注册到该 hook 上。在 index.php 页面被渲染的时候,会触发该 hook,从而实现 hello 插件的功能。

03

可以看到,我预先注册了一个叫做 p 的 hook,该 hook 并不影响该页面的正常显示。而当开发者需要添加一些特定的内容或者功能时,直接将开发好的插件与特定的 hook 相连即可。

在这篇文章中,也有关于 hook 实现的小例子。

wordpress 之 hook

因为 hook 机制是事件驱动型架构中很重要的一部分,即一种通过监听事件状态并作出反应的设计,而在 wordpress 中,许多功能点其实就是对应了一个个的事件,比如,编辑文章、修改设置等。

从上面可以看出,我们可以很容易地将一个插件和 wordpress 中定义好的 hook 相关联,从而实现丰富和扩展 wordpress 的目的。也正是因为 hook,wordpress 拥有了完备的生态圈,和数以万计的功能强大的插件及主题。

在 wordpress 中,有两种 hook,action hookfilter hook。它们最大的区别就是,filter hook 会拿到 wordpress 提供的一个参数,然后对其进行修改,并返回该修改后的参数的值,而 action hook 不需要有 return 值。

在参考文章中说到:

在实际场景中,Filter Hooks 通常更专注于「内容」方面。例如说,Filter Hook title_save_pre 是作用于文章标题的, content_save_pre 就是作用于文章内容的。另一方面,Action Hooks 顾名思义,它偏向于「在 WordPress 做某件事时执行相应的动作」,还是拿 publish_post 为例,这个 Action Hook 就是在「任意一篇文章被发表时」被触发。

当 wordpress 在执行某些动作的时候,因为动作一般不需要返回值,所以对应的是 action hook。而当 wordpress 需要展示内容的时候,允许用户对某些部分进行定制,从而需要接收用户修改后的值,此时对应的就是 filter hook。

action hook

在 wordpress 中,如何添加一个 action hook 呢?该功能对应的函数是 add_action()。

04

第一个参数 tag 是所指定的 hook,第二参数是在该 hook 被触发时所调用的函数,第三个参数是同一个 hook 中每个函数的优先级,第四个参数是所添加的 function_to_add 函数需要接收的参数个数。

跟进 add_filter()。

05

在该函数中,先取了一个全局变量 wp_filter,该变量是 wordpress 的 hook 机制中很重要的一个变量,该变量中存放了所有已经注册的 hook 的信息。我们可以查看一下该变量的值,在根目录中的 index.php 文件的最后一行添加:var_dump($wp_filter);

06

可以看到其中有很多 WP_Hook 类的值。找一下全局变量 wp_filter 的定义处。

07

在 plugin.php 文件开头,先包含了 class-wp-hook.php 文件,在该文件中定义了 WP_Hook 类,接着初始化了三个数组 wp_filter、wp_actions、wp_current_filter。

回到 add_filter() 中,该函数通过传进来的 hook 去 wp_filter 中找,如果没有则实例化一个 WP_Hook 类,并将 hook 和新建的类通过键值对的形式相关联,存进 wp_filter 中。最后调用传进来的 hook,即 WP_Hook 类的 add_filter() 方法。跟进该方法。

08

在该方法中,先通过 hook、函数名和优先级的值生成了一个索引 idx,然后将和这个 hook 相关联的函数,根据优先级和 idx 的值存入该类的 callbacks 属性中,其中存放了与 hook 关联的函数的名字和接收参数的数量,最后通过 ksort() 对 callbacks 按优先级进行排序。

到此,我们就将一个 hook 触发时需要执行的函数,与指定 hook 相关联。实际上,就是实例化了一个 WP_Hook 类,存进该函数的信息,并将该 WP_Hook 类以 hook 为索引放入了 wp_filter 数组中。

由上面的分析看出,通过执行 add_action() ,可以将一个函数与指定的 hook 相关联,那我们如何触发 hook 从而调用关联的函数呢?在 wordpress 中,有数千个可以被使用的 hook,这里我通过叫作 wp_head 的 hook 来说明 hook 是如何被触发的。看如下代码:

1
2
3
4
// /wp-includes/general-template.php line 2608
function wp_head() {
do_action( 'wp_head' );
}

可以看到 wp_head() 函数中,就执行了 do_action() 函数,且传入了 wp_head hook。其实,通过和 add_action() 函数名的对比,大概也能猜到该函数的作用了,跟进 do_action() 。

09

还记得前面在定义 wp_filter 的同时,还定义了另外两个全局变量 wp_actions 和 wp_current_filter 吗?wp_filter 是用来存放所有的 hook 信息的,wp_actions 是用来记录这个 hook 被调用的次数的,wp_current_filter 是用来存放当前 hook 的,当与 hook 关联的函数被执行完后,就会从 wp_current_filter 从弹出该 hook。

该函数对传进来的参数 arg 进行判断,取出所有的参数,存入 args 数组中。因为对于 action hook 来说,一般不需要参数,所以默认为空。然后调用了指定 hook 的 do_action 方法,在这里 hook 就是 wp_head。

跟进 WP_Hook 类的 do_action()。

1
2
3
4
5
6
7
8
9
// /wp-include/class-wp-hook.php line 308
public function do_action( $args ) {
$this->do_action = true;
$this->apply_filter( '', $args );

if ( ! $this->nesting_level ) {
$this->do_action = false;
}
}

在 do_action() 中,先将 wp_head 中的 do_action 属性设置为 true,然后调用了 apply_filter() 方法。

10

因为这是 action hook,不需要接收 wordpress 传递过来以用来修改的参数,所以 value 默认为空。最后,将与 wp_head 这个 hook 关联的,存放在 callbacks 属性中的函数,通过 call_user_func_array() 执行。

filter hook

因为所有的 hook 都存放在全局变量 wp_filter 中,所以 action hook 和 filter hook 实现大体相同。比如 title_save_pre 这个 hook,它可以实现动态更改标题的目的。

wordpress 在这个 hook 上关联了一些函数。

1
2
// /wp-includes/default-filters.php line 250
add_filter( 'title_save_pre', 'trim' );

其实这个 add_filter(),我们已经见过了。在为 action hook 关联函数的时候,直接使用的是 add_action(),其中就调用了 add_filter()。接着,看一下在哪里可以触发这个 hook。

11

1
2
// /wp-includes/customize/class-wp-customize-nav-menu-item-setting.php line 702
$menu_item_value['title'] = wp_unslash( apply_filters( 'title_save_pre', wp_slash( $menu_item_value['title'] ) ) );

可以看到,这个 hook 是通过 apply_filters() 触发的,并且传入 menu_item_value [ ‘ title ‘ ] 这个值。在传入之前,通过 wp_slash() 处理,实际是 wordpress 实现可以对数组调用的 addslashes()。跟进 apply_filters()。

12

这里的 tag 变量,即 title_save_pre,由前面知道,都是注册进了 wp_filter 这个全局变量中,实际也是一个 WP_Hook 类,这里和触发 action hook 一样,都是调用了 WP_Hook 类的 apply_filters 方法。正如前面所说,和 action hook 不同的是,filter hook 传入了一个 value,实际就是 menu_item_value [ ‘ title ‘ ],处理完之后,将修改好的值,传回给 wordpress 继续使用。

总结

在 wordpress 中,实现了两种 hook,一种是 action hook,一种是 filter hook。第一种 hook 通过 add_action() 可以关联一个函数,当该 hook 被 do_action() 触发的时候执行关联函数。第二种 hook 通过 add_filter() 关联一个函数,当该 hook 被 apply_filters() 触发的时候执行关联函数。

ref :
https://www.cnblogs.com/whiterock/p/7286188.html
https://www.mywpku.com/understanding-and-using-the-wordpress-hook-system.html
https://www.wpdaxue.com/wordpress-hook.html