实现 AJAX 通信需要服务器端 PHP 脚本的两个部分。首先,我们需要在网页上加载 jQuery 脚本并本地化 jQuery 脚本所需的任何 PHP 值。其次是实际处理 AJAX 请求。
加载脚本
本节涵盖了 WordPress 中 AJAX 的两个主要怪癖,这些怪癖会让刚接触 WordPress 的有经验的程序员感到困惑。其中之一是需要加载脚本以便在页面的头部区域正确显示元链接。另一个是所有AJAX 请求都需要通过 wp-admin/admin-ajax.php 发送。永远不要直接向您的插件页面发送请求。
加载
使用函数 wp_enqueue_script() 让 WordPress 在页面的头部区域插入指向您脚本的元链接。永远不要在头部模板中硬编码此类链接。作为插件开发者,您无法直接访问头部模板,但无论如何都要提及此规则。
加载函数接受以下五个参数:
- $handle 是脚本的名称。
- $src 定义脚本的位置。为了可移植性,请使用
plugins_url()构建正确的 URL。如果您是为插件以外的内容加载脚本,请使用相关函数创建正确的 URL – 永远不要硬编码它 - $deps 是一个可以处理您的新脚本依赖的任何脚本的数组,例如 jQuery。由于我们要使用 jQuery 发送 AJAX 请求,您至少需要在数组中列出
'jquery'。 - $ver 让您列出版本号。
- $args 是一个参数数组,定义了底部打印(通过
in_footer键)和脚本加载策略(通过strategy键),例如defer或async。这自 WordPress 6.3 版本起替换/重载了$in_footer参数。
wp_enqueue_script(
'ajax-script',
plugins_url( '/js/myjquery.js', __FILE__ ),
array( 'jquery' ),
'1.0.,0',
array(
'in_footer' => true,
)
);当您的插件代码页面加载时,您无法直接从其中加载脚本。脚本必须从几个操作钩子之一进行加载 – 具体取决于需要将脚本链接到哪种类型的页面。对于管理页面,请使用 admin_enqueue_scripts。对于前端页面,请使用 wp_enqueue_scripts,除了登录页面,在这种情况下请使用 login_enqueue_scripts。
admin_enqueue_scripts 钩子将当前页面文件名传递给您的回调函数。使用此信息仅在需要它的页面上加载脚本。前端版本不传递任何内容。在这种情况下,使用模板标签,如 is_home()、is_single() 等,以确保您仅在需要的地方加载脚本。这是我们示例的完整加载代码:
add_action( 'admin_enqueue_scripts', 'my_enqueue' );
function my_enqueue( $hook ) {
if ( 'myplugin_settings.php' !== $hook ) {
return;
}
wp_enqueue_script(
'ajax-script',
plugins_url( '/js/myjquery.js', __FILE__ ),
array( 'jquery' ),
'1.0.0',
array(
'in_footer' => true,
)
);
}为什么我们在这里使用命名函数,但对 jQuery 使用匿名函数?因为闭包最近才由 PHP 支持。jQuery 已经支持它们很长时间了。由于有些人可能仍在运行旧版本的 PHP,我们始终使用命名函数以获得最大兼容性。如果您拥有较新版本的 PHP 并且只为自己的安装开发,请随意使用闭包。
注册与加载
您将在其他教程中看到示例,这些示例虔诚地使用 wp_register_script()。这很好,但它的使用是可选的。不是可选的是 wp_enqueue_script()。必须调用此函数才能在网页上正确链接您的脚本文件。那么为什么要注册脚本呢?它创建了一个有用的标签或句柄,您可以在代码的各个部分根据需要轻松引用该脚本。如果您只需要加载脚本并且不在代码的其他地方引用它,则无需注册它。
延迟脚本加载
WordPress 提供了通过 wp_register_script() 和 wp_enqueue_script() 函数指定脚本加载策略的支持,这是通过 WordPress 6.3 中引入的新 $args 数组参数中的 strategy 键实现的。
支持的策略如下:
- defer
- 通过向 $args 参数指定数组键值对
'strategy' => 'defer'添加。 - 标记为延迟执行的脚本 — 通过 defer 脚本属性 — 仅在 DOM 树完全加载后执行(但在
DOMContentLoaded和 window load 事件之前)。与异步脚本不同,延迟脚本以它们在 DOM 中打印/添加的顺序执行。
- 通过向 $args 参数指定数组键值对
- async
- 通过向
$args参数指定数组键值对'strategy' => 'async'添加。 - 标记为异步执行的脚本 — 通过
async脚本属性 — 在浏览器加载它们后立即执行。异步脚本没有保证的执行顺序,因为脚本 B(尽管在 DOM 中添加到脚本 A 之后)可能会首先执行,前提是它可能在脚本 A 之前完成加载。此类脚本可能在 DOM 完全构建之前或之后DOMContentLoaded事件后执行。
- 通过向
以下是为插件中的额外脚本加载指定加载策略的示例:
wp_register_script(
'ajax-script-two',
plugins_url( '/js/myscript.js', __FILE__ ),
array( ajax-script ),
'1.0.,0',
array(
'strategy' => 'defer',
)
);使用 wp_enqueue_script() 时适用相同的方法。在上面的示例中,我们表明我们要以延迟方式加载 'ajax-script-two' 脚本。
在指定延迟脚本加载策略时,在决定“合格策略”时会考虑脚本的依赖树(其依赖项和/或依赖者),以避免应用对树中的其他脚本有害的策略,导致意外的执行顺序。因此,您通过 $args 参数传递的预期加载策略可能不是最终(选定)策略,但它永远不会对(或比)预期策略有害。
随机数
您需要创建一个随机数,以便 jQuery AJAX 请求可以验证为合法请求,而不是来自某些未知恶意行为者的潜在恶意请求。只有您的 PHP 脚本和您的 jQuery 脚本才知道此值。当收到请求时,您可以验证它是此处创建的值。这是我们示例中创建随机数的方法:
$title_nonce = wp_create_nonce( 'title_example' );参数 title_example 可以是任何任意字符串。建议该字符串与随机数的用途相关,但它实际上可以是任何适合您的内容。
本地化
如果您记得在 jQuery 部分中,由 PHP 为 jQuery 创建的数据是通过名为 my_ajax_obj 的全局对象传递的。在我们的示例中,此数据是一个随机数和到 admin-ajax.php 的完整 URL。分配对象属性并创建全局 jQuery 对象的过程称为本地化。这是我们示例中使用的本地化代码,它使用 wp_localize_script()。
wp_localize_script(
'ajax-script',
'my_ajax_obj',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => $title_nonce,
)
);注意我们的脚本句柄 ajax-script 的使用方式,以便将全局对象分配给正确的脚本。该对象对我们的脚本是全局的,而不是对所有脚本都是全局的。本地化也可以从用于加载脚本的同一钩子调用。创建 nonce 也是如此,尽管该特定函数可以在几乎任何地方调用。所有这些组合在单个钩子回调中看起来像这样:
add_action( 'admin_enqueue_scripts', 'my_enqueue' );
/**
* 加载我的脚本和资产。
*
* @param $hook
*/
function my_enqueue( $hook ) {
if ( 'myplugin_settings.php' !== $hook ) {
return;
}
wp_enqueue_script(
'ajax-script',
plugins_url( '/js/myjquery.js', __FILE__ ),
array( 'jquery' ),
'1.0.0',
true
);
wp_localize_script(
'ajax-script',
'my_ajax_obj',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'title_example' ),
)
);
} 请记住,仅将 nonce 本地化添加到需要的页面,不要向不应该使用它的人显示 nonce。并且记得使用 current_user_can() 与权限或角色一起完成安全验证。AJAX 操作
服务器端 PHP 代码的另一大部分是实际的 AJAX 处理器,它接收 POST 数据,对其进行处理,然后向浏览器发送适当的响应。这采用 WordPress 操作钩子 的形式。您使用的钩子标签取决于用户是否已登录以及您的 jQuery 脚本传递的 action: 值是什么。
$_GET、$_POST 和 $_COOKIE 与 $_REQUEST您可能使用过 PHP 超级全局变量之一或更多,例如 $_GET 或 $_POST 来从表单或 Cookie(使用 $_COOKIE)中检索值。也许您更喜欢 $_REQUEST,或者至少见过它被使用。这有点酷——无论请求方法是 POST 还是 GET,它都会有表单值。对于同时使用这两种方法的页面效果很好。除此之外,它还具有 Cookie 值。一站式购物!然而,这也存在其悲剧性的缺陷。在名称冲突的情况下,Cookie 值将覆盖任何表单值。因此,坏人可以极其容易地在他们的浏览器中伪造一个 Cookie,从而覆盖您可能期望从请求中获得的任何表单值。$_REQUEST 是黑客向您的表单值注入任意数据的便捷途径。为了更加安全,请坚持使用特定变量,避免一刀切的方法。
由于我们的 AJAX 交换是针对插件的设置页面,用户必须已登录。如果您记得 jQuery 部分 中的内容,action: 值是 "my_tag_count"。这意味着我们的操作钩子标签将是 wp_ajax_my_tag_count。如果我们的 AJAX 交换被未登录的用户使用,操作钩子标签将是 wp_ajax_nopriv_my_tag_count 用于挂钩操作的基本代码如下所示:
add_action( 'wp_ajax_my_tag_count', 'my_ajax_handler' );
/**
* 处理我的 AJAX 请求。
*/
function my_ajax_handler() {
// 在此处处理 AJAX 请求
wp_die(); // 所有 AJAX 处理器在完成时都会终止
}您的 AJAX 处理器首先应该做的是使用 check_ajax_referer() 验证 jQuery 发送的 nonce,该值应与脚本加载时本地化的相同值一致。
check_ajax_referer( 'title_example' );提供的参数必须与之前提供给 wp_create_nonce() 的参数完全相同。如果 nonce 验证失败,该函数将直接终止。如果这是一个真正的 nonce,现在它已被使用,其值就不再有效。您会生成一个新的并发送给回调脚本,以便用于下一次请求。但由于 WordPress nonce 有效期为二十四小时,您只需检查即可。
数据
在 nonce 处理完毕后,我们的处理器可以处理 jQuery 脚本中发送的数据,这些数据包含在 $_POST['title'] 中。首先我们将该值分配给一个新变量,并通过运行 wp_unslash() 来移除任何意外的引号。
$title = wp_unslash( $_POST['title'] );我们可以使用 update_user_meta() 将用户的选择保存在用户元数据中。
update_user_meta( get_current_user_id(), 'title_preference', sanitize_post_title( $title ) );然后我们构建一个查询以获取所选标题标签的帖子计数。
$args = array(
'tag' => $title,
);
$the_query = new WP_Query( $args );最后,我们可以将响应发送回 jQuery 脚本。有几种传输数据的方法。在我们处理示例的具体内容之前,让我们先看看一些选项。
XML
PHP 对 XML 的支持不尽如人意。幸运的是,WordPress 提供了 WP_Ajax_Response 类来简化任务。WP_Ajax_Response 类将生成 XML 格式的响应,设置正确的内容类型作为标头,输出响应 xml,然后终止——确保提供适当的 XML 响应。
JSON
这种格式轻量且易于使用,WordPress 提供了 wp_send_json 函数来将您的响应编码为 JSON、打印它并终止——实际上取代了 WP_Ajax_Response。WordPress 还提供了 wp_send_json_success 和 wp_send_json_error 函数,允许适当的 done() 或 fail() 回调在 JS 中触发。
其他
只要发送方和接收方协调一致,您就可以以任何方式传输数据。像逗号分隔或制表符分隔这样的文本格式是众多可能性之一。对于少量数据,发送原始流可能就足够了。我们将对示例采用这种方法——我们将发送实际的替换 HTML,仅此而已。
echo esc_html( $title ) . ' (' . $the_query->post_count . ') ';在现实世界的应用程序中,您必须考虑到操作可能因某种原因失败的可能性——例如,也许数据库服务器已宕机。响应应允许这种意外情况,接收响应的 jQuery 脚本应根据此采取行动,也许告诉用户稍后再试。
终止
当处理器完成所有任务后,它需要终止。如果您使用 WP_Ajax_Response 或 wp_send_json* 函数,这会自动为您处理。否则,只需使用 WordPress wp_die() 函数。
AJAX 处理器摘要
我们示例的完整 AJAX 处理器如下所示:
/**
* 使用 JSON 的 AJAX 处理器
*/
function my_ajax_handler__json() {
check_ajax_referer( 'title_example' );
$title = wp_unslash( $_POST['title'] );
update_user_meta( get_current_user_id(), 'title_preference', sanitize_post_title( $title ) );
$args = array(
'tag' => $title,
);
$the_query = new WP_Query( $args );
wp_send_json( esc_html( $title ) . ' (' . $the_query->post_count . ') ' );
}/**
* 不使用 JSON 的 AJAX 处理器。
*/
function my_ajax_handler() {
check_ajax_referer( 'title_example' );
$title = wp_unslash( $_POST['title'] );
update_user_meta( get_current_user_id(), 'title_preference', sanitize_post_title( $title ) );
$args = array(
'tag' => $title,
);
$the_query = new WP_Query( $args );
echo esc_html( $title ) . ' (' . $the_query->post_count . ') ';
wp_die(); // 所有 AJAX 处理器在完成时都应终止
}