管理WordPress多站点网络

管理 WordPress 多站点网络与管理单站点 WordPress 安装略有不同。

在本课中,你将学习如何管理 WordPress 多站点网络。

你将了解网络管理仪表板、网络设置页面中的可用选项,以及在网络上创建和管理子站点的不同方法。

网络管理仪表板

一旦启用了多站点网络,你的管理员用户将变为超级管理员,并可以访问网络管理仪表板。在这里,你可以管理网络中的站点、用户、主题、插件和设置。

网络设置页面

网络设置页面是管理多站点网络的地方。

操作设置允许你设置网络标题和管理员邮箱。

注册设置允许你设置网络的注册选项。

例如,如果你想允许用户在网络中注册新站点,可以启用“站点和用户账户都可以注册”选项。

你还可以设置新站点的禁用名称、限制特定电子邮件域名的注册,以及禁止特定电子邮件域名的注册。

新站点设置控制着在网络中创建新站点时发送的电子邮件和创建的内容。

上传设置控制文件上传,包括文件大小限制和允许的文件类型。

语言设置允许你控制网络中新建站点的默认语言。

最后,菜单设置允许你控制站点管理员可以访问哪些菜单。

创建站点

你可能想做的第一件事就是创建一个子站点。为此,请转到站点页面,并点击“添加新站点”按钮。系统会要求你输入站点地址(子域名或子目录)、站点标题、站点语言和站点管理员邮箱地址。如果你使用现有用户的邮箱地址,该用户将被添加为站点管理员。否则,将创建一个新用户并设为新站点的管理员。

站点创建后,你可以通过点击站点列表中的“编辑”按钮来编辑该站点。

信息选项卡管理站点地址,显示站点的注册时间和最后更新时间,并管理站点属性。

用户选项卡允许你将现有用户添加到站点,或向站点添加新用户。

主题选项卡允许你为站点激活一个主题。这里可用的主题是那些未在整个网络中激活的主题。这意味着你可以为网络中的特定站点设置专属主题。

设置选项卡包含你可以管理的所有其他站点设置。

为站点分配顶级域名

还可以为网络中的站点设置顶级域名或根域名。如果你或站点管理员想为网络中的某个站点使用不同的域名,这将非常有用。

要实现这一点,你需要在域名注册商处注册该域名,并将其指向与多站点安装相同的位置。

在这种情况下,你可以看到新的顶级域名指向与多站点安装相同的位置,因为它会重定向到多站点的 URL。

然后,在网络管理中,编辑相关站点,并将站点地址字段更改为新的顶级域名。

现在,当你浏览该域名时,将被重定向到网络中关联的站点。

允许用户注册自己的站点

根据你计划如何使用多站点网络,你可能希望用户能够注册自己的子站点。为此,请转到网络设置页面,并启用“站点和用户账户都可以注册”选项。

现在,如果用户浏览默认的 WordPress 注册 URL,他们将看到还可以在网络中注册站点的选项。

// 示例代码块,内容保持不变

输入用户名和邮箱地址后,系统会询问他们是想要创建站点,还是仅在网络中注册用户账户。

如果他们选择创建站点,则需要设置站点名称和标题。

一旦他们点击注册,站点将被创建,用户将收到一封激活新站点的电子邮件,之后他们就可以登录到该子站点。

进一步阅读

有关管理多站点网络的更多详细信息,请查看 WordPress 开发者文档中的网络管理和网络设置页面。

搭建WordPress多站点网络

WordPress 多站点网络是一种在单个 WordPress 安装中托管多个 WordPress 站点的方式。

在本课中,你将进一步了解 WordPress 多站点、为何要考虑设置多站点网络,以及创建多站点网络需要遵循的步骤。

什么是 WordPress 多站点网络?

WordPress 多站点网络是一组共享单个 WordPress 安装的 WordPress 站点。通过多站点网络,可以允许用户创建自己的站点,或者将网络配置为仅允许管理员创建站点。这些站点在网络中被称为“子站点”或“独立站点”。

多站点网络中的所有站点都使用相同的 WordPress 安装文件,并共享相同的插件和主题。插件和主题在网络级别安装,然后在各个独立站点上激活。

每个独立站点在共享安装中都有独立的媒体上传目录,并在数据库中有独立的表来存储站点内容。

需要注意的是,虽然它们共享相同的 WordPress 核心文件,但多站点网络中的各个站点是相互独立的。它们不像其他类型的网络那样相互连接。如果你计划创建紧密互联、共享数据或共享用户的站点,那么多站点网络可能不是最佳解决方案。

为何要使用 WordPress 多站点网络?

当你拥有多个性质相似但需要相互独立的站点时,多站点网络是一个很好的解决方案。

这方面的例子包括高等教育网站、非营利组织和开源项目。

一个活跃多站点网络最典型的例子就是 Make WordPress。

在这里,每个参与 WordPress 开源项目的贡献者团队都拥有自己的子站点,这些子站点是 Make WordPress 网络的一部分。这意味着,如果你是核心团队的成员,你只能登录到核心子站点,并为该团队创建内容。

多站点网络支持

你使用的网络主机或本地开发环境将决定你如何在本地或在线服务器上创建多站点网络。两种常见的选择是:要么在新安装过程中提供一个设置选项,将新安装设置为多站点网络;要么在 WordPress 安装完成后,按照手动步骤设置多站点网络。

使用 Apache 网络服务器的网络主机和本地开发环境通常允许你使用手动方法将现有的 WordPress 安装转换为多站点网络。使用 Nginx 的环境通常要求你在创建新站点的过程中创建多站点网络。这是因为默认情况下,nginx 不支持用于手动启用多站点的 .htaccess 文件。

无论你使用的是自动还是手动启用多站点的网络主机或本地开发环境,了解从 WordPress 安装创建多站点网络所需的额外步骤都是一个好主意,这也是本课的重点。

如何创建多站点网络

在创建多站点网络之前,请务必阅读开发者文档中的“在创建网络之前”页面,因为它涵盖了一些你在创建多站点网络之前需要了解的重要注意事项。

接下来,你应该备份当前站点的文件和数据库。这不是绝对必要的,特别是如果你创建了一个全新的 WordPress 安装,但如果你已经在要转换为多站点网络的站点上创建了一些内容,那么备份是个好主意。

此外,如果你有站点文件的备份,那么在创建多站点网络过程中如果出现任何问题,你可以快速撤销所做的任何更改。

同时,你应该确保站点上的固定链接正常工作,并且已停用所有已安装的插件。

最后,如果你希望将 WordPress 安装运行在其自己的目录中,请确保在创建多站点网络之前完成此操作。

启用多站点

要启用多站点,你需要编辑 WordPress 安装根目录下的 wp-config.php 文件,并将 PHP 常量 WP_ALLOW_MULTISITE 定义为 true

/* 多站点 */
define( 'WP_ALLOW_MULTISITE', true );

然后,刷新你的 WordPress 仪表盘。通过设置此常量,你现在会在仪表盘的“工具”菜单下看到一个新的菜单项,名为“网络设置”。

安装网络

“网络设置”页面将引导你完成创建多站点网络的步骤。

首先,你需要选择是使用顶级域名的子域名还是子目录作为网络上站点的地址。子目录更简单,因为你不需要任何额外的 DNS 配置,但子域名能让站点拥有更专业的 URL。要使用子域名,你需要为你的顶级域名设置一个通配符 DNS 记录。

在本课中,我们将使用子目录。

您可以保留其余设置不变,或根据自己的喜好进行更改。

准备好后,点击“安装”按钮。

WordPress 现在将运行网络安装,创建所需的任何数据库表,并向这些表添加所需的特定数据。

启用网络

安装完成后,您需要启用网络。这需要做两处更改:

  1. 再次编辑 wp-config.php 文件,并添加仪表盘中“启用网络”页面第一步描述的常量。
  2. 编辑 WordPress 安装根目录中的 .htaccess 文件,并添加仪表盘中“启用网络”页面第二步描述的规则。

完成这些更改后,刷新您的 WordPress 仪表盘,系统会要求您重新登录。

您将被重定向回“网络设置”页面,但会注意到您现在已作为网络管理员登录,并且新创建的多站点网络为您提供了一组不同的菜单选项。

进一步阅读

有关如何创建多站点网络的更多详细信息,请务必阅读官方 WordPress 文档中的“创建网络”页面。

常用的国际化函数

要开始对代码进行国际化,您需要了解 WordPress 提供的用于帮助您完成此过程的函数。

在本课中,您将学习 WordPress 中最常用的国际化函数,以及如何在代码中使用它们。

如何对代码进行国际化 (0:18)

每当您编写一段将显示给用户的文本字符串时,都应使用 WordPress 的 i18n 函数,以确保它可以被翻译。

有许多可用的 i18n 函数,每个函数都执行与国际化相关的不同任务。

最常用的 i18n 函数是 __() 函数。此函数接收一个文本字符串,并返回该字符串的翻译版本。如果没有可用的翻译,则返回原始字符串。

您还会注意到,此函数以及大多数其他 i18n 函数都接受第二个参数。此参数用于指定文本域。

文本域是您的插件或主题的唯一标识符。它用于确保使用正确的翻译。

主题和插件在其头部都有一个“文本域”字段。这用于指定主题或插件的文本域。

每当您使用翻译函数时,都应始终将文本域作为第二个参数包含在内。

__( 'Some Text', 'my-textdomain' );

要了解其工作原理,让我们看一个示例:

首先,浏览到 GitHub 上的“初学者开发者学习路径”仓库,并下载“国际化主题”zip 文件。

然后,在您的 WordPress 网站上安装该主题,并在代码编辑器中浏览它。

functions.php 文件中,一个 JavaScript 文件在 WordPress 仪表盘的上下文中被入队。它还在“外观”菜单中注册了一个子菜单项,用于渲染主题设置页面。

<?php
/**
 * Enqueue theme.js file in the dashboard.
 */
add_action( 'admin_enqueue_scripts', 'internationalization_enqueue_scripts' );
function internationalization_enqueue_scripts() {
    wp_enqueue_script(
        'internationalization-theme-js',
        get_stylesheet_directory_uri() . '/assets/theme.js',
        array(),
        '1.0.0',
        true
    );
}

/**
 * Create a submenu item under the "Appearance" menu.
 */
add_action( 'admin_menu', 'internationalization_add_submenu_page' );
function internationalization_add_submenu_page() {
    add_submenu_page(
        'themes.php',
        'Internationalization',
        'Internationalization',
        'manage_options',
        'internationalization',
        'internationalization_display_page'
    );
}

/**
 * Render the page for the submenu item.
 */
function internationalization_display_page() {
    ?>
    <div class="wrap">
        <h1>Internationalization Settings</h1>
        <p>This is a settings page for the Internationalization theme</p>
        <button id="internationalization-settings-button" class="button button-primary">Alert</button>
    </div>
    <?php
}

如果您浏览到设置页面,它会包含一个按钮,点击该按钮会显示一个警告框。

此警告框由主题的 JavaScript 文件处理。

/**
 * Add event listener to the button
 */
document.querySelector( '#internationalization-settings-button' ).addEventListener( 'click', function(){
    alert( 'Settings button clicked' );
} );

在这段代码中,您有许多需要国际化的英文字符串。

第一步是国际化 PHP 代码中的文本字符串。为此,您可以将文本字符串包裹在 __() 函数中,并指定文本域。

首先检查主题的文本域。对于主题,这位于 style.css 文件中;对于插件,则位于主插件文件中。

在这种情况下,文本域是 internationalization。如果此主题没有文本域,您需要指定一个。

然后,更新 internationalization_add_submenu_page() 函数中的文本字符串。

/**
 * Create an admin submenu item under the "Appearance" menu.
 */
add_action( 'admin_menu', 'internationalization_add_submenu_page' );
function internationalization_add_submenu_page() {
    add_submenu_page(
        'themes.php',
        __( 'Internationalization', 'internationalization' ),
        __( 'Internationalization', 'internationalization' ),
        'manage_options',
        'internationalization',
        'internationalization_display_page'
    );
}

您可以为 internationalization_display_page() 函数中的文本字符串执行相同操作。

/**
 * Render the page for the submenu item.
 */
function internationalization_display_page() {
    ?>
    <div class="wrap">
        <h1><?php echo __( 'Internationalization Settings', 'internationalization' ); ?></h1>
        <p><?php echo __( 'This is a settings page for the Internationalization theme', 'internationalization' ); ?></p>
        <button id="internationalization-settings-button" class="button button-primary"><?php echo __( 'Alert', 'internationalization' ); ?></button>
    </div>
    <?php
}

WordPress 还包含一个用于直接输出可翻译字符串的简写函数。此函数是 _e() 函数。它既进行国际化,又直接输出字符串。您可以使用此函数来简化代码。

/**
 * Render the page for the submenu item.
 */
function internationalization_display_page() {
    ?>
    <div class="wrap">
        <h1><?php _e( 'Internationalization Settings', 'internationalization' ); ?></h1>
        <p><?php _e( 'This is a settings page for the Internationalization theme', 'internationalization' ); ?></p>
        <button id="internationalization-settings-button" class="button button-primary"><?php echo __( 'Alert', 'internationalization' ); ?></button>
    </div>
    <?php
}

接下来,您需要国际化 JavaScript 文件中的文本字符串。

为此,有一个与 PHP 的 __() 函数等效的 JavaScript 版本,它在 WordPress 前端的 wp.i18n 对象中可用。

为了确保您可以使用此函数,您需要更新您的 internationalization_enqueue_scripts() 函数,以将 wp-i18n 包作为依赖项。这将确保您的 JavaScript 代码仅在 wp-i18n 包加载且 wp.i18n 对象可用时才会被加载。

/**
 * Enqueue theme.js file in the dashboard.
 */
add_action( 'admin_enqueue_scripts', 'internationalization_enqueue_scripts' );
function internationalization_enqueue_scripts() {
    wp_enqueue_script(
        'internationalization-theme-js',
        get_stylesheet_directory_uri() . '/assets/theme.js',
        array( 'wp-i18n' ),
        '1.0.0',
        true
    );
}

然后,您需要为您想要翻译的脚本调用 wp_set_script_translations 函数。此函数接受脚本的句柄和文本域作为参数。这将加载脚本的翻译。

wp_set_script_translations( 'internationalization-theme-js', 'internationalization' );

完成此操作后,您就可以设置并使用来自 wp.i18n 对象的 __() 函数来翻译 JavaScript 文件中的文本字符串。

const __ = wp.i18n.__;
/**
 * Add event listener to the button
 */
document.querySelector( '#internationalization-settings-button' ).addEventListener( 'click', function(){
    alert( wp.i18n.__( 'Settings button clicked', 'internationalization' ) );
} );

如果您刷新页面,不会立即看到任何变化,所有功能仍将按预期工作。

但是,如果有人愿意,他们可以基于所有国际化的文本字符串为主题生成英文语言文件,以便进行翻译。

块开发中的国际化函数

如果您正在为块编辑器开发一个块,您也可以在块的 JavaScript 中使用 JavaScript 翻译函数来国际化块中的文本字符串。

您需要做的就是从 @wordpress/i18n 包中导入相关函数。

/**
 * Retrieves the translation of text.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
 */
import { __ } from '@wordpress/i18n';

进一步阅读

本课程仅涵盖部分可用的国际化函数。如需了解所有可用函数的更多信息,请查阅 WordPress 开发者资源中通用 API 手册的国际化章节。

什么是国际化?

国际化是指开发软件时,使其能够在不修改源代码的情况下轻松翻译成其他语言的过程。

让我们了解在 WordPress 中什么是国际化、为什么它很重要,并学习在哪里可以找到更多关于如何在 WordPress 开发中实现国际化的信息。

什么是国际化?

WordPress 被全球使用,用户使用多种语言。

因此,WordPress 中的任何文本字符串都需要进行编码,以便它们可以轻松翻译成其他语言。

让我们看一个例子:

当你第一次登录 WordPress 仪表盘时,页面顶部会出现“仪表盘”这个标题。

你可以在 wp-admin/index.php 文件的第 33 行找到生成这个标题的代码。

$title       = __( 'Dashboard' );

这里 $title 变量被设置为“仪表盘”。

注意文本字符串是如何被包裹在 __() 函数中的。这是一个 WordPress 函数,用于使文本字符串可翻译。

如果你访问用户的个人资料并将其更新为另一种语言,文本字符串将被翻译成该语言。

例如,如果我将我的用户个人资料更改为西班牙语,标题将被翻译为“Escritorio”。

但底层的代码保持不变。

这是可能的,因为西班牙语的语言文件已安装在网站上,并且包含了文本字符串“仪表盘”的翻译。

#. translators: Network menu item.
#: wp-includes/admin-bar.php:431 wp-includes/admin-bar.php:596
#: wp-includes/admin-bar.php:716 wp-includes/deprecated.php:2822
#: wp-includes/deprecated.php:2824 wp-admin/index.php:33 wp-admin/menu.php:24
#: wp-admin/my-sites.php:142 wp-admin/user/menu.php:10
#: wp-admin/includes/class-wp-ms-sites-list-table.php:746
#: wp-admin/network/index.php:21 wp-admin/network/menu.php:11
#: wp-admin/network/site-info.php:138 wp-admin/network/site-settings.php:95
#: wp-admin/network/site-themes.php:181 wp-admin/network/site-users.php:226
msgid "Dashboard"
msgstr "Escritorio"

由于使用了翻译函数,一旦为用户设置了语言,WordPress 将在相关语言文件中搜索该词的翻译,并根据上下文显示或存储翻译。

国际化是编写代码的过程,通过将可能显示给用户的任何文本字符串包裹在正确的翻译函数中,使其可翻译。

国际化通常缩写为 i18n(因为字母 i 和 n 之间有 18 个字母)。

将国际化的文本字符串翻译并适应特定区域或语言的过程称为本地化。

虽然本地化不在本课程范围内,但你可以在 WordPress 开发者资源中通用 API 手册的本地化部分阅读更多相关内容。

国际化不是什么

国际化并不等同于确保你网站前端的内容以多种语言提供。

这通常被称为确保你的内容是多语言或已翻译的。

由于这些内容存储在数据库中,最好为每种你想要支持的语言准备一个完全翻译的网站副本。

这可以通过使用 TranslatePress、Polylang 或 WeGlot 等插件来实现。

在哪里找到更多信息

WordPress 开发者资源在通用 API 手册中有一个关于国际化的优秀部分。它包括一个过程概述,以及一个列出 WordPress 中常用于国际化的函数的页面。它还包含一个关于国际化指南的部分,对于任何希望使其 WordPress 插件或主题可翻译的开发者来说,这都是必读内容。

安全漏洞检测工具

为了帮助你开发安全的 WordPress 插件和主题,有许多工具可以帮助你检测代码中的安全漏洞。

在本课中,你将了解一些可用于测试代码安全漏洞的工具,并简要介绍如何使用它们。

你还将了解在哪里可以找到关于 Web 应用程序关键安全风险的更多信息。

插件

WordPress.org 仓库中有两个插件可以帮助你验证代码。

Plugin Check 是一个 WordPress 插件,你可以用它来测试你的插件是否符合 WordPress.org 插件目录的要求标准,其中一项要求就是你的插件代码是安全的。

安装并激活该插件后,你可以从 WordPress 管理仪表盘的“工具”菜单中访问 Plugin Check 管理页面。

选择你要测试的插件,并确保勾选了“安全”复选框。

点击检查!按钮运行测试。

结果会以表格形式显示,列出需要解决的问题,包括文件名和行号。

还有一个 Theme Check 插件,它与 Plugin Check 类似,但适用于主题。

安装此插件后,你可以从 WordPress 管理仪表盘的“外观”菜单中访问 Theme Check 管理页面。

同样,你选择要检查的主题,然后点击检查!按钮运行测试。

结果会显示在页面底部,列出需要解决的问题。

命令行

如果你想要可以从命令行运行的工具,可以使用 PHP_CodeSniffer 配合 WordPress 编码标准规则。

这种组合不仅会根据 WordPress 编码标准检查你的代码,还会检查安全漏洞。

要使用 PHP_CodeSniffer 配合 WordPress 编码标准,你需要安装 Composer,这是一个用于 PHP 项目的依赖管理器。

安装 Composer 不在本课的讨论范围内,但你可以在 Composer 网站上找到适用于 macOS/Linux 和 Windows 操作系统的安装说明。

为了让 Composer 工作,你还需要在计算机上安装 PHP,这样你就可以使用 PHP CLI 二进制文件,它允许你在终端中运行 PHP 脚本,而不仅仅是在浏览器中。

你可以在 PHP 手册的“安装与配置”部分找到在你的系统上安装 PHP 的方法。

安装 PHP 后,请确保将 PHP CLI 二进制文件的路径添加到计算机的操作系统路径中,这样你就可以在计算机的任何位置运行 PHP 命令。

要测试这一点,你可以在终端中运行以下命令:

php -v

它应该会输出 PHP 版本。

安装 Composer 后,你在终端中运行以下命令,在你的插件或主题目录中初始化一个新的 Composer 项目:

composer init

按照终端内的说明创建一个新的 Composer 项目。

这将在当前目录中创建一个 composer.json 文件,其中包含你项目的依赖项列表。

接下来,按照 WordPress 编码标准规则仓库中的说明,在你的插件目录中安装所有必需的依赖项:

composer require --dev wp-coding-standards/wpcs:"^3.0"

安装完成后,你可以使用 WordPress 标准对代码运行 PHP_CodeSniffer,它会输出需要解决的问题列表。

vendor/bin/phpcs --standard=WordPress your-plugin-file.php

代码编辑器

根据你使用的代码编辑器,有多种方法可以在编码时检查漏洞。

例如,Visual Studio Code 有许多扩展可以在编辑器内运行 PHP_CodeSniffer 工具,你可以在 VS Code 扩展市场中搜索“phpcs”找到它们。

此外,还有一些第三方服务可以在你的代码编辑器中扫描代码漏洞,例如 Sonar 的 SonarLint 工具。

SonarLint 对所有开源项目完全免费。只有当你想要分析私有仓库时才需要付费。

SonarLint 可作为 Visual Studio Code、JetBrains 编辑器和 Eclipse 的插件使用。

配置正确后,这些扩展可以在你编写代码时高亮显示问题,这样你就可以在提交代码之前修复它们。

OWASP

虽然不特指某个工具,但熟悉开放 Web 应用程序安全项目(OWASP)也是一个好主意。

OWASP 是一个致力于提高软件安全性的非营利基金会。他们提供了许多资源,包括 OWASP Top 10,这是 Web 应用程序最关键的十大安全风险列表。

安全是一个不断变化的领域,漏洞也会随着时间的推移而演变。通过遵循开放网络应用安全项目(OWASP)十大列表,你可以及时了解最新的安全风险和最佳实践。

修复常见安全漏洞

在本模块的第一课中,您学习了开发自定义插件或主题时应遵循的主要安全原则。

在本课中,您将学习如何在开发 WordPress 插件和主题时应用这些原则,通过修复一个编码不安全的表单提交插件来实践。

编码糟糕的表单提交插件

首先,下载 Learn Plugin Security 插件,并将其安装到您的本地开发环境中。

然后,在代码编辑器中打开主插件文件,查看代码。

  1. 在主插件 PHP 文件的顶部,设置了一些常量,这些常量在插件其他地方使用。前两个用于定义插件将用于重定向的页面别名。为了使插件功能正常工作,这些页面必须存在,且具有正确的别名。
  2. 接下来,在插件激活钩子上注册了一个回调函数。这会在数据库中创建一个自定义的 form_submissions 表。
  3. 之后,插件的管理 JavaScript 和前端样式 CSS 文件被加载。
  4. 接着,注册了一个短代码,用于在前端显示表单。
  5. 然后,一个回调函数被挂载到 wp 动作上,插件使用它来处理表单提交。
  6. 接下来,插件注册了一个管理子菜单,用于显示表单提交列表。
  7. 有一个函数,管理子菜单使用它从数据库中获取表单提交数据。
  8. 最后,有一个回调函数被挂载到 wp_ajax 函数上,这是插件用于从管理子菜单页面删除表单提交的 Ajax 端点。

管理 JavaScript 文件处理从提交页面删除表单提交的 Ajax 请求。

前端 style.css 文件在用户为短代码输入 class 属性时使用。默认颜色为红色,但用户可以将其更改为蓝色。它只是为表单添加了一个边框。

当短代码被添加到文章或页面时,它会渲染表单,用户可以提交他们的详细信息。表单提交后,会根据提交是否成功重定向到成功或错误页面。然后在仪表盘中,管理员可以查看表单提交,并使用 Ajax 删除提交。

SQL 注入

我们要查找的第一个常见漏洞是 SQL 注入。

当输入的值没有被正确清理,允许使用输入数据的任何 SQL 命令可能在数据库上执行时,就会发生 SQL 注入。

我们需要解决潜在 SQL 注入漏洞的第一个地方是在 wp_learn_maybe_process_form 函数中。

function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }
    $name = $_POST['name'];
    $email = $_POST['email'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
    $result = $wpdb->query($sql);
    if ( 0 < $result ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}
  1. 我们需要确保在查询中使用任何 $_POST 数据之前,先对其进行清理。
  2. 我们需要使用 wpdb 的 prepare 或 insert 函数。
function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }
    $name = sanitize_text_field($_POST['name']);
    $email = sanitize_email($_POST['email']);

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows = $wpdb->insert(
        $table_name,
        array(
            'name' => $name,
            'email' => $email,
        )
    );
    if ( 0 < $rows ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}

这将确保名称和电子邮件字段值在从表单提交请求中接收时都被清理,并且在用于将记录存储到数据库之前也被清理。虽然这看起来可能有些过度,但如果您只清理输入,而代码后来被更改为以不同方式使用这些值,您仍然可能容易受到 SQL 注入攻击。

另一个需要防止 SQL 注入的地方是在 wp_learn_delete_form_submission 函数中。

  1. 我们需要确保在查询中使用任何 $_POST 数据之前,先对其进行清理。
  2. 我们需要使用 wpdb 的 prepare 或 delete 函数。
function wp_learn_delete_form_submission() {
    $id = (int) $_POST['id'];
    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows_deleted = $wpdb->delete( $table_name, array( 'id' => $id ) );
    if ( 0 < $rows_deleted ) {
        $result = 'success';
    } else {
        $result = 'error';
    }
    return wp_send_json( array( 'result' => $result ) );
}

由于这是一个整数,我们可以使用 PHP 类型转换功能来确保它始终被转换为整数。

跨站脚本攻击 (XSS) (3:47)

我们要查找的下一个常见漏洞是跨站脚本攻击 (XSS)。

当恶意方将 JavaScript 注入到网页中时,就会发生跨站脚本攻击 (XSS),这可用于从网站发起多种不同的攻击或恶意活动。

您可以通过转义输出、剥离不需要的数据来避免 XSS 漏洞。您的代码应根据要转义的内容类型,使用适当的函数转义动态内容。

让我们看看一些输出数据的地方,并确保它们被正确转义。

第一个地方是在 wp_learn_form_shortcode 短代码回调的包装 div 中。

<div id="wp_learn_form" class="<?php echo $atts['class'] ?>">

在这里,div 的 class 属性是根据传递给短代码的属性渲染的。这是一个潜在的 XSS 漏洞,因为 class 属性没有被转义。

<div id="wp_learn_form" class="<?php echo esc_attr( $atts['class'] ) ?>">

请注意,您应该专门使用 esc_attr 函数来转义 HTML 属性。

接下来,我们有 wp_learn_render_admin_page 函数,它用于渲染管理页面。

function wp_learn_render_admin_page(){
    $submissions = wp_learn_get_form_submissions();
    ?>
    <div class="wrap" id="wp_learn_admin">
        <h1>Admin</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
            </thead>
            <?php foreach ($submissions as $submission){ ?>
                <tr>
                    <td><?php echo $submission->name?></td>
                    <td><?php echo $submission->email?></td>
                    <td><a class="delete-submission" data-id="<?php echo $submission->id?>" style="cursor:pointer;">Delete</a></td>
                </tr>
            <?php } ?>
        </table>
    </div>
    <?php
}

这里,$submission->name、$submission->email 和 $submission->id 应该被转义。

<?php foreach ($submissions as $submission){ ?>
    <tr>
        <td><?php echo esc_html($submission->name)?></td>
        <td><?php echo esc_html($submission->email)?></td>
        <td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>
    </tr>
<?php } ?>

在这个示例中,你可以使用 esc_html,因为当 HTML 元素包含要显示的数据段时,这是正确的函数。务必密切关注每个转义函数的作用,因为有些会移除 HTML,而有些则允许保留。

你必须根据要输出的内容和上下文,选择最合适的函数。

最后,你将 ID 转换为整数,因为它被用于数据属性中。

跨站请求伪造 (CSRF)

下一个需要防范的漏洞是跨站请求伪造。CSRF 是指恶意方诱骗用户在他们已认证的 Web 应用中执行非预期的操作。

在使用 WordPress 开发时,熟悉 WordPress nonce 是帮助防范 CSRF 的必备技能。

Nonce 是“一次性使用的数字”,它提供了一种验证请求来源是否合法的方式。

  1. 当你需要验证请求是否合法时,创建一个 nonce。
  2. 你将 nonce 输出或传递给需要发起请求的地方。
  3. 当请求发起时,你验证该 nonce。

这个插件中存在两个可能的 CSRF 漏洞。

第一个漏洞出现在表单提交并处理数据时。要修复此问题,我们需要在短代码渲染的表单中添加一个 nonce,然后在表单提交时验证它。

在表单中,我们使用 wp_nonce_field 函数来添加一个包含 nonce 的隐藏字段。

<?php
wp_nonce_field( 'wp_learn_form_nonce_action', 'wp_learn_form_nonce_field' );
?>

注意你是如何传入一个 action 和一个 name 的。action 用于标识 nonce,而 name 是添加到表单中的字段名称。

如果你检查表单,可以看到 nonce 字段,它使用了你传递给函数的名称以及 nonce 值。

然后在表单提交函数中,你使用 wp_verify_nonce 函数来验证 nonce,传入 nonce 字段的值和 action。

    /**
     * 04 (b). Verify the nonce
     * https://developer.wordpress.org/apis/security/nonces/
     */
    if ( ! isset( $_POST['wp_learn_form_nonce_field'] ) || ! wp_verify_nonce( $_POST['wp_learn_form_nonce_field'], 'wp_learn_form_nonce_action' ) ) {
        wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
        die();
    }

这里我们检查 nonce 字段是否已在请求中传递,然后验证 nonce 是否有效。如果 nonce 未传递或无效,我们将重定向到错误页面。

另一个需要防范 CSRF 的地方是用于删除表单提交数据的 Ajax 回调。

function wp_learn_delete_form_submission() {
    $id = (int) $_POST['id'];
    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $rows_deleted = $wpdb->delete( $table_name, array( 'id' => $id ) );
    if ( 0 < $rows_deleted ) {
        $result = 'success';
    } else {
        $result = 'error';
    }
    return wp_send_json( array( 'result' => $result ) );
}

要修复此问题,首先我们需要使用 wp_create_nonce 手动创建一个 nonce,然后使用 wp_localize_script 将 nonce 传递给 JavaScript 层。

  /**
   * 04 (a). Add an ajax nonce to the script
   * https://developer.wordpress.org/apis/security/nonces/
   */
  $ajax_nonce = wp_create_nonce( 'wp_learn_ajax_nonce' );
  wp_localize_script(
      'wp-learn-admin',
      'wp_learn_ajax',
      array(
          'ajax_url' => admin_url( 'admin-ajax.php' ),
          'nonce'    => $ajax_nonce,
      )
  );

然后,我们需要在 jQuery POST 请求中包含该 nonce。

jQuery.post(
    wp_learn_ajax.ajax_url,
    {
        '_ajax_nonce': wp_learn_ajax.nonce,
        'action': 'delete_form_submission',
        'id': id,
    },
    function (response) {
        console.log( response );
        alert( 'Form submission deleted' );
        document.location.reload();
    }
);

注意我们如何在 POST 请求中将 nonce 指定为 _ajax_nonce。这是 WordPress 在处理 Ajax 请求时预期的 nonce 名称。

最后,在 Ajax 回调中,我们使用方便的 check_ajax_referrer 函数来验证 nonce。

    check_ajax_referer( 'wp_learn_ajax_nonce' );

你会看到传递给 check_ajax_referer 的字符串与我们创建 nonce 时传递给 wp_create_nonce 的字符串相同。

如果 check_ajax_referrer 失败,它将导致执行停止,因此我们不需要检查该函数的结果。

访问控制失效

这个插件中还有一个漏洞,即访问控制失效。BAC 是指用户能够访问他们本不应访问的资源。例如,用户可能能够访问管理员功能,即使他们不是管理员。

在我们的示例中,Ajax 函数存在一个访问控制失效漏洞。目前,任何人都可以使用正确的数据向 Ajax 请求 URL 发起请求,从而删除一个 form_submission。

要修复此问题,我们可以使用 WordPress 角色和权限 API 来检查用户是否具有删除表单提交数据的正确权限。在这种情况下,只需检查用户是否为管理员用户即可。

    if ( ! current_user_can( 'manage_options' ) ) {
        return wp_send_json( array( 'result' => 'Authentication error' ) );
    }

注意我们在这里进行了两次检查,一次针对 CSRF,一次针对访问控制。在这个示例中,执行顺序不是特别重要,但通常来说,先检查 CSRF,再检查访问控制是一个好习惯。

附加题 – 开放重定向

这个插件中还有一个额外的安全漏洞。你能找到它吗?

这是一个很难发现的漏洞,但所有使用 wp_redirect 的地方都应替换为 wp_safe_redirect。这是因为代码正在重定向到本地 URL,而 wp_safe_redirect 会检查其使用的 $location 在具有绝对路径时是否为允许的主机。这可以防止如果重定向 $location 受到攻击时可能发生的恶意重定向。

进一步阅读

要了解更多关于修复 WordPress 代码中常见漏洞的信息,请务必收藏 WordPress 开发者文档安全部分中关于常见漏洞的页面,以及包含权限检查、数据验证、安全输入、安全输出和 nonce 的示例。

安全开发插件和主题

到目前为止,你已经熟悉了为 WordPress 开发插件和主题的基础知识。现在也是考虑编写代码时安全影响的好时机。

在本课中,你将学习如何以安全为出发点进行开发。

你将了解确保代码安全的好处、保护代码安全的步骤,以及在哪里可以找到更多关于以安全为出发点进行开发的信息。

免责声明

本课旨在作为开发插件和主题时培养安全意识的入门介绍。本课中使用的代码非常简化。你不应在插件或主题中使用本教程中的任何代码。

请务必阅读 WordPress 开发者手册中关于安全的完整文档,网址为 https://developer.wordpress.org/apis/security/,以确保你遵循正确的方法和流程。

什么是以安全为出发点进行开发?

以安全为出发点进行开发,是指确保你的代码不仅能够正常工作,而且不会引入任何安全漏洞的过程。

如果你的插件或主题代码存在安全漏洞,可能会使任何安装了你的产品的 WordPress 网站面临潜在攻击,并可能导致该网站被入侵。

在编写代码时,培养安全思维并思考你的代码可能被恶意利用的方式非常重要。

  • 不要信任任何数据,无论是用户输入、第三方 API 数据,甚至是数据库中的数据。始终检查以确保数据有效且使用安全。
  • WordPress 提供了许多 API,可以帮助你完成常见任务,例如清理用户输入、验证数据和转义输出。依赖这些 API 来帮助验证和清理数据,而不是编写自己的函数。
  • 及时了解常见漏洞,并保持代码更新以防止它们。

安全在开发过程中处于什么位置?

安全应该在开发过程的每个阶段都加以考虑。

通常,发现的大多数安全漏洞都发生在 PHP 层,该层在服务器上执行。

因此,本课将重点介绍 PHP 代码中最常见的预防措施。

清理输入

开发时要采取的首要步骤之一是确保任何用户输入都经过清理。这意味着任何来自用户的数据,例如表单提交或 URL 参数,都要经过检查以确保使用安全。

在此示例代码中,名称和电子邮件字段是从插件生成的表单提交的,然后保存在名为 form_submissions 的自定义数据库表中。

$name = $_POST['name'];
$email = $_POST['email'];
global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';

$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);

如你所见,数据直接保存到数据库,没有任何清理。

这意味着如果用户提交的名称为 John'; DROP TABLE form_submissions;--,SQL INSERT 查询将运行,随后是 DROP 查询,该表将从数据库中删除!

WordPress 提供了一个清理 API,可用于清理传入的数据。你可以使用 sanitize_text_fieldsanitize_email 函数在名称和电子邮件字段用于查询之前对其进行清理。

$name = sanitize_text_field( $_POST['name'] );
$email = sanitize_email( $_POST['email'] );

global $wpdb;
$table_name = $wpdb->prefix . 'form_submissions';

$sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
$result = $wpdb->query($sql);

请注意,代码遵循了清理数据的一个关键原则:尽可能早地进行清理。

要了解更多关于 WordPress 开发者可用的清理函数,请查看 WordPress 开发者文档中的“清理数据”页面。

验证数据 (4:06)

验证数据是根据预定义模式(或多个模式)对其进行测试的过程,结果明确:有效或无效。

不可信数据可能来自许多来源:用户、第三方 API 数据,甚至你的数据库数据也可能被视为不可信,尤其是当其他内容修改过它时。即使是网站管理员也可能犯错,输入不正确或不安全的数据,因此始终检查数据非常重要。

在此示例中,删除函数要求将数字 ID 发布到 admin-ajax 回调:

add_action( 'wp_ajax_delete_form_submission', 'wp_learn_delete_form_submission' );
function wp_learn_delete_form_submission() {
    if ( ! isset( $_POST['id'] ) ) {
        wp_send_json_error( 'Invalid ID' );
    }
    $id = $_POST['id'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql    = "DELETE FROM $table_name WHERE id = $id";
    $result = $wpdb->get_results( $sql );

    return wp_send_json( array( 'result' => $result ) );
}

这里 ID 直接用于 SQL 查询,没有任何验证。同样,这意味着如果用户提交的 ID 为 1; DROP TABLE form_submissions;,相同的 SQL DROP 查询将在删除后运行,表将被删除。

为了防止这种情况,你可以使用 PHP 的类型转换功能,确保 $id 的值始终是整数。可以通过在变量名前添加 (int) 来实现,如下所示:

$id = (int) $_POST['id'];

请注意,这仅当通过 $_POST 数组传递的字符串的第一个字符可以转换为整数时才有效,否则 $id 的值将为 0。在这种情况下,最好更新代码以处理这种情况。

该代码还遵循了验证数据的一个关键原则,即尽早进行验证。

if ($id === 0){
    // return early with an error
    return wp_send_json( array( 'result' => 'Invalid ID passed' ) );

}

要了解更多关于验证数据的不同方法,请查看 WordPress 开发者文档中的“验证数据”部分。

转义输出

安全的另一个方面是确保你输出到浏览器的任何信息都是安全的,包括任何文本、HTML 或 JavaScript 代码,以及来自数据库的数据。

即使你的代码不负责所显示数据的来源,它也有责任安全地显示它。

在这个例子中,代码从数据库中获取表单提交数据,然后循环遍历这些提交数据,并在 WordPress 仪表盘的管理界面中输出提交数据:

    $submissions = wp_learn_get_form_submissions();
    ?>
    <div class="wrap" id="wp_learn_admin">
        <h1>Admin</h1>
        <table>
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                </tr>
            </thead>
            <?php foreach ($submissions as $submission){ ?>
                <tr>
                    <td><?php echo $submission->name; ?></td>
                    <td><?php echo $submission->email; ?></td>
                    <td><a class="delete-submission" data-id="<?php echo $submission->id?>" style="cursor:pointer;">Delete</a></td>
                </tr>
            <?php } ?>
        </table>
    </div>
    <?php

这里我们有三个需要转义的数据项:$submission->name$submission->email 字段,以及 $submission->id

<td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>

对于名称和电子邮件字段,我们可以使用 WordPress 内置的转义函数 esc_html()。ID 可以通过将其转换为整数来转义,就像你在数据验证时可能做的那样。

  <td><?php echo esc_html( $submission->name ); ?></td>
  <td><?php echo esc_html( $submission->email ); ?></td>
  <td><a class="delete-submission" data-id="<?php echo (int) $submission->id?>" style="cursor:pointer;">Delete</a></td>

请注意,这段代码遵循了转义数据的一个关键原则,即尽可能晚地转义数据。

要了解更多关于转义输出的不同方法,请查看 WordPress 开发者文档中的“转义数据”部分。

防止无效请求

每当发出请求时,检查该请求是否有效非常重要。这意味着要检查请求是否来自可信来源。

例如,你可能有一个短代码用于渲染表单,用户可以通过它提交信息。

渲染表单的函数可能看起来像这样。

add_shortcode( 'wp_learn_form_shortcode', 'wp_learn_form_shortcode' );
function wp_learn_form_shortcode() {
    ob_start();
    ?>
    <form method="post">
        <input type="hidden" name="wp_learn_form" value="submit">
        <div>
            <label for="email">Name</label>
            <input type="text" id="name" name="name" placeholder="Name">
        </div>
        <div>
            <label for="email">Email address</label>
            <input type="text" id="email" name="email" placeholder="Email address">
        </div>
        <div>
            <input type="submit" id="submit" name="submit" value="Submit">
        </div>
    </form>
    <?php
    $form = ob_get_clean();
    return $form;
}
?>

当用户提交表单时,数据会被发送到服务器。然后数据被处理、清理并存储在数据库中。

add_action( 'wp', 'wp_learn_maybe_process_form' );
function wp_learn_maybe_process_form() {
    if (!isset($_POST['wp_learn_form'])){
        return;
    }

    $name = sanitize_text_field( $_POST['name'] );
    $email = sanitize_email( $_POST['email'] );

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql = "INSERT INTO $table_name (name, email) VALUES ('$name', '$email')";
    $result = $wpdb->query($sql);
    if ( 0 < $result ) {
        wp_redirect( WPLEARN_SUCCESS_PAGE_SLUG );
        die();
    }

    wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
    die();
}

由于表单可能出现在任何使用该短代码的页面上,恶意用户可能会尝试向表单发送 POST 请求,要么寻找插件中的漏洞,要么发送多个请求。

为了防止这种情况,你可以检查请求是否来自可信来源。为此,你可以实现一种称为 nonce(一次性数字)的机制。

首先,在表单本身中,你可以通过使用 wp_nonce_field 函数添加一个 nonce 字段,并向该函数传递一个 nonce 操作和 nonce 名称:

wp_nonce_field( 'wp_learn_form_nonce_action', 'wp_learn_form_nonce_field' );

当表单在前端渲染时,会向表单中添加一个隐藏字段,使用 nonce 名称作为隐藏字段的 id 和 name 属性,并将生成的 nonce 作为字段值。当表单提交时,这些数据会被发送。

然后,在处理表单数据的函数中,你可以通过使用 wp_verify_nonce 函数来验证 nonce 是否有效,并将 POST 提交的 nonce 字段和 nonce 操作传递给该函数。如果验证结果为 false,你可以提前退出,防止任何进一步的代码执行。

    if ( ! wp_verify_nonce( $_POST['wp_learn_form_nonce_field'], 'wp_learn_form_nonce_action' ) {
        wp_redirect( WPLEARN_ERROR_PAGE_SLUG );
        die();
    }

任何时候你的代码发出网络请求,无论是通过重定向到新 URL、向表单 POST 数据,还是发出 AJAX 请求,你都应该检查请求是否有效。

要了解更多关于如何在插件中使用 nonce,请查看 WordPress 开发者文档中的“Nonce”部分。

防止未认证用户

根据代码的功能,最好将某些功能限制为仅具有特定权限级别的用户使用。例如,你可能有一个从数据库中删除数据的函数。

function wp_learn_delete_form_submission() {
    if ( ! isset( $_POST['id'] ) ) {
        wp_send_json_error( 'Invalid ID' );
    }
    $id = (int) $_POST['id'];

    global $wpdb;
    $table_name = $wpdb->prefix . 'form_submissions';

    $sql    = "DELETE FROM $table_name WHERE id = $id";
    $result = $wpdb->get_results( $sql );

    return wp_send_json( array( 'result' => $result ) );
}

虽然你可能会尽力防止任何没有权限的人运行此函数,但针对这种情况添加检查仍然是一个好主意。

WordPress 包含一个强大的用户角色和权限系统,允许你使用默认的用户角色和权限,或创建自定义角色和权限。

在这种情况下,可以简单地只允许具有管理站点选项权限的用户执行操作,这是管理员角色中包含的标准权限。

function wp_learn_delete_form_submission() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return wp_send_json( array( 'result' => 'Authentication error' ) );
    }
    // rest of function code
}

WordPress 开发者文档中有一个关于用户角色和权限的详细部分,其中包含默认权限列表以及如何创建自定义权限。

进一步阅读

为了准备好以安全为出发点进行开发,请务必阅读 WordPress 开发者文档中关于安全性的条目,其中包含本课程中的所有示例,以及关于安全最佳实践、常见漏洞和更多示例代码的额外信息。

扩展WordPress REST API

WordPress REST API 提供了一种统一的接口,用于从 WordPress 站点获取、添加、更新和删除数据。

虽然 REST API 中可用的数据类型模式相当广泛,但有时你可能需要存储不属于核心模式的其他数据。

在本课中,你将学习两种在 REST API 请求中添加字段的方法:一种是在 REST API 路由中启用自定义字段,另一种是将自定义字段作为顶级字段提供。

你还将了解这两种方法的优缺点。

如果你跳过了本模块中的前几课,请从仓库自述文件中的链接下载 Bookstore 插件 1.0.2 版本,并在本地 WordPress 安装中安装并激活该插件。

此外,如果你还没有这样做,请下载并安装适用于你操作系统的 Postman 应用。

关于修改响应的重要说明

在开始之前,务必注意修改 WP REST API 响应可能会产生意外后果。从核心 REST API 端点响应中更改或删除数据可能会破坏插件或 WordPress 核心行为,应尽可能避免。

如果你需要从 REST API 请求中检索数据子集,推荐的方法是使用 _fields 全局参数来限制响应中返回的字段。

例如,如果你只需要应用程序中的 id 和 title 字段,可以使用 fields 参数将返回的字段限制为仅这两个。

向 REST API 响应添加字段风险较小,因此本课仅涵盖添加字段。

使用自定义字段

如果你观看了“WordPress 插件简介”模块中的课程,你应该已经了解了自定义字段(也称为元数据)。

这些字段通常用于自定义文章类型,以存储特定于该文章类型的额外数据。在底层,这些自定义字段存储在 postmeta 表中,作为一组键/值对,通过文章 ID 附加到文章上。

除了文章,WordPress 还支持其他数据类型(如评论和用户)的元数据。你可以在元数据 API 文档中了解更多信息。

WP REST API 允许你在创建或更新数据时创建或更新自定义字段。

这可以通过将键/值对对象传递给所操作文章类型的 meta 属性来实现。

但是,要使用自定义字段,你必须先注册它。这可以使用 register_meta 函数完成。

注册自定义字段

如果你想在文章上注册一个名为 location 的自定义字段,你可以像这样使用 register_meta 函数:

register_meta( 'post', 'location', array(
    'type'         => 'string',
    'description'  => 'A location for the post',
    'single'       => true,
    'show_in_rest' => true,
) );

虽然可以将其添加到插件的任何位置,但建议将其添加到类似 init 动作钩子的地方。

add_action( 'init', function() {
    register_meta( 'post', 'location', array(
        'type'         => 'string',
        'description'  => 'A location for the post',
        'single'       => true,
        'show_in_rest' => true,
    ) );
} );

务必确保 show_in_rest 属性设置为 true,否则自定义字段将无法在 REST API 中使用。

这将使自定义字段能够添加到文章的 REST API 模式中,并且还允许你使用 REST API 将数据发布到自定义字段。这通过在请求体的 meta 对象 中传递自定义字段作为键值对来处理。

{
    "title": "My Post",
    "content": "This is my post content.",
    "meta": {
        "location": "New York"
    }
}

你可以通过在 Postman 中创建一个新的 POST 请求并将其发送到文章路由来测试这一点。

为特定 WP REST API 路由启用自定义字段

WordPress 4.9.8 之前,使用 register_metashow_in_rest 设置为 true 的自定义字段会注册到给定类型的所有对象上。例如,如果你向文章类型添加了一个自定义字段,然后创建了一个自定义文章类型,该自定义字段将自动在自定义文章类型中可用。

WordPress 4.9.8 开始,可以使用带有 object_subtype 参数的 register_meta,该参数允许将元键的使用范围限制到特定的文章类型。

例如,假设你想仅在 book 自定义文章类型上注册一个 ISBN 自定义字段。你可以将其添加到注册自定义文章类型的代码中,如下所示:

register_post_type( 'book', array(
    'labels'      => array(
        'name'          => __( 'Books', 'bookstore' ),
        'singular_name' => __( 'Book', 'bookstore' ),
    ),
    'public'      => true,
    'has_archive' => true,
    'show_in_rest' => true,
    'supports'    => array( 'title', 'editor', 'custom-fields' ),
) );

register_meta( 'post', 'isbn', array(
    'type'         => 'string',
    'description'  => 'The ISBN of the book',
    'single'       => true,
    'show_in_rest' => true,
    'object_subtype' => 'book',
) );

现在,isbn 自定义字段将仅对 book 自定义文章类型可用。

你可以通过 REST API 添加一本书来测试这一点。

在 Postman 中,创建或更新对书籍路由的 POST 请求,并在 meta 对象中包含 ISBN 字段:

POST /wp-json/wp/v2/books

然后在请求体中发布以下内容:

{
    "title": "My Book",
    "content": "This is my book content.",
    "meta": {
        "isbn": "978-3-16-148410-0"
    }
}

如果你随后在 WordPress 管理后台编辑这本书,并且启用了自定义字段面板,你将看到 isbn 字段的值。它也是 book 文章类型唯一可用的自定义字段。

将自定义字段添加为 API 响应的顶级字段

另一种将自定义字段添加到 WP REST API 的方法是将它们添加为 API 响应的顶级字段。

在之前的示例中,isbn 被注册为元字段,因此它出现在 REST API 响应的元对象中。但如果你希望它作为顶级字段,与标题、内容和摘要并列,该怎么办呢?

这可以通过使用 register_rest_field 函数来实现。让我们看看如何实现这一点。

将自定义字段添加为顶级字段

首先,你需要在 rest_api_init 动作钩子中注册你的 REST 字段。这是为了确保该字段仅在 REST API 上注册。

add_action( 'rest_api_init', 'bookstore_add_rest_fields' );
function bookstore_add_rest_fields() { 
    // register some REST API functionality
}

然后,你使用 register_rest_field 函数来注册该字段。第一个参数是字段应注册的对象类型。对于单个对象,可以是字符串;对于多个对象,可以是数组。在本例中,只需在 book 自定义文章类型上注册该字段。第二个参数是字段的名称。在本例中,只需将其设置为与自定义字段相同的名称,即 isbn

    register_rest_field(
        'book',
        'isbn',
    );

第三个参数是一个参数数组,用于决定字段的功能。你至少需要向该数组传递以下三个参数。

  1. get_callback – 返回字段值的函数
  2. update_callback – 更新字段值的函数
  3. schema – 包含字段模式的数组
    register_rest_field(
        'book',
        'isbn',
        array(
            'get_callback'    => null,
            'update_callback' => null,
            'schema'          => null,
        )
    );

现在,你可以将 REST 字段的模式参数保留为 null,但你需要指定 get_callbackupdate_callback 函数。这些函数将在 API 请求发出时触发,无论是用于获取数据,还是用于创建或更新数据。

    register_rest_field(
        'book',
        'isbn',
        array(
            'get_callback'    => 'bookstore_rest_get_isbn',
            'update_callback' => 'bookstore_rest_update_isbn',
            'schema'          => null,
        )
    );

默认情况下,文章类型的准备数据数组会作为第一个参数传递给 get_callback 函数。这个函数的实现可以简单到直接返回自定义字段的值。

function bookstore_rest_get_isbn( $book ){
    return  get_post_meta( $book['id'], 'isbn', true );
}

从 REST API 创建或更新请求中为字段发送的值会作为第一个参数传递给 update_callback 函数,而模型对象(即文章)则作为第二个参数。这个函数的实现可以简单到直接更新自定义字段的值。

function bookstore_rest_update_isbn( $value, $book ){
    return update_post_meta( $book->ID, 'isbn', $value );
}

如果你通过创建一本新书并为 isbn 字段传递一个值来测试这一点,你会看到数据被保存到数据库的 post_meta 表中,同时也会作为顶级字段显示在 REST API 响应中。

包含模式

模式参数是一个描述字段模式的数组。虽然这不是必须的,但建议包含模式。至少,它有助于未来的开发者理解字段的用途。它还可以用于在创建自动化 API 测试时验证发送的数据。

    register_rest_field(
        'book',
        'isbn',
        array(
            'get_callback'    => 'wp_learn_rest_get_isbn',
            'update_callback' => 'wp_learn_rest_update_isbn',
            'schema'          => array(
                'description' => __( 'The ISBN of the book' ),
                'type'        => 'string',
            ),
        )
    );

你可以在 WP REST API 手册中阅读更多关于如何定义 REST API 资源和字段模式的内容。

是否使用 register_rest_field

在决定是仅使用 register_meta,还是添加 register_rest_field 的使用时,你应该考虑每种方法的优缺点。

仅使用 register_meta 方法的主要优点是,只要你记得在应用程序代码中使用元对象来获取和保存数据,就不需要添加任何额外的代码来启用自定义字段的存储或检索。你只需启用该字段在 REST API 中显示,就可以直接使用它。这也是性能更优的选择,因为它不会增加任何需要执行的额外代码。

另一方面,使用 register_rest_field 方法的优点是,你可以在数据返回或保存之前对其进行额外的处理。例如,你可以在数据保存到数据库之前对其进行一些验证。你还可以向 get_callback 和 update_callback 函数添加钩子,以便对数据进行额外处理,或允许其他开发者扩展你的自定义字段。缺点是它会为 API 请求增加一些开销,因为需要执行更多的代码。

最终,你选择哪种方法应根据具体情况来决定。

进一步阅读

有关修改 REST API 响应的更多信息,请查看 WP REST API 手册中的“修改响应”部分。

与WordPress REST API交互

虽然 WP REST API 通常用于从 WordPress 获取数据,但它也可以用来执行其他操作。

REST API 还允许你创建、更新和删除各种 WordPress 数据类型。

在本课中,你将了解 WP REST API 模式、验证 WP REST API 请求的方法、测试 WP REST API 请求的工具,以及通过 WP REST API 添加、编辑或删除数据的几种方式。

如果你跳过了本模块前面的课程,请从仓库自述文件中的链接下载 Bookstore 插件 1.0.1 版本,并在本地 WordPress 安装中安装并激活该插件。

WP REST API 模式

在使用 REST API 时,最好将 WP REST API 文档中的端点参考部分放在手边。端点参考列出了 WordPress 核心附带的所有端点。

点击某个端点,例如文章,将显示该端点的模式。模式定义了在获取或创建特定类型数据时,该资源存在的所有字段。

如果你创建了自定义文章类型,比如 bookstore 插件中的书籍自定义文章类型,那么自定义文章类型端点的模式将与文章端点类似。

你会注意到,许多端点字段与 WordPress 数据库中与该数据类型相关的表中的字段相匹配。然而,有些字段略有不同。例如,文章端点的标题字段将与文章表中的 post_title 字段对应。记住这些差异很重要,并且在通过 API 交互时使用正确的字段名称。

身份验证

默认情况下,WordPress REST API 使用与登录 WordPress 仪表盘时相同的基于 Cookie 的身份验证方法。

对于任何非公开的 REST API 端点,或者需要验证用户才能查看或修改的端点,必须存在身份验证 Cookie。

例如,块编辑器就是这样工作的。

有多种验证请求的方法,包括 JSON Web 令牌和 OAuth。

WordPress 内置的另一种方法是应用程序密码。

应用程序密码可以按用户设置,用于验证对 WP REST API 的请求。这允许你让用户访问 API,而无需共享他们用于登录 WordPress 仪表盘的密码。

要为你的用户创建应用程序密码,请导航到用户列表中的该用户,点击该用户进行编辑。滚动到屏幕底部,在应用程序密码部分下。

为新的应用程序密码命名,然后点击添加新应用程序密码。

密码将为你生成。请务必复制并安全地存储它,因为你将无法再次看到它。

在此屏幕上,如果密码泄露,你也可以撤销它。

使用应用程序密码为你的用户测试 REST API 请求,是使用 REST API 测试工具的好方法。

如果你打算构建更复杂的东西,比如连接到 WordPress REST API 的移动应用,你应该考虑使用 JSON Web 令牌或 OAuth 1.0a。

Postman

有许多工具可用于测试 REST API 请求。

例如,如果你使用 PhpStorm,它内置了 HTTP 客户端;如果你使用 VS Code,则有像 Postcode 这样的扩展。还有像 Hoppscotch 和 Postman 这样的独立工具。你甚至可以在终端中使用 curl 命令测试 REST API 端点。

为了本课的目的,你将学习如何使用 Postman 测试一些 REST API 请求。

你可以从 Postman 网站下载 Postman。默认情况下,Postman 会创建一个初始工作区,用于存储你的请求集合。

安装后,打开 Postman,点击创建集合按钮。这将创建一个新集合,你可以在其中添加多个请求进行测试。

你可以为集合命名,以区别于其他集合。在集合内部,点击添加请求按钮。

这将打开一个新请求,你可以为请求指定一个唯一名称。然后,输入本地 books 端点的 URL,并点击发送按钮。

http://yourlocal.wordpress.site/wp-json/wp/v2/books

请求将被发送,JSON 响应将被解析并显示在响应区域中。

现在,创建一个新请求,输入相同的 books 端点 URL,但这次将请求方法更改为 POST,然后点击发送。通过将请求方法更改为 POST,你告诉服务器你想要创建或可能更新一本书。

这次你将看到一个错误消息,因为你没有经过身份验证。

要验证请求,请点击“授权”选项卡,然后从下拉菜单中选择“基本认证”。

接着,输入你的用户名以及之前创建的应用程序密码,然后点击保存按钮。

这次你不会遇到同样的错误,因为你已经通过了身份验证。现在你可以创建书籍了。

继续操作,点击请求中的“正文”选项卡,并选择“原始”单选按钮。然后,从下拉菜单中选择 JSON,并输入以下 JSON 内容:

{
    "title": "My Postman Book",
    "content": "This is my Postman book",
    "status": "publish"
}

再次点击发送,书籍将被创建,并返回新书籍的 JSON 响应。

为了确认,请前往 WordPress 后台检查书籍列表,你应该能看到这本书。

要更新一本书,你可以使用与添加书籍相同的请求配置,但需要修改端点 URL,使其包含书籍 ID。

要删除一本书,你可以使用与更新书籍相同的端点 URL,但需要将请求方法改为 DELETE,并且不在请求正文中发送任何数据。

你还会注意到,删除一篇文章实际上会将其移至回收站,而不是永久删除。这与 WordPress 后台的行为一致。

使用 Postman 这样的工具来测试 REST API 端点,是学习如何使用 WP REST API 的好方法。它对于测试 WP REST API 请求也非常有用,可以确保你打算发送的数据格式正确,并且请求被发送到了正确的端点。

创建一本书

让我们使用 WP REST API 和 api-fetch 来创建一本新书。

为此,我们需要将标题和内容字段作为 POST 请求发送到 books 端点。

你已经有一个可以列出书籍的插件,所以可以以此为基础开始。

首先,你需要更新页面,添加一个表单,用于输入要创建的书籍的标题和内容。你可以使用以下 HTML 来创建表单,并将其添加到 bookstore_render_booklist() 管理页面回调函数中,放在现有 HTML 代码下方:

<div style="width:50%;">
    <h2>Add Book</h2>
    <form>
        <div>
            <label for="bookstore-book-title">Book Title</label>
            <input type="text" id="bookstore-book-title" placeholder="Title">
        </div>
        <div>
            <label for="bookstore-book-content">Book Content</label>
            <textarea id="bookstore-book-content" cols="100" rows="10"></textarea>
        </div>
        <div>
            <input type="button" id="bookstore-submit-book" value="Add">
        </div>
    </form>
</div>

这段 HTML 代码在自定义管理页面中添加了一个新表单,允许你输入新书的标题和内容。表单还包含一个提交按钮。

添加表单后,下一步是添加 JavaScript 代码,用于处理按钮点击事件:

const submitBookButton = document.getElementById( 'bookstore-submit-book' );
if ( submitBookButton ) {
    submitBookButton.addEventListener( 'click', function () {
        // create post code
    } );
}

现在你已经添加了按钮点击事件监听器,可以添加处理书籍创建的代码了。

为此,建议创建一个单独的函数来创建书籍,并在点击事件中调用该函数。

首先,你需要创建一个 submitBook 函数:

function submitBook() {
    // create book code
}

然后更新点击事件监听器,使其调用该函数:

submitBookButton.addEventListener( 'click', submitBook );

submitBook 函数内部,你需要从表单字段中获取标题和内容的值:

    const title = document.getElementById( 'bookstore-book-title' ).value;
    const content = document.getElementById( 'bookstore-book-content' ).value;

现在,你可以使用 api-fetch 向 books 端点发送请求,将路径设置为 books 端点,请求方法设置为 POST,并将 titlecontent 作为数据对象传递:

    wp.apiFetch( {
        path: '/wp/v2/books/',
        method: 'POST',
        data: {
            title: title,
            content: content
        },
    } ).then( ( result ) => {
        alert( 'Book saved!' );
    } );

打开自定义管理页面,输入标题和内容,然后点击“添加”按钮。你应该会看到一个提示,显示“书籍已保存!”。

然后,如果你浏览到书籍列表,就会看到新书被列出来了。

更新和删除书籍

你也可以使用 WP REST API 来更新和删除书籍。

你可以使用与添加项目相同的 api-fetch 实现来更新项目。你需要更新路径,使其包含要更新的数据实体(本例中为书籍)的 ID,以便更新该项目,同时更新数据对象,为要更新的字段提供新值。

    wp.apiFetch( {
        path: '/wp/v2/books/' + id,
        method: 'POST',
        data: {
            title: newTitle,
            content: newContent
        },
    } ).then( ( result ) => {
        alert( 'Book Updated!' );
    } );

删除一篇文章只需要将路径设置为该项目的 URL,并将方法设置为 DELETE

    wp.apiFetch( {
        path: '/wp/v2/books/' + id,
        method: 'DELETE',
    } ).then( ( result ) => {
        alert( 'Book deleted!' );
    } );

进一步阅读

有关使用 WP REST API 创建、更新和删除数据的更多信息,请查看 WP REST API 手册中的“使用 REST API”部分,以及区块编辑器手册中的 api-fetch 包参考。

使用WordPress REST API

WordPress REST API 提供了一个统一的接口,用于与 WordPress 站点中的数据进行交互。

在本课中,你将学习如何使用 WP REST API 从你的 WordPress 站点获取数据。

你将发现三种进行 REST API 请求的内部选项,然后使用它们执行 GET 请求来获取一些公开的自定义文章类型数据。

Bookstore 插件

如果你完成了“WordPress 插件入门”模块,你应该已经构建了一个注册名为 book 的自定义文章类型的插件。

如果你跳过了那个模块,你可以通过点击 Bookstore 插件链接,从 GitHub 仓库下载主插件代码。

安装并激活插件后,在你的代码编辑器中打开主插件文件。

你会注意到传递给 register_post_type 函数的参数之一是 show_in_rest

register_post_type( 'book',
    array(
        'labels'      => array(
            'name'          => __( 'Books', 'textdomain' ),
            'singular_name' => __( 'Book', 'textdomain' ),
        ),
        'public'      => true,
        'has_archive' => true,
        'show_in_rest' => true,
    )
);

此参数设置为 true,这意味着该自定义文章类型在 REST API 中可用。

这意味着如果你浏览到 wp-json/wp/v2/book 路由,你将在响应中看到自定义文章类型的数据。

如果你查看 register_post_type 函数参考,你会发现一些额外的参数可用于控制 REST API 响应。

例如,你可以更改 rest_base 参数来改变自定义文章类型数据可用的路由。

考虑到你期望能够从 book 路由获取多本书,将 rest_base 改为 books 是个好主意。

register_post_type( 'book',
    array(
        'labels'      => array(
            'name'          => __( 'Books', 'textdomain' ),
            'singular_name' => __( 'Book', 'textdomain' ),
        ),
        'public'      => true,
        'has_archive' => true,
        'show_in_rest' => true,
        'rest_base'   => 'books',
    )
);

这样做将允许你向 wp-json/wp/v2/books 路由发出请求,通过 REST API 访问书籍。

发起 REST API 请求

假设你想在 WordPress 仪表盘中添加一个页面,该页面获取书籍并以逗号分隔的列表形式显示书名和永久链接。

首先,你可以使用 admin_menu 钩子和 add_submenu_page 函数,在“书籍”菜单下添加一个管理子菜单页面。

add_action( 'admin_menu', 'bookstore_add_admin_menu' );
function bookstore_add_admin_menu() {
    add_submenu_page(
        'edit.php?post_type=book',
        __( 'Book List', 'textdomain' ),
        __( 'Book List', 'textdomain' ),
        'manage_options',
        'book-list',
        'bookstore_render_booklist'
    );
}

然后,你需要创建 bookstore_render_booklist 回调函数,该函数将输出管理页面的 HTML。

function bookstore_render_booklist() {
    ?>
    <div class="wrap">
        <h1>Book List</h1>
        <button id="load-books" class="button button-primary">Load Books</button>
        <textarea id="book-list" rows="10" cols="50"></textarea>
    </div>
    <?php
}

如果你浏览到仪表盘并点击“书籍”菜单,你会看到一个名为“Book List”的新子菜单页面。

点击该链接将带你进入一个包含“Load Books”按钮和一个文本区域的页面。

现在,你可以向 bookstore_render_booklist 函数添加功能,通过 PHP 获取书籍列表,并使按钮触发页面刷新。

然而,为了获得更流畅的用户体验,你希望使用 JavaScript 和 REST API 异步获取书籍数据并填充书籍列表,而无需等待整个页面刷新。

加载管理 JavaScript

在“WordPress 插件入门”模块中,你学习了如何在插件中加载 JavaScript 文件。

由于此功能被添加到管理仪表盘中,你需要设置一个单独的 wp_enqueue_script 函数调用,并将其挂载到 admin_enqueue_scripts 钩子上,这样 JavaScript 文件就只在管理仪表盘中加载。

首先,在插件目录中创建一个新的 JavaScript 文件,命名为 admin_bookstore.js

然后,将以下代码添加到主插件文件中,以在仪表盘中加载该 JavaScript 文件:

add_action( 'admin_enqueue_scripts', 'bookstore_enqueue_admin_scripts' );
function bookstore_enqueue_admin_scripts() {
    wp_enqueue_script(
        'bookstore-admin',
        plugin_dir_url( __FILE__ ) . 'admin_bookstore.js',
        array(),
        '1.0.0',
        true
    );
}

请注意,这段代码不仅加载了 JavaScript 文件,还指定了一个空的依赖项数组、一个版本号,并通过将最后一个参数设置为 true,指定它应在 HTML 页面的页脚中加载。

你可以在 wp_enqueue_script 函数参考的“参数”部分阅读有关这些参数的更多信息。

你可以通过在 admin_bookstore.js 文件中添加一个简单的 alert,然后刷新管理页面来测试它是否正确加载。

alert( 'Hello, World!' );

一旦你确认它正常工作,就可以从文件中删除该行。

选项 1:使用 Backbone.js 客户端

自从 REST API 被添加到 WordPress 以来,它就包含了一个 Backbone.js REST API JavaScript 客户端,用于直接向 WP REST API 发出请求。

它通过为所有通过 API 暴露的端点提供模型和集合,提供了一个使用 WP REST API 的接口。

为了确保你的 JavaScript 代码能够使用 REST API 客户端,你需要将其添加为已加载 JavaScript 的依赖项。

wp_enqueue_script 的第三个参数是一个包含所有依赖项的数组,你可以将 wp-api 作为依赖项添加到你的 wp_enqueue_script 函数调用中。

add_action( 'admin_enqueue_scripts', 'bookstore_enqueue_admin_scripts' );
function bookstore_enqueue_admin_scripts() {
    wp_enqueue_script(
        'bookstore-admin',
        plugin_dir_url( __FILE__ ) . 'admin_bookstore.js',
        array( 'wp-api' ),
        '1.0.0',
        true
    );
}

这将确保你的插件 JavaScript 代码仅在 REST API JavaScript 客户端加载完成后才加载,从而你可以在插件中使用它。

你需要先为新按钮注册一个点击事件处理器。

const loadBooksByRestButton = document.getElementById( 'bookstore-load-books' );
if ( loadBooksByRestButton ) {
    loadBooksByRestButton.addEventListener( 'click', function () {
        //do somthing
    } );
}

然后,在事件处理函数中,你可以通过全局对象 wp 访问 WP API 客户端,来创建一个新的书籍集合。

    const allBooks = new wp.api.collections.Books();

此时,allBooks 只是一个空集合,因此你需要通过调用集合的 fetch 方法来获取文章。

   allBooks.fetch();

fetch 方法返回一个 Promise,因此你可以链式调用 done 方法来处理响应,并实现一个回调函数来接收 API 请求的响应。

你可以在该回调函数中指定一个 books 参数,用于接收 API 请求的响应。

    allBooks.fetch().done(
        function ( books ) {
            // do something with books
        }
    );

现在,你可以使用类似 forEach 的方法遍历 books 对象,并逐一访问每本书。

    allBooks.fetch().done(
        function ( books ) {
            books.forEach( function ( book ) {
              // do something with book
            } );
        }
    );

最后,你可以将书名和永久链接添加到文本区域中。

首先,你需要在 forEach 循环之前创建一个文本区域的实例,然后在 forEach 循环内部将值追加到文本区域的 value 属性中。

        allBooks.fetch().done(
            function ( books ) {
                const textarea = document.getElementById( 'bookstore-booklist' );
                books.forEach( function ( book ) {
                    textarea.value += book.title.rendered + ',' + book.link + ',\n'
                });
            }
        );

你的最终代码将类似这样。

const loadBooksByRestButton = document.getElementById( 'bookstore-load-books' );
if ( loadBooksByRestButton ) {
    loadBooksByRestButton.addEventListener( 'click', function () {
        const allBooks = new wp.api.collections.Books();
        allBooks.fetch().done(
            function ( books ) {
                const textarea = document.getElementById( 'bookstore-booklist' );
                books.forEach( function ( book ) {
                    textarea.value += book.title.rendered + ',' + book.link + ',\n'
                });
            }
        );
    });
}

切换回自定义的“书籍列表”管理页面,点击“加载书籍”按钮,即可看到书籍列表出现在文本区域中。

选项 2:使用 @wordpress/fetch-api

自 WordPress 5.0 引入区块编辑器以来,@wordpress/fetch-api 包也已可用,用于发起 REST API 请求。

该包是浏览器 fetch API 的封装,提供了一种更现代、更灵活的方式来向 REST API 发起请求。

要使用 fetch API,你可以更新插件的 JavaScript 依赖项,加入 wp-api-fetch

    wp_enqueue_script(
        'bookstyle-script',
        plugins_url() . '/bookstore/admin_bookstore.js',
        array( 'wp-api', 'wp-api-fetch' ),
        '1.0.0',
        true
    );

你可以移除 wp-api 依赖,或者将 wp-api-fetch 作为额外依赖添加。

接下来,在表单的“操作”区域添加一个按钮,用于触发 fetch 请求。

<button id="bookstore-fetch-books">Fetch Books</button>

和之前一样,在你的 bookstore.js 文件中,为新按钮的点击事件设置一个事件监听器,但这次使用 apiFetch 方法来向 REST API 发起请求。

const fetchBooksByRestButton = document.getElementById( 'bookstore-fetch-books' );
if ( fetchBooksByRestButton ) {
    fetchBooksByRestButton.addEventListener( 'click', function () {
        wp.apiFetch( { path: '/wp/v2/books' } ).then( ( books ) => {
            const textarea = document.getElementById( 'bookstore-booklist' );
            books.map( ( book ) => {
                textarea.value += book.title.rendered + ',' + book.link + ',\n'
            });
        } );
    });
}

注意,你将指向书籍端点的路径作为一个对象传递给 wp.apiFetch 函数。这比使用 Backbone.js 客户端更灵活,因为后者需要使用特定的集合来访问书籍。

你可以链式调用 then 方法来处理响应。这与 Backbone 示例中使用 done 方法类似,因为它返回一个 Promise,等待 REST API 请求完成,然后将结果返回给回调函数。

在回调函数内部,你可以访问 books 对象,并使用 map 方法遍历它,将书名和永久链接追加到文本区域。

你还会注意到,这段代码使用了箭头函数语法作为接收响应的回调,这是一种更现代的 JavaScript 函数编写方式。

刷新管理页面,点击“获取书籍”按钮,即可看到书籍列表出现在文本区域中。

选项 3:使用 @wordpress/core-data

如果你正在开发区块,还有一个 core-data 包可用,用于从 REST API 访问数据。

core-data 旨在简化对核心 WordPress 实体的访问和操作。它注册了自己的存储,并提供了许多选择器,可自动从 WordPress REST API 解析数据,同时提供派发动作创建器来操作数据。

core-data 使用了大量 React 功能,因此最适合在区块上下文中使用。

让我们看看如何在区块中使用 core-data 模块从 REST API 获取书籍。

首先,使用你在“区块开发入门”模块中学到的 create-block 工具,创建一个名为 bookstore-block 的新区块。

cd path/to/local/site/wp-content/plugins
npx @wordpress/create-block bookstore-block

这将搭建新区块的脚手架,并生成一些代码供你编辑。

在区块的 edit.js 文件中,从 @wordpress/data 包导入 useSelect 钩子,以及从 @wordpress/core-data 包导入 store

import { useSelect } from '@wordpress/data';
import { store as bookStore } from '@wordpress/core-data';

然后,你可以使用这些来从 REST API 获取书籍。

    const books = useSelect(
        select =>
            select( bookStore ).getEntityRecords( 'postType', 'book' ),
        []
    );

useSelect 是一个钩子,允许你从已注册的选择器中检索数据。

useSelect 接受一个回调函数作为第一个参数,在该函数中你可以使用 bookStore 存储的 getEntityRecords 选择器从 REST API 中检索书籍。这些书籍随后会被存储在 books 变量中。

最后,你可以更新代码,使其在没有返回书籍时返回一个空组件,或者遍历书籍对象并输出书名和链接。

    if ( ! books ) {
        return (
            <div { ...useBlockProps() }></div>
        )
    }

    return (
        <div { ...useBlockProps() }>
            { books.map( ( book ) => (
                <p>
                    <a href={ book.link }>{book.title.rendered}</a>
                </p>
            ) ) }
        </div>
    );

现在,运行区块构建步骤,激活插件,并将书店区块添加到文章或页面中。

你将看到区块输出从 REST API 获取的书名和链接。

不同选项之间的区别

Backbone 客户端是三个选项中最古老的,但它也是与 REST API 集成最紧密的。如果你需要使用 WP REST API 构建管理仪表板页面,它是一个不错的选择,并且远优于使用传统的 admin-ajax.php 端点。

apiFetch 是一个出色的全能解决方案,因为它既可用于管理仪表板页面,也可用于编辑器中的区块。它也是一种更现代的向 REST API 发起请求的方式,并且比 Backbone 客户端更灵活。

core-data 最适合在区块上下文中使用,因为它使用了在区块编辑器上下文之外不可用的 React 功能。

进一步阅读

有关在 WordPress 中使用 REST API 的各种方式的更多信息,请查看 REST API 手册中的 Backbone JavaScript 客户端部分、区块编辑器手册中的 api-fetch 包,以及区块编辑器手册中的 core-data 包。