Wordpress Plugin Handbook 插件用户手册中文文档
Wordpress Plugin Handbook 插件用户手册
- 插件手册
- 插件开发简介
- 插件基础知识
- 插件安全
- 挂钩
- 隐私
- 管理菜单
- 简码
- 设置
- Metadata元数据
- 自定义帖子类型
- taxonomy分类法
- 用户
- HTTP API
- JavaScript、Ajax 和 jQuery
- Cron计划任务
- 国际化
- WordPress.org 插件目录
- 开发者工具
插件手册
欢迎来到 WordPress 插件开发者手册; 您准备好进入 WordPress 插件的世界了吗? 插件开发者手册是有关 WordPress 插件所有内容的资源。 无论您是 WordPress 插件开发新手,还是经验丰富的插件开发人员,您都应该能够在这里找到许多与插件相关的问题的答案。 如果您是插件开发新手,请先阅读简介,然后继续学习基础知识。 插件安全性中的信息将介绍安全相关内容的最佳实践。 Hooks 使您的插件与 WordPress 交互,以及如何让其他开发人员与您的插件交互。 隐私将帮助您了解如何处理敏感数据。 要了解有关可在插件中使用的 WordPress 内置功能的更多信息,请查看管理菜单、简码、设置、元数据、自定义帖子类型、分类法和用户。 了解如何使用 HTTP API 获取数据。 如果您在插件中使用 JavaScript、jQuery 或 Ajax,您将在该部分找到所需的信息。 要了解基于时间的 WordPress 任务,请查看 Cron 一章。 国际化是让您的插件准备好在您自己的语言环境之外的语言环境中使用的方法。 完成所有操作后,您可以准备插件以包含在插件目录中 最后:一些您可能会觉得有用的开发人员工具。 WordPress 插件开发人员手册由 WordPress 社区为 WordPress 社区创建。 我们一直在寻找更多的贡献者; 如果您有兴趣,请访问文档团队博客以了解有关参与的更多信息。
插件开发简介
欢迎来到插件开发者手册。 无论您正在编写第一个插件还是第五十个插件,我们希望此资源可以帮助您编写最好的插件。 插件开发人员手册涵盖了各种主题 - 从插件标头中应包含的内容到安全最佳实践,再到可用于构建插件的工具。 这也是一项正在进行的工作 - 如果您发现某些内容缺失或不完整,请通知 Slack 文档团队,我们将共同改进。 我们为什么制作插件 如果 WordPress 开发中有一条基本规则,那就是:不要触及 WordPress 核心。 这意味着您不需要编辑核心 WordPress 文件来向您的网站添加功能。 这是因为 WordPress 每次更新都会覆盖核心文件。 您想要添加或修改的任何功能都应该使用插件来完成。 WordPress 插件可以根据您的需要简单或复杂,具体取决于您想要做什么。 最简单的插件是单个 PHP 文件。 Hello Dolly 插件就是此类插件的一个示例。 插件 PHP 文件只需要一个插件头、几个 PHP 函数和一些用于附加函数的钩子。 插件允许您极大地扩展 WordPress 的功能,而无需触及 WordPress 核心本身。
插件基础知识
插件基础知识
入门
最简单的来说,WordPress 插件是一个带有 WordPress 插件标头注释的 PHP 文件。强烈建议您创建一个目录来保存您的插件,以便将所有插件的文件整齐地组织在一个位置。
要开始创建新插件,请按照以下步骤操作。
导航到 WordPress 安装的 wp-content目录。
打开插件目录。
创建一个新目录并以插件命名(例如plugin-name)。
打开新插件的目录。
创建一个新的 PHP 文件(最好以您的插件命名该文件,例如plugin-name.php)。
该过程在 Unix 命令行上如下所示:
wordpress $ cd wp-content
wp-content $ cd plugins
plugins $ mkdir plugin-name
plugins $ cd plugin-name
plugin-name $ vi plugin-name.php
在上面的示例中,vi
是文本编辑器的名称。使用您觉得舒服的编辑器。
现在您正在编辑新插件的 PHP 文件,您需要添加插件标头注释。这是一个特殊格式的 PHP 块注释,其中包含有关插件的元数据,例如名称、作者、版本、许可证等。插件标头注释必须符合标头要求,并且至少包含插件 的名称插入。
插件文件夹中只有一个文件应具有标题注释 - 如果插件有多个 PHP 文件,则只有其中一个文件应具有标题注释。
保存文件后,您应该能够在 WordPress 站点中看到列出的插件。登录到您的 WordPress 站点,然后单击WordPress 管理员左侧导航窗格中的插件。此页面显示您的 WordPress 网站拥有的所有插件的列表。您的新插件现在应该在该列表中!
挂钩:操作和过滤器
WordPress 挂钩允许您在特定点接入 WordPress 来更改 WordPress 的行为方式,而无需编辑任何核心文件。
WordPress 中有两种类型的挂钩:操作和过滤器。操作允许您添加或更改 WordPress 功能,而过滤器允许您在加载并向网站用户显示内容时更改内容。
Hooks 不仅仅适用于插件开发者;它也适用于插件开发者。WordPress 核心本身广泛使用 hooks 来提供默认功能。其他挂钩是未使用的占位符,当您需要更改 WordPress 的工作方式时,可以轻松使用它们。这就是 WordPress 如此灵活的原因。
基本挂钩
创建插件时需要的 3 个基本钩子是register_activation_hook()、register_deactivation_hook()和register_uninstall_hook()。
当您激活插件时,register_activation_hook就会运行。您可以使用它来提供设置插件的功能 - 例如,在表中创建一些默认设置。options
当您停用插件时,register_deactivation_hook就会运行。您可以使用它来提供一个函数来清除插件存储的任何临时数据。
这些register_uninstall_hook方法用于在使用 WordPress 管理员删除插件后进行清理。您可以使用它来删除插件创建的所有数据,例如添加到表中的任何选项options
。
添加挂钩
您可以使用do_action()添加您自己的自定义挂钩,这将使开发人员能够通过挂钩传递函数来扩展您的插件。
拆除挂钩
您还可以使用调用remove_action() 来删除之前定义的函数。例如,如果您的插件是另一个插件的附加组件,则可以将remove_action() 与上一个插件通过add_action()添加的相同函数回调一起使用。在这些情况下,操作的优先级很重要,因为remove_action() 需要在初始add_action()之后运行。
从钩子中删除操作以及更改优先级时应该小心,因为很难看出这些更改将如何影响与同一钩子的其他交互。我们强烈建议经常进行测试。
您可以在本手册的“钩子”部分了解有关创建钩子以及与它们交互的更多信息。
WordPress API
您是否知道 WordPress 提供了许多应用程序编程接口 (API)?这些 API 可以极大地简化您需要在插件中编写的代码。您不想重新发明轮子,尤其是当这么多人为您完成了大量工作和测试时。
最常见的是Options API,它可以轻松地将数据存储在插件的数据库中。如果您正在考虑在插件中使用cURL ,那么您可能会对HTTP API感兴趣。
由于我们谈论的是插件,因此您需要研究Plugin API。它具有多种功能可以帮助您开发插件。
WordPress 如何加载插件
当 WordPress 在 WordPress 管理员的插件页面上加载已安装插件的列表时,它会搜索该plugins
文件夹(及其子文件夹)以查找带有 WordPress 插件标头注释的 PHP 文件。如果您的整个插件仅包含一个 PHP 文件(例如Hello Dolly ),则该文件可以直接位于plugins
文件夹的根目录内。但更常见的是,插件文件将驻留在自己的文件夹中,以插件命名。
分享你的插件
有时您创建的插件仅适用于您的网站。但许多人喜欢与 WordPress 社区的其他成员分享他们的插件。在共享您的插件之前,您需要做的一件事是选择许可证。这可以让您的插件的用户知道他们如何被允许使用您的代码。为了保持与 WordPress 核心的兼容性,建议您选择适用于 GNU 通用公共许可证 (GPLv2+) 的许可证。
标题要求
如入门中所述,主 PHP 文件应包含标头注释,告诉 WordPress 该文件是一个插件并提供有关该插件的信息。
最小字段
标头注释至少必须包含插件名称:
/*
* Plugin Name: YOUR PLUGIN NAME
*/
标头字段
可用的标头字段:
- 插件名称:(必填)您的插件的名称,该名称将显示在 WordPress 管理中心的插件列表中。
- 插件URI:插件的主页,应该是唯一的URL,最好在您自己的网站上。这对于您的插件来说必须是唯一的。您不能在此处使用 WordPress.org URL。
- 描述:插件的简短描述,如 WordPress 管理中的插件部分所示。请将此描述控制在 140 个字符以内。
- 版本:插件的当前版本号,例如 1.0 或 1.0.3。
- 至少需要:插件可以运行的最低 WordPress 版本。
- 需要 PHP:所需的最低 PHP 版本。
- 作者:插件作者的姓名。可以使用逗号列出多个作者。
- 作者 URI:作者的网站或其他网站(例如 WordPress.org)上的个人资料。
- 许可证:插件许可证的简称(slug)(例如GPLv2)。有关许可的更多信息可以在WordPress.org 指南中找到。
- 许可证 URI:许可证全文的链接(例如https://www.gnu.org/licenses/gpl-2.0.html)。
- 文本域:插件的gettext文本域。更多信息可以在如何国际化插件页面的文本域部分找到。
- 域路径:域路径让 WordPress 知道在哪里可以找到翻译。更多信息可以在如何国际化插件页面的域路径部分找到。
- 网络:插件是否只能在网络范围内激活。只能设置为true,不需要时应省略。
- 更新 URI:允许第三方插件避免意外被 WordPress.org 插件目录中同名插件的更新覆盖。有关更多信息,请阅读相关的开发说明。
带有标题注释的有效 PHP 文件可能如下所示:
/*
* Plugin Name: My Basics Plugin
* Plugin URI: https://example.com/plugins/the-basics/
* Description: Handle the basics with this plugin.
* Version: 1.10.3
* Requires at least: 5.2
* Requires PHP: 7.2
* Author: John Smith
* Author URI: https://author.example.com/
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Update URI: https://example.com/my-plugin/
* Text Domain: my-basics-plugin
* Domain Path: /languages
*/
这是另一个允许文件级 PHPDoc DocBlock 以及 WordPress 插件文件头的示例:
/**
* Plugin Name
*
* @package PluginPackage
* @author Your Name
* @copyright 2019 Your Name or Company Name
* @license GPL-2.0-or-later
*
* @wordpress-plugin
* Plugin Name: Plugin Name
* Plugin URI: https://example.com/plugin-name
* Description: Description of the plugin.
* Version: 1.0.0
* Requires at least: 5.2
* Requires PHP: 7.2
* Author: Your Name
* Author URI: https://example.com
* Text Domain: plugin-slug
* License: GPL v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Update URI: https://example.com/my-plugin/
*/
笔记
为项目分配版本号时,请记住 WordPress 使用 PHP version_compare() 函数来比较插件版本号。因此,在发布新版本的插件之前,您应该确保该 PHP 函数认为新版本比旧版本“更好”。例如,1.02 实际上大于 1.1。
包括软件许可证
大多数 WordPress 插件都是在GPL下发布的,这与WordPress 本身使用的许可证相同 。但是,还有其他兼容选项可用。最好清楚地表明您的插件使用的许可证。
在“标头要求”部分中,我们简要提到了如何在插件标头注释中指示插件的许可证。另一种常见且鼓励的做法是在主插件文件顶部附近放置许可证块注释(与具有插件标头注释的文件相同)。
该许可证块注释通常如下所示:
/*
{Plugin Name} is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
any later version.
{Plugin Name} is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with {Plugin Name}. If not, see {URI to Plugin License}.
*/
激活/停用挂钩
激活和停用挂钩提供了在激活或停用插件时执行操作的方法。
- 激活时,插件可以运行例程来添加重写规则、添加自定义数据库表或设置默认选项值。
- 在停用时,插件可以运行例程来删除临时数据,例如缓存和临时文件和目录。
停用挂钩有时会与卸载挂钩混淆。卸载挂钩最适合永久删除所有数据,例如删除插件选项和自定义表等。
激活
要设置激活挂钩,请使用register_activation_hook() 函数:
register_activation_hook(
__FILE__,
'pluginprefix_function_to_run'
);
停用
要设置停用挂钩,请使用register_deactivation_hook() 函数:
register_deactivation_hook(
__FILE__,
'pluginprefix_function_to_run'
);
每个函数中的第一个参数指的是您的主插件文件,即您在其中放置插件标头注释的文件。通常这两个函数将从主插件文件中触发;但是,如果函数放置在任何其他文件中,则必须更新第一个参数以正确指向主插件文件。
例子
激活挂钩最常见的用途之一是当插件注册自定义帖子类型时刷新 WordPress 永久链接。这消除了令人讨厌的 404 错误。
让我们看一个如何执行此操作的示例:
/**
* Register the "book" custom post type
*/
function pluginprefix_setup_post_type() {
register_post_type( 'book', ['public' => true ] );
}
add_action( 'init', 'pluginprefix_setup_post_type' );
/**
* Activate the plugin.
*/
function pluginprefix_activate() {
// Trigger our function that registers the custom post type plugin.
pluginprefix_setup_post_type();
// Clear the permalinks after the post type has been registered.
flush_rewrite_rules();
}
register_activation_hook( __FILE__, 'pluginprefix_activate' );
如果您不熟悉注册自定义帖子类型,请不要担心 - 这将在稍后介绍。使用这个例子只是因为它很常见。
使用上面的示例,以下是如何反转此过程并停用插件:
/**
* Deactivation hook.
*/
function pluginprefix_deactivate() {
// Unregister the post type, so the rules are no longer in memory.
unregister_post_type( 'book' );
// Clear the permalinks to remove our post type's rules from the database.
flush_rewrite_rules();
}
register_deactivation_hook( __FILE__, 'pluginprefix_deactivate' );
有关激活和停用挂钩的更多信息,以下是一些优秀的资源:
- WordPress 函数参考中的register_activation_hook() 。
- WordPress 函数参考中的register_deactivation_hook() 。
卸载方法
从站点卸载时,您的插件可能需要进行一些清理。
如果用户停用插件,然后单击 WordPress 管理员中的删除链接,则该插件将被视为已卸载。
卸载插件后,您需要清除特定于插件的所有插件选项和/或设置,和/或其他数据库实体(例如表)。
经验不足的开发人员有时会错误地使用停用挂钩来实现此目的。
此表说明了停用和卸载之间的差异。
设想 | 停用挂钩 | 卸载挂钩 |
---|---|---|
刷新缓存/临时 | 是的 | 不 |
刷新固定链接 | 是的 | 不 |
从 {$wpdb ->prefix}_options中删除选项 | 不 | 是的 |
从wpdb中删除表 | 不 | 是的 |
方法一:register_uninstall_hook
要设置卸载挂钩,请使用register_uninstall_hook() 函数:
register_uninstall_hook(
__FILE__,
'pluginprefix_function_to_run'
);
方法二:uninstall.php
要使用此方法,您需要uninstall.php在插件的根文件夹中创建一个文件。当用户删除插件时,这个魔术文件会自动运行。例如:/plugin-name/uninstall.php
警报:WP_UNINSTALL_PLUGIN在做任何事情之前 一定要检查常量uninstall.php。这可以防止直接访问。
该常量将由 WordPress 在uninstall.php调用期间定义。
当执行卸载时,该常量未定义register_uninstall_hook() 。
以下是删除选项条目并删除数据库表的示例:
// if uninstall.php is not called by WordPress, die
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
die;
}
$option_name = 'wporg_option';
delete_option( $option_name );
// for site options in Multisite
delete_site_option( $option_name );
// drop a custom database table
global $wpdb;
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}mytable" );
笔记:在多站点中,循环遍历所有博客以删除选项可能会占用大量资源。
最佳实践
以下是一些帮助组织代码的最佳实践,使其能够与 WordPress 核心和其他 WordPress 插件一起良好运行。
避免命名冲突
当您的插件对变量、函数或类使用与另一个插件相同的名称时,就会发生命名冲突。
幸运的是,您可以使用以下方法来避免命名冲突。
程序编码方法
默认情况下,所有变量、函数和类都在全局命名空间中定义,这意味着您的插件可以覆盖另一个插件设置的变量、函数和类,反之亦然。在函数或类内部定义的变量不受此影响。
给所有东西加上前缀
所有全局可访问的代码都应以唯一标识符作为前缀。前缀可以防止其他插件覆盖您的变量并意外调用您的函数和类。它还会阻止您做同样的事情。
为了防止与其他插件冲突,您的前缀长度应至少为 5 个字母。您应该避免使用常见的英语单词,而应选择您的插件特有的单词。
应加前缀的代码包括:
- 函数(除非命名空间)
- 类、接口和特征(除非命名空间)
- 命名空间
- 全局变量
- 选项和瞬态
检查现有实施
PHP 提供了许多函数来验证变量、函数、类和常量是否存在。如果实体存在,所有这些都将返回 true。
- 变量: isset() (包括数组、对象等)
- 函数: function_exists()
- 类: class_exists()
- 常量: defined()
例子:
// Create a function called "wporg_init" if it doesn't already exist
if ( ! function_exists( 'wporg_init' ) ) {
function wporg_init() {
register_setting( 'wporg_settings', 'wporg_option_foo' );
}
}
// Create a function called "wporg_get_foo" if it doesn't already exist
if ( ! function_exists( 'wporg_get_foo' ) ) {
function wporg_get_foo() {
return get_option( 'wporg_option_foo' );
}
}
面向对象的编程方法
解决命名冲突问题的一种更简单的方法是使用插件代码的类。
您仍然需要检查您想要的类的名称是否已被占用,但其余的将由 PHP 处理。
例子
if ( ! class_exists( 'WPOrg_Plugin' ) ) {
class WPOrg_Plugin {
public static function init() {
register_setting( 'wporg_settings', 'wporg_option_foo' );
}
public static function get_foo() {
return get_option( 'wporg_option_foo' );
}
}
WPOrg_Plugin::init();
WPOrg_Plugin::get_foo();
}
文件组织
插件目录的根级别应包含您的plugin-name.php
文件和(可选)您的uninstall.php文件。所有其他文件应尽可能组织到子文件夹中。
文件夹结构
清晰的文件夹结构可以帮助您和其他开发插件的人将相似的文件保存在一起。
以下是供参考的示例文件夹结构:
/plugin-name
plugin-name.php
uninstall.php
/languages
/includes
/admin
/js
/css
/images
/public
/js
/css
/images
插件架构
您为插件选择的架构或代码组织可能取决于插件的大小。
对于与 WordPress 核心、主题或其他插件交互有限的小型单一用途插件,设计复杂的类几乎没有什么好处;除非你知道这个插件以后会大大扩展。
对于包含大量代码的大型插件,请从类开始。单独的样式和脚本文件,甚至与构建相关的文件。这将有助于代码组织和插件的长期维护。
条件加载
将管理代码与公共代码分开很有帮助。使用条件式is_admin()。您仍必须执行功能检查,因为这并不表明用户已通过身份验证或具有管理员级别访问权限。请参阅检查用户能力。
例如:
if ( is_admin() ) {
// we are in admin mode
require_once __DIR__ . '/admin/plugin-name-admin.php';
}
架构模式
虽然有许多可能的架构模式,但它们大致可以分为三种变体:
架构模式解释
上述更复杂的代码组织的具体实现已经写成教程和幻灯片:
样板起点
您可能希望从样板文件开始,而不是从头开始编写您编写的每个新插件。使用样板的优点之一是在您自己的插件之间保持一致性。如果您使用其他人已经熟悉的样板文件,样板文件还可以让其他人更轻松地为您的代码做出贡献。
这些也可以作为不同但可比较的架构的进一步示例。
- WordPress 插件样板:WordPress 插件开发的基础,旨在为构建插件提供清晰一致的指南。
- WordPress Plugin Bootstrap:使用 Grunt、Compass、GIT 和 SVN 开发 WordPress 插件的基本引导程序。
- WP Skeleton Plugin:专注于单元测试和使用 Composer 进行开发的骨架插件。
- WP CLI Scaffold:WP CLI 的 Scaffold 命令创建一个骨架插件,其中包含 CI 配置文件等选项
当然,您可以利用这些和其他方面的不同方面来创建您自己的自定义样板。
单文件包含函数
<?php
/**
* Move Floating Social Bar in Genesis
*
* @package Move_Floating_Social_Bar_In_Genesis
* @author Gary Jones <gary@garyjones.co.uk>
* @license GPL-2.0+
* @link https://github.com/GaryJones/move-floating-social-bar-in-genesis
* @copyright 2013 Gary Jones, Gamajo Tech
*
* @wordpress-plugin
* Plugin Name: Move Floating Social Bar in Genesis
* Plugin URI: https://github.com/GaryJones/move-floating-social-bar-in-genesis
* Description: Moves the Floating Social Bar plugin output from just inside the entry content to just before it.
* Version: 1.0.0
* Author: Gary Jones
* Author URI: https://github.com/GaryJones/move-floating-social-bar-in-genesis
* Text Domain: move-floating-social-bar-in-genesis
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Domain Path: /languages
* GitHub Plugin URI: https://github.com/GaryJones/move-floating-social-bar-in-genesis
* GitHub Branch: master
*/
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
}
add_action( 'pre_get_posts', 'mfsbig_remove_floating_social_bar', 15 );
/**
* Remove Floating Social Bar from outputting at the top of the content.
*
* FSB adds these filters at pre_get_posts, priorty 10, so we'll remove them
* just after that.
*
* @since 1.0.0
*/
function mfsbig_remove_floating_social_bar() {
if ( class_exists( 'floating_social_bar' ) ) {
remove_filter( 'the_excerpt', array( floating_social_bar::get_instance(), 'fsb' ), apply_filters( 'fsb_social_bar_priority', 10 ) );
remove_filter( 'the_content', array( floating_social_bar::get_instance(), 'fsb' ), apply_filters( 'fsb_social_bar_priority', 10 ) );
}
}
add_action( 'genesis_before_post_content', 'mfsbig_add_floating_social_bar' ); // XHTML themes
add_action( 'genesis_before_entry_content', 'mfsbig_add_floating_social_bar' ); // HTML5 themes
/**
* Echo the Floating Social Bar in the right place, just before the entry
* content (after the header and entry meta) in Genesis child themes.
*
* As fsb() is really a function for filtering, it requires a single argument
* (the content or excerpt), so we fool it by passing in an empty string instead.
*
* @since 1.0.0
*/
function mfsbig_add_floating_social_bar() {
if ( class_exists( 'floating_social_bar' ) ) {
echo floating_social_bar::get_instance()->fsb('');
}
}
单个插件文件,包含类、实例化对象和可选函数
<?php
/*
Plugin Name: WP Comment Notes
Plugin URI: http://andrewnorcross.com/plugins/
Description: Add custom notes before or after the comment form.
Version: 1.0.0
Author: Andrew Norcross
Author URI: http://andrewnorcross.com
Copyright 2013 Andrew Norcross
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
if( !defined( 'WPCMN_VER' ) )
define( 'WPCMN_VER', '1.0.0' );
// Start up the engine
class WP_Comment_Notes
{
/**
* Static property to hold our singleton instance
*
*/
static $instance = false;
/**
* This is our constructor
*
* @return void
*/
private function __construct() {
// back end
add_action ( 'plugins_loaded', array( $this, 'textdomain' ) );
add_action ( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
add_action ( 'do_meta_boxes', array( $this, 'create_metaboxes' ), 10, 2 );
add_action ( 'save_post', array( $this, 'save_custom_meta' ), 1 );
// front end
add_action ( 'wp_enqueue_scripts', array( $this, 'front_scripts' ), 10 );
add_filter ( 'comment_form_defaults', array( $this, 'custom_notes_filter' ) );
}
/**
* If an instance exists, this returns it. If not, it creates one and
* retuns it.
*
* @return WP_Comment_Notes
*/
public static function getInstance() {
if ( !self::$instance )
self::$instance = new self;
return self::$instance;
}
/**
* load textdomain
*
* @return void
*/
public function textdomain() {
load_plugin_textdomain( 'wpcmn', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
}
/**
* Admin styles
*
* @return void
*/
public function admin_scripts() {
$types = $this->get_post_types();
$screen = get_current_screen();
if ( in_array( $screen->post_type , $types ) ) :
wp_enqueue_style( 'wpcmn-admin', plugins_url('lib/css/admin.css', __FILE__), array(), WPCMN_VER, 'all' );
endif;
}
/**
* call metabox
*
* @return void
*/
public function create_metaboxes( $page, $context ) {
$types = $this->get_post_types();
if ( in_array( $page, $types ) )
add_meta_box( 'wp-comment-notes', __( 'Comment Notes', 'wpcmn' ), array( $this, 'wpcmn_notes_meta' ), $page, 'advanced', 'high' );
}
/**
* display meta fields for notes meta
*
* @return void
*/
public function wpcmn_notes_meta( $post ) {
// Use nonce for verification
wp_nonce_field( 'wpcmn_meta_nonce', 'wpcmn_meta_nonce' );
$post_id = $post->ID;
// get postmeta, and our initial settings
$notes = get_post_meta( $post_id, '_wpcmn_notes', true );
$before_text = isset( $notes['before-text'] ) ? $notes['before-text'] : '';
$before_type = isset( $notes['before-type'] ) ? $notes['before-type'] : 'wpcmn-notes-standard';
$after_text = isset( $notes['after-text'] ) ? $notes['after-text'] : '';
$after_type = isset( $notes['after-type'] ) ? $notes['after-type'] : 'wpcmn-notes-standard';
echo '<script type="text/javascript">jQuery(document).ready(function($){$("#comment_status").click(function(){$(".wpcmn-notes-table tr").toggle();})});</script>';
$disabled_display = comments_open( $post_id ) ? ' style="display:none;"' : '';
$enabled_display = ! comments_open( $post_id ) ? ' style="display:none;"' : '';
echo '<table class="form-table wpcmn-notes-table">';
echo '<tr class="wpcmn-notes-disabled"' . $disabled_display . '>';
echo '<th>' . __( 'Enable comments in order to use Comment Notes', 'wpcmn' ) . '</th>';
echo '</tr>';
echo '<tr class="wpcmn-notes-title"' . $enabled_display . '>';
echo '<td colspan="2"><h5>'.__( 'Before Notes Area', 'wpcmn' ) . '</h5></td>';
echo '</tr>';
echo '<tr class="wpcmn-notes-data wpcmn-notes-before-text"' . $enabled_display . '>';
echo '<th>'.__( 'Message Text', 'wpcmn' ) . '</th>';
echo '<td>';
echo '<textarea class="widefat" name="wpcmn-notes[before-text]" id="wpcmn-before">'.esc_attr( $before_text ) . '</textarea>';
echo '<p class="description">'.__( 'Note: This will not appear to users who are logged in.', 'wpcmn' ) . '</p>';
echo '</td>';
echo '</tr>';
echo '<tr class="wpcmn-notes-data wpcmn-notes-before-type"' . $enabled_display . '>';
echo '<th>'.__( 'Message Type', 'wpcmn' ) . '</th>';
echo '<td>';
echo '<select id="wpcmn-before-type" name="wpcmn-notes[before-type]">';
echo '<option value="wpcmn-notes-standard"' . selected( $before_type, 'wpcmn-notes-standard', false ) . '>' . __( 'Standard', 'wpcmn' ) . '</option>';
echo '<option value="wpcmn-notes-warning"' . selected( $before_type, 'wpcmn-notes-warning', false ) . '>' . __( 'Warning', 'wpcmn' ) . '</option>';
echo '<option value="wpcmn-notes-alert"' . selected( $before_type, 'wpcmn-notes-alert', false ) . '>' . __( 'Alert', 'wpcmn' ) . '</option>';
do_action( 'wpcmn_before_types', $before_type );
echo '</select>';
echo '</td>';
echo '</tr>';
echo '<tr class="wpcmn-notes-title"' . $enabled_display . '>';
echo '<td colspan="2"><h5>'.__( 'After Notes Area', 'wpcmn' ) . '</h5></td>';
echo '</tr>';
echo '<tr class="wpcmn-notes-data wpcmn-notes-after-text"' . $enabled_display . '>';
echo '<th>'.__( 'Message Text', 'wpcmn' ) . '</th>';
echo '<td>';
echo '<textarea class="widefat" name="wpcmn-notes[after-text]" id="wpcmn-after">'.esc_attr( $after_text ) . '</textarea>';
echo '</td>';
echo '</tr>';
echo '<tr class="wpcmn-notes-data wpcmn-notes-after-type"' . $enabled_display . '>';
echo '<th>'.__( 'Message Type', 'wpcmn' ) . '</th>';
echo '<td>';
echo '<select id="wpcmn-after-type" name="wpcmn-notes[after-type]">';
echo '<option value="wpcmn-notes-standard"' . selected( $after_type, 'wpcmn-notes-standard', false ) . '>' . __( 'Standard', 'wpcmn' ) . '</option>';
echo '<option value="wpcmn-notes-warning"' . selected( $after_type, 'wpcmn-notes-warning', false ) . '>' . __( 'Warning', 'wpcmn' ) . '</option>';
echo '<option value="wpcmn-notes-alert"' . selected( $after_type, 'wpcmn-notes-alert', false ) . '>' . __( 'Alert', 'wpcmn' ) . '</option>';
do_action( 'wpcmn_after_types', $after_type );
echo '</select>';
echo '</td>';
echo '</tr>';
echo '</table>';
}
/**
* save post metadata
*
* @return void
*/
public function save_custom_meta( $post_id ) {
// make sure we aren't using autosave
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
return $post_id;
// do our nonce check. ALWAYS A NONCE CHECK
if ( ! isset( $_POST['wpcmn_meta_nonce'] ) || ! wp_verify_nonce( $_POST['wpcmn_meta_nonce'], 'wpcmn_meta_nonce' ) )
return $post_id;
$types = $this->get_post_types();
if ( !in_array ( $_POST['post_type'], $types ) )
return $post_id;
// and make sure the user has the ability to do shit
if ( 'page' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return $post_id;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return $post_id;
}
}
// all clear. get data via $_POST and store it
$notes = ! empty( $_POST['wpcmn-notes'] ) ? $_POST['wpcmn-notes'] : false;
// update side meta data
if ( $notes ) {
$allowed_html = array(
'a' => array(
'href' => array(),
'title' => array(),
'class' => array(),
'id' => array()
),
'br' => array(),
'em' => array(),
'strong' => array(),
'span' => array(
'class' => array(),
'id' => array()
)
);
update_post_meta( $post_id, '_wpcmn_notes', wp_kses( $notes, $allowed_html ) );
do_action( 'wpcmn_notes_save', $post_id, $notes );
} else {
delete_post_meta( $post_id, '_wpcmn_notes' );
}
}
/**
* call front-end CSS
*
* @return void
*/
public function front_scripts() {
// check for killswitch first
$killswitch = apply_filters( 'wpcmn_killswitch', false );
if ( $killswitch )
return false;
$types = $this->get_post_types();
if ( is_singular( $types ) )
wp_enqueue_style( 'wpcmn-notes', plugins_url( 'lib/css/wpcmn-notes.css', __FILE__ ), array(), WPCMN_VER, 'all' );
}
/**
* The actual filter for adding the notes.
*
* @return array
*/
public function custom_notes_filter( $fields ) {
global $post;
// get the possible meta fields
$notes = get_post_meta( $post->ID, '_wpcmn_notes', true );
if ( empty( $notes ) )
return $fields;
if ( isset( $notes['before-text'] ) ) :
// grab the variables
$text = $notes['before-text'];
$class = isset( $notes['before-type'] ) ? $notes['before-type'] : 'wpcmn-notes-standard';
// build the string
$before = '<p class="wpcmn-notes wpcmn-notes-before' . esc_attr( $class ) . '">' . $text . '</p>';
// output
$fields['comment_notes_before'] = $before;
endif;
if ( isset( $notes['after-text'] ) ) :
// grab the variables
$text = $notes['after-text'];
$class = isset( $notes['after-type'] ) ? $notes['after-type'] : 'wpcmn-notes-standard';
// build the string
$after = '<p class="wpcmn-notes wpcmn-notes-after' . esc_attr( $class ) . '">' . $text . '</p>';
// output
$fields['comment_notes_after'] = $after;
endif;
return $fields;
}
/**
* Return the list of post types that support Comment Notes
*
* @return array
*/
public function get_post_types() {
$types = get_post_types( array( 'public' => true, 'show_ui' => true ) );
foreach( $types as $type ) {
if( ! post_type_supports( $type, 'comments' ) ) {
unset( $types[ $type ] );
}
}
return apply_filters( 'wpcmn_type_support', $types );
}
/// end class
}
// Instantiate our class
$WP_Comment_Notes = WP_Comment_Notes::getInstance();
主插件文件,然后是一个或多个类文件
下载网址:https://github.com/DevinVinson/WordPress-Plugin-Boilerplate
可以通过下列网址自动生成结构:https://wppb.me/
WordPress 插件样板
用于构建高质量 WordPress 插件的标准化、有组织、面向对象的基础。
内容
WordPress 插件样板包含以下文件:
.gitignore
。用于从存储库中排除某些文件。CHANGELOG.md
。核心项目的更改列表。README.md
。您当前正在阅读的文件。- 包含源代码的目录
plugin-name
- 完全可执行的 WordPress 插件。
特征
- 该样板基于插件 API、编码标准和文档标准。
- 所有类、函数和变量都已记录下来,以便您知道需要更改哪些内容。
- Boilerplate 使用严格的文件组织方案,该方案与 WordPress 插件存储库结构相对应,并且可以轻松组织组成插件的文件。
- 该项目包括一个
.pot
文件作为国际化的起点。
安装
Boilerplate 可以直接“按原样”安装到您的插件文件夹中。您将需要重命名它及其内部的类以满足您的需要。例如,如果您的插件名为“example-me”,则:
- 将文件重命名
plugin-name
为example-me
- 更改
plugin_name
为example_me
- 更改
plugin-name
为example-me
- 更改
Plugin_Name
为Example_Me
- 更改
PLUGIN_NAME_
为EXAMPLE_ME_
此时激活插件是安全的。由于样板没有真正的功能,因此在编写代码之前不会添加菜单项、元框或自定义帖子类型。
WordPress.org 准备
此版本样板的最初发布包括在 WordPress.org 上使用插件所需的文件夹结构。该文件夹结构已移至其自己的存储库:https ://github.com/DevinVinson/Plugin-Directory-Boilerplate
推荐工具
国际化工具
WordPress 插件样板使用变量来存储在整个样板中国际化字符串时使用的文本域。要利用此方法,建议使用一些工具来提供正确的可翻译文件:
上述任何工具都应该为您提供适当的工具来国际化插件。
许可证
WordPress 插件样板根据 GPL v2 或更高版本获得许可。
该程序是免费软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证第 2 版的条款重新分发和/或修改它。
分发此程序的目的是希望它有用,但不提供任何保证;甚至没有适销性或特定用途适用性的默示保证。有关更多详细信息,请参阅 GNU 通用公共许可证。
您应该随该程序一起收到 GNU 通用公共许可证的副本;如果没有,请写信给 Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
许可证的副本包含在插件目录的根目录中。该文件名为LICENSE
.
重要笔记
许可
WordPress 插件样板已根据 GPL v2 或更高版本获得许可;但是,如果您选择使用与 v2 不兼容的第三方代码,那么您可能需要改用与 GPL v3 兼容的代码。
作为参考,这里的讨论涵盖了Bootstrap使用的 Apache 2.0 许可证。
包括
请注意,如果您包含自己的类或第三方库,则所述文件可能位于三个位置:
plugin-name/includes
是管理区域和网站面向公众的部分之间共享功能的地方plugin-name/admin
适用于所有特定于管理员的功能plugin-name/public
适用于所有面向公众的功能
请注意,以前版本的样板不包含该类Plugin_Name_Loader
,但该类用于向 WordPress 注册所有过滤器和操作。
提供的示例代码展示了如何向 Loader 类注册您的钩子。
其他功能怎么样?
WordPress 插件样板的早期版本包含对许多不同项目的支持,例如GitHub Updater。
这些工具不是这个样板的核心部分,因为我认为它们是对样板的补充、分叉或其他贡献。
使用 Grunt、Composer 等工具也是如此。这些都是很棒的工具,但并不是每个人都使用它们。为了保持核心Boilerplate尽可能轻,这些功能已被删除,并将在其他版本中引入,并将在项目主页上列出和维护。
确定插件和内容目录
在编写 WordPress 插件时,您经常需要在整个 WordPress 安装过程中以及插件或主题中引用各种文件和文件夹。
WordPress 提供了多种函数来轻松确定给定文件或目录所在的位置。始终在插件中使用这些函数,而不是对 wp-content 目录进行硬编码引用或使用 WordPress 内部常量。
WordPress 允许用户将 wp-content 目录放置在任何他们想要的地方,并根据需要重命名它。永远不要假设插件将在 wp-content/plugins 中,上传将在 wp-content/uploads 中,或者主题将在 wp-content/themes 中。
PHP 的__FILE__
magic-constant 自动解析符号链接,因此如果 wp-content
或 wp-content/plugins
什至单个插件目录被符号链接,则硬编码路径将无法正常工作。
常见用法
如果您的插件包含 JavaScript 文件、CSS 文件或其他外部文件,那么您可能需要这些文件的 URL,以便将它们加载到页面中。为此,您应该使用 plugins_url() 函数如下:
plugins_url( 'myscript.js', __FILE__ );
这将返回 myscript.js 的完整 URL,例如 example.com/wp-content/plugins/myplugin/myscript.js
。
要将插件的 JavaScript 或 CSS 加载到页面中,您应该分别使用 wp_enqueue_script()
或 wp_enqueue_style()
,将结果 plugins_url()
作为文件 URL 传递。
可用功能
WordPress 包含许多其他函数,用于确定插件、主题和 WordPress 本身内的文件或目录的路径和 URL。有关其使用的完整信息,请参阅每个功能的单独 DevHub 页面。
插件
plugins_url()
plugin_dir_url()
plugin_dir_path()
plugin_basename()
主题
get_template_directory_uri()
get_stylesheet_directory_uri()
get_stylesheet_uri()
get_theme_root_uri()
get_theme_root()
get_theme_roots()
get_stylesheet_directory()
get_template_directory()
网站首页
home_url()
get_home_path()
WordPress
admin_url()
site_url()
content_url()
includes_url()
wp_upload_dir()
常数
WordPress 在确定内容和插件目录的路径时使用以下常量。这些不应由插件或主题直接使用,但为了完整性而在此处列出。
WP_CONTENT_DIR // no trailing slash, full paths only
WP_CONTENT_URL // full url
WP_PLUGIN_DIR // full path, no trailing slash
WP_PLUGIN_URL // full url, no trailing slash
// Available per default in MS, not set in single site install
// Can be used in single site installs (as usual: at your own risk)
UPLOADS // (If set, uploads folder, relative to ABSPATH) (for e.g.: /wp-content/uploads)
有关的
WordPress 目录:
插件安全
清理数据
不受信任的数据来自许多来源(用户、第三方网站,甚至您自己的数据库!),所有这些数据在使用之前都需要进行检查。
请记住:即使管理员也是用户,用户也会有意或无意地输入错误的数据。你的工作就是保护他们免受伤害。
清理输入是保护/清理/过滤输入数据的过程。验证优于清理,因为验证更具体。但当“更具体”不可能时,消毒就是下一个最好的选择。
例子
假设我们有一个名为的输入字段title
:
<input id="title" type="text" name="title">
我们不能在这里使用验证,因为文本字段太笼统:它可以是任何东西。因此,我们使用以下函数清理输入数据sanitize_text_field()
:
$title = sanitize_text_field( $_POST['title'] );
update_post_meta( $post->ID, 'title', $title );
在后台,sanitize_text_field()
执行以下操作:
- 检查无效的 UTF-8
- 将单个小于号 (<) 转换为实体
- 删除所有标签
- 删除换行符、制表符和多余的空白
- 剥离八位字节
消除功能
有许多功能可以帮助您清理数据。
sanitize_email()
sanitize_file_name()
sanitize_hex_color()
sanitize_hex_color_no_hash()
sanitize_html_class()
sanitize_key()
sanitize_meta()
sanitize_mime_type()
sanitize_option()
sanitize_sql_orderby()
sanitize_term()
sanitize_term_field()
sanitize_text_field()
sanitize_textarea_field()
sanitize_title()
sanitize_title_for_query()
sanitize_title_with_dashes()
sanitize_user()
sanitize_url()
wp_kses()
wp_kses_post()
验证数据
不受信任的数据来自许多来源(用户、第三方网站,甚至您自己的数据库!),所有这些数据在使用之前都需要进行检查。
请记住:即使管理员也是用户,用户也会有意或无意地输入错误的数据。你的工作就是保护他们免受伤害。
验证输入是根据预定义模式(或多个模式)测试数据的过程,并得出明确的结果:有效或无效。与清理相比,验证是一种更具体的方法,但两者都有其作用。
简单的验证示例:
- 检查必填字段是否未留空
- 检查输入的电话号码是否仅包含数字和标点符号
- 检查请求的字符串是否是五个有效选项之一
- 检查数量字段是否大于 0
应尽早进行数据验证。这意味着在执行任何操作之前验证数据。
验证理念
关于如何进行验证有几种不同的理念。每个都适合不同的场景。
安全名单
仅接受来自已知和可信值的有限列表的数据。
将不受信任的数据与安全列表进行比较时,确保使用严格的类型检查非常重要。否则,攻击者可能会以某种方式制作输入,使其通过安全列表,但仍然具有恶意效果。
比较运算符
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
if ( 1 === $untrusted_input ) { // == would have evaluated to true, but === evaluates to false
echo '<p>Valid data';
} else {
wp_die( 'Invalid data' );
}
in_array()
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
$safe_values = array( 1, 5, 7 );
if ( in_array( $untrusted_input, $safe_values, true ) ) { // `true` enables strict type checking
echo '<p>Valid data';
} else {
wp_die( 'Invalid data' );
}
switch()
$untrusted_input = '1 malicious string'; // will evaluate to integer 1 during loose comparisons
switch ( true ) {
case 1 === $untrusted_input: // do your own strict comparison instead of relying on switch()'s loose comparison
echo '<p>Valid data';
break;
default:
wp_die( 'Invalid data' );
}
黑名单
拒绝来自已知不可信值的有限列表中的数据。这很少是一个好主意。
格式检测
测试数据的格式是否正确。如果是的话,只接受它。
if ( ! ctype_alnum( $data ) ) {
wp_die( "Invalid format" );
}
if ( preg_match( "/[^0-9.-]/", $data ) ) {
wp_die( "Invalid format" );
}
格式修正
接受大部分数据,但删除或更改危险部分。
$trusted_integer = (int) $untrusted_integer;
$trusted_alpha = preg_replace( '/[^a-z]/i', "", $untrusted_alpha );
$trusted_slug = sanitize_title( $untrusted_slug );
实例一
假设我们有一个输入字段旨在接受美国邮政编码:
<input type="text" id="wporg_zip_code" name="my-zipcode" maxlength="10" />
在这里,我们告诉浏览器最多只允许输入十个字符……但是对于可以输入的字符没有限制。他们可以输入11221
或eval()
。
这就是验证的用武之地。在处理表单时,我们编写代码来检查每个字段的数据类型是否正确,如果不正确则将其丢弃。
例如:要检查该my-zipcode
字段,我们可能会这样做:
/**
* Validate a US zip code.
*
* @param string $zip_code RAW zip code to check.
*
* @return bool true if valid, false otherwise.
*/
function wporg_is_valid_us_zip_code( string $zip_code ):bool {
// Scenario 1: empty.
if ( empty( $zip_code ) ) {
return false;
}
// Scenario 2: more than 10 characters.
// The `maxlength` attribute is only enforced by
// the browser, so we still need to validate the
// length of the input on the server to protect
// against a manual submission.
if ( 10 < strlen( trim( $zip_code ) ) ) {
return false;
}
// Scenario 3: incorrect format.
if ( ! preg_match( '/^d{5}(-?d{4})?$/', $zip_code ) ) {
return false;
}
// Passed successfully.
return true;
}
然后,在处理表单时,您的代码应该检查该wporg_zip_code
字段并根据结果执行操作:
if ( isset( $_POST['wporg_zip_code'] ) && wporg_is_valid_us_zip_code( $_POST['wporg_zip_code'] ) ) {
// $_POST['wporg_zip_code'] is valid; carry on
}
请注意,此特定示例正在检查提供的数据的格式是否正确;它不会检查提供的且格式正确的数据是否是有效的邮政编码。为此,您需要第二个函数来与有效邮政编码列表进行比较。
示例二
假设您的代码将查询数据库中的帖子,并且您希望允许用户对查询结果进行排序。
$allowed_keys = array( 'author', 'post_author', 'date', 'post_date' );
$orderby = sanitize_key( $_POST['orderby'] );
if ( in_array( $orderby, $allowed_keys, true ) ) {
// $orderby is valid; carry on
}
此示例代码通过将传入的排序键(存储在orderby
输入参数中)与允许的排序键数组进行比较来检查其有效性。这可以防止用户传递任意和潜在的恶意数据。
在根据数组检查传入的排序键之前,该键会被传递到内置的 WordPress 函数中 sanitize_key()
。该函数确保(除其他外)密钥为小写,这是我们想要的,因为in_array()
执行区分大小写的搜索。
传递true
到第三个参数 in_array()
可以启用严格的类型检查,这告诉函数不仅比较值,还比较值类型。这允许代码确定传入的排序键是字符串而不是其他数据类型。
验证功能
大多数验证都是作为自定义代码的一部分完成的,但也有一些辅助函数。这些是除“消毒”页面上列出的内容之外的内容。
检查WordPress 代码参考以获取更多此类功能。搜索具有如下名称的函数:*_exists()
、*_validate()
和 is_*()
。并非所有这些都是验证函数,但许多都是有帮助的。
转义数据
转义 输出是通过删除不需要的数据(例如格式错误的 HTML 或脚本标记)来保护输出数据的过程。此过程有助于在向最终用户呈现数据之前保护您的数据。
大多数 WordPress 函数都会正确准备输出数据,并且不需要额外的转义。
转义函数
WordPress 有许多辅助函数,可用于最常见的场景。
请密切注意每个函数的作用,因为有些函数会删除 HTML,而另一些函数则允许它。您必须使用最适合您所呼应的内容和上下文的功能。你总是在回声的时候想逃跑,而不是之前。
esc_html()
– 在 HTML 元素包含正在显示的数据部分时使用。这将删除 HTML。
<h4><?php echo esc_html( $title ); ?></h4>
esc_js()
– 用于内联 Javascript。
<div onclick='<?php echo esc_js( $value ); ?>' />
esc_url()
– 用于所有 URL,包括 HTML 元素的 src 和 href 属性中的 URL。
<img alt="" src="<?php echo esc_url( $media_url ); ?>" />
esc_url_raw()
– 在数据库中存储 URL 或需要非编码 URL 的其他情况下使用。esc_xml()
– 用于转义 XML 块。esc_attr()
– 用于打印到 HTML 元素属性中的所有其他内容。
<ul class="<?php echo esc_attr( $stored_class ); ?>">
esc_textarea()
– 使用它对文本进行编码以在文本区域元素内使用。wp_kses()
– 用于安全转义所有不受信任的 HTML(帖子文本、评论文本等)。这会保留 HTML。wp_kses_post()
– 替代版本wp_kses()
自动允许帖子内容中允许的所有 HTML。wp_kses_data()
– 其替代版本wp_kses()
仅允许帖子评论中允许的 HTML。
自定义转义示例
如果您需要以特定方式转义输出,函数wp_kses() (发音为“kisses”)将会派上用场。
此函数可确保输出中仅出现指定的 HTML 元素、属性和属性值,并规范化 HTML 实体。
<?php
echo wp_kses_post( $partial_html );
echo wp_kses(
$another_partial_html,
array(
'a' => array(
'href' => array(),
'title' => array(),
),
'br' => array(),
'em' => array(),
'strong' => array(),
)
); ?>
在此示例中,除 <a>
、 <br>
、 <em>
和 之外的所有标签都<strong>
将被删除。此外,如果 <a>
传递了标记,则转义可确保仅 返回thehref
和 the 。title
总是晚使用escape
最好尽可能晚地进行输出转义,最好是在输出数据时进行。
出于以下几个原因,最好晚点逃跑:
- 代码审查和部署可以更快地进行,因为一看就可以认为输出是安全的,而不是通过许多行代码来查看它在哪里以及是否已经被转义。
- 有些东西可能会无意中更改首次投射和输出之间的变量,从而引入潜在的漏洞。
- 后期转义可以更轻松地进行自动代码扫描,从而节省时间并减少审查和部署时间。
- 只要有可能,后期转义就会使代码更加健壮并且面向未来。
- 输出上的转义/转换消除了任何歧义并增加了清晰度(始终为维护者开发)。
// Okay, but not great.
$url = esc_url( $url );
$text = esc_html( $text );
echo '<a href="'. $url . '">' . $text . '</a>';
// Much better!
echo '<a href="'. esc_url( $url ) . '">' . esc_html( $text ) . '</a>';
......除非你不能
有时晚逃是不切实际的。在极少数情况下,输出无法传递到 wp_kses()
,因为根据定义,它会删除正在生成的脚本。
_escaped
在这种情况下,在创建字符串时始终进行转义,并将值存储在以, _safe
or 为后缀的变量中 _clean
(例如, $variable
变为 $variable_escaped
or $variable_safe
)。
如果一个函数无法在内部输出并且转义较晚,那么它必须始终返回“安全”HTML。这允许 不需要通过 允许此类标签echo my_custom_script_code();
的版本传递脚本标签来完成 。wp_kses()
通过本地化逃脱
通常使用 WordPress 本地化功能,例如或 ,而不是用于echo
输出数据。_e()
__()
这些函数只是将本地化函数包装在转义函数中:
esc_html_e( 'Hello World', 'text_domain' );
// Same as
echo esc_html( __( 'Hello World', 'text_domain' ) );
这些辅助函数结合了本地化和转义:
例子
echo $int;
根据它是整数还是浮点数, (int)
、 absint()
、 (float)
都是正确且可接受的。
有时, number_format()
或者 number_format_i18n()
可能更合适。
intval()
、 floatval()
是可以接受的,但是是过时的 (PHP4) 函数。
转义 HTML 属性中的任意变量
echo '<div id="', $prefix, '-box', $id, '">';
这应该通过一次调用来转义 esc_attr()
。
当变量用作属性或 url 的一部分时,最好对整个字符串进行转义,这样变量之前的潜在转义字符将被正确转义。
正确的:
echo '<div id="', esc_attr( $prefix . '-box' . $id ), '">';
错误:
echo '<div id="', esc_attr( $prefix ), '-box', esc_attr( $id ), '">';
注意: wp_create_nonce()
如果在 HTML 属性中使用,则使用创建的随机数也应该像这样转义。
在 HTML 属性中以及其他上下文中转义任意 URL
echo '<a href="', $url, '">';
这应该用 转义 esc_url()
。
正确的:
echo '<a href="', esc_url( $url ), '">';
错误:
echo '<a href="', esc_attr( $url ), '">';
echo '<a href="', esc_attr( esc_url( $url ) ), '">';
通过以下方式将任意变量传递给 JavaScript wp_localize_script()
wp_localize_script( 'handle', 'name',
array(
'prefix_nonce' => wp_create_nonce( 'plugin-name' ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'errorMsg' => __( 'An error occurred', 'plugin-name' ),
)
);
不需要转义,WordPress 会转义。
在 JavaScript 块中转义任意变量
<script type="text/javascript">
var myVar = <?php echo $my_var; ?>
</script>
$my_var
应该用 转义 esc_js()
。
正确的:
<script type="text/javascript">
var myVar = <?php echo esc_js( $my_var ); ?>
</script>
在内联 JavaScript 中转义任意变量
<a href="#" onclick="do_something(<?php echo $var; ?>); return false;">
$var
应该用 转义 esc_js()
。
正确的:
<a href="#" onclick="do_something(<?php echo esc_js( $var ); ?>); return false;">
转义 HTML 属性中的任意变量以供 JavaScript 使用
<a href="#" data-json="<?php echo $var; ?>">">
$var
应使用 esc_js()
, json_encode()
或 进行转义wp_json_encode()
。
正确的:
<a href="#" data-json="<?php echo esc_js( $var ); ?>">
在 HTML 文本区域中转义任意字符串
echo '<textarea>', $data, '</textarea>';
$data
应该用 转义 esc_textarea()
。
正确的:
echo '<textarea>', esc_textarea( $data ), '</textarea>';
转义 HTML 标签内的任意字符串
echo '<div>', $phrase, '</div>';
这取决于是否 $phrase
期望包含 HTML。
- 如果没有,请使用
esc_html()
或其任何变体。 - 如果需要 HTML,请使用
wp_kses_post()
,wp_kses_allowed_html()
或 以及wp_kses()
您想要允许的一组 HTML 标记。
在 XML 或 XSL 上下文中转义任意字符串
echo '<loc>', $var, '</loc>';
正确的:
echo '<loc>', ent2ncr( $var ), '</loc>';
随机数
随机数是“使用一次的数字”,有助于保护 URL 和表单免遭某些类型的滥用、恶意或其他形式的滥用。
从技术上讲,WordPress 随机数并不是严格意义上的数字;而是数字。它们是由数字和字母组成的哈希值。它们也不是只使用一次:它们的“寿命”有限,之后就会过期。在此时间段内,将为给定上下文中的给定用户生成相同的随机数。该操作的随机数对于该用户将保持不变,直到该随机数生命周期完成。
WordPress 的安全令牌被称为“随机数”(尽管与真正的随机数存在上述差异),因为它们的用途与随机数几乎相同。它们有助于防范包括 CSRF 在内的多种类型的攻击,但不能防范重放攻击,因为它们不会被检查为一次性使用。切勿依赖随机数进行身份验证、授权或访问控制。使用 保护您的函数current_user_can()
,并始终假设随机数可能会受到损害。
为什么要使用随机数?
有关使用随机数的示例,请考虑管理屏幕可能会生成这样的 URL,该 URL 会丢弃帖子编号 123
http://example.com/wp-admin/post.php?post=123&action=trash
当您访问该 URL 时,WordPress 将验证您的身份验证 cookie 信息,并且如果您被允许删除该帖子,则将继续删除它。攻击者可以利用此漏洞让您的浏览器在您不知情的情况下访问该 URL。例如,攻击者可以在第 3 方页面上制作一个伪装链接,如下所示:
<img src="http://example.com/wp-admin/post.php?post=123&action=trash" />
这将触发您的浏览器向 WordPress 发出请求,浏览器会自动附加您的身份验证 cookie,并且 WordPress 会认为这是一个有效的请求。
添加随机数可以防止这种情况发生。例如,当使用随机数时,WordPress 为用户生成的 URL 如下所示:
http://example.com/wp-admin/post.php?post=123&action=trash&_wpnonce=b192fc4204
如果有人试图在没有 WordPress 生成并提供给用户的正确随机数的情况下删除编号 123 的帖子,WordPress 将向浏览器发送“403 Forbidden”响应。
创建随机数
您可以创建一个随机数并将其添加到 URL 中的查询字符串中,也可以将其添加到表单中的隐藏字段中,或者可以通过其他方式使用它。
对于要在 AJAX 请求中使用的随机数,通常将随机数添加到隐藏字段,JavaScript 代码可以从中获取它。
请注意,随机数对于当前用户的会话是唯一的,因此如果用户异步登录或注销,页面上的任何随机数将不再有效。
为访客(非登录用户)自定义随机数
默认情况下,WordPress 核心会为具有相同用户 ID(值 0
)的访客生成相同的随机数。也就是说,它不能阻止访客遭受 CSRF 攻击。为了增强关键操作的安全性,您可以为访客开发会话机制,并挂钩 nonce_user_logged_out 过滤器,以将用户 ID 值替换 0
为会话机制中的另一个随机 ID。
将随机数添加到 URL
要将随机数添加到 URL,请调用wp_nonce_url()
指定裸 URL 和表示操作的字符串。例如:
$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID );
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
默认情况下,wp_nonce_url()
添加一个名为 的字段_wpnonce
。您可以在函数调用中指定不同的名称。例如:
$complete_url = wp_nonce_url( $bare_url, 'trash-post_'.$post->ID, 'my_nonce' );
向表单添加随机数
要将随机数添加到表单,请调用wp_nonce_field()
指定表示操作的字符串。默认情况下wp_nonce_field()
生成两个隐藏字段,一个的值为随机数,另一个的值为当前 URL(引荐来源网址),并且它会回显结果。例如,这个调用:
wp_nonce_field( 'delete-comment_'.$comment_id );
可能会回显类似的内容:
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
您可以为 nonce 字段指定不同的名称,您可以指定您不需要引用字段,并且您可以指定您希望返回结果而不是回显。有关语法的详细信息,请参阅:wp_nonce_field()
。
创建一个随机数以其他方式使用
要创建以其他方式使用的随机数,请调用wp_create_nonce()
指定表示操作的字符串。例如:
$nonce = wp_create_nonce( 'my-action_'.$post->ID );
这只是返回随机数本身。例如:295a686963
为了获得最大程度的保护,请确保表示操作的字符串尽可能具体。
验证随机数
您可以验证在 URL、管理屏幕中的表单、AJAX 请求或其他上下文中传递的随机数。
验证从管理屏幕传递的随机数
要验证在管理屏幕中的 URL 或表单中传递的随机数,请调用check_admin_referer()
指定表示操作的字符串。
例如:
check_admin_referer( 'delete-comment_'.$comment_id );
此调用检查随机数和引荐来源网址,如果检查失败,则采取正常操作(使用“403 Forbidden”响应和错误消息终止脚本执行)。
如果您在创建随机数时未使用默认字段名称 ( _wpnonce
),请指定字段名称。
例如:
check_admin_referer( 'delete-comment_'.$comment_id, 'my_nonce' );
验证 AJAX 请求中传递的随机数
要验证 AJAX 请求中传递的随机数,请调用check_ajax_referer() 并指定表示操作的字符串。例如:
check_ajax_referer( 'process-comment' );
此调用检查随机数(但不检查引用者),如果检查失败,则默认情况下它会终止脚本执行。
如果您在创建随机数时未使用默认字段名称(_wpnonce
或)之一,或者如果您想采取其他操作而不是终止执行,则可以指定其他参数。_ajax_nonce
详细信息请参见:check_ajax_referer()
。
验证在其他上下文中传递的随机数
要验证在其他上下文中传递的随机数,请wp_verify_nonce()
调用指定随机数和表示操作的字符串。
例如:
wp_verify_nonce( $_REQUEST['my_nonce'], 'process-comment'.$comment_id );
如果结果为 false,则不再继续处理该请求。相反,采取一些适当的行动。通常的操作是调用wp_nonce_ays()
,它会向浏览器发送“403 Forbidden”响应。
修改nonce系统
您可以通过添加各种操作和过滤器来修改随机数系统。
修改随机数生存期
默认情况下,随机数的生命周期为一天。此后,即使随机数与操作字符串匹配,随机数也不再有效。要更改生命周期,请添加一个 nonce_life 过滤器,指定生命周期(以秒为单位)。
例如,要将生命周期更改为四小时:
add_filter( 'nonce_life', function () { return 4 * HOUR_IN_SECONDS; } );
执行额外验证
check_admin_referrer()
要在发现随机数和引荐来源网址有效时执行附加验证,请添加check_admin_referer
操作。
例如:
function wporg_additional_check ( $action, $result ) {
...
}
add_action( 'check_admin_referer', 'wporg_additional_check', 10, 2 );
以同样的方式check_ajax_referer()
添加一个动作。check_ajax_referer
更改错误消息
您可以使用翻译系统更改随机数无效时发送的错误消息。例如:
function my_nonce_message ($translation) {
if ($translation === 'Are you sure you want to do this?') {
return 'No! No! No!';
}
return $translation;
}
add_filter('gettext', 'my_nonce_message');
附加信息
本节包含有关 WordPress 中的随机数系统的其他信息,这些信息有时可能有用。
随机数生命周期
请注意,正如 WordPress 随机数不是“使用一次的数字”一样,随机数生命周期也不是真正的随机数生命周期。WordPress 使用具有两个刻度(生命周期的一半)的系统,并验证当前刻度和最后一个刻度的随机数。在默认设置(24 小时生命周期)中,这意味着随机数中的时间信息与自 Unix 纪元以来经过了多少个 12 小时时间段相关。这意味着在中午和午夜之间创建的随机数将具有生命周期,直到第二天中午。因此,实际寿命在 12 至 24 小时之间变化。
当随机数有效时,验证随机数的函数将返回当前刻度编号 1 或 2。例如,您可以使用此信息来刷新第二个刻度中的随机数,以便它们不会过期。
随机数安全性
如果您正确安装了 WordPress,则使用您网站特有的密钥和盐生成随机数。NONCE_KEY
和NONCE_SALT
在您的文件中定义wp-config.php
,并且该文件包含提供更多信息的注释。
切勿依赖随机数进行身份验证或授权或访问控制。使用 保护您的函数current_user_can()
,始终假设随机数可能会受到损害。
更换随机数系统
构成随机数系统的一些函数是可插入的,以便您可以通过提供自己的函数来替换它们。
要更改管理请求或 AJAX 请求的验证方式,您可以替换check_admin_referrer()
或check_ajax_referrer()
,或两者都替换。
要将随机数系统替换为其他随机数系统,您可以替换wp_create_nonce()
,wp_verify_nonce()
和wp_nonce_tick()
。
有关的
随机数函数:wp_nonce_ays()
, wp_nonce_field()
, wp_nonce_url()
, wp_verify_nonce()
, wp_create_nonce()
, check_admin_referer()
, check_ajax_referer()
,wp_referer_field()
随机数挂钩:nonce_life
, nonce_user_logged_out
, explain_nonce_(verb)-(noun)
,check_admin_referer
用户角色和能力
用户角色和能力
如果您的插件允许用户提交数据(无论是在管理员端还是公共端),它应该检查用户功能。
用户角色和能力
创建高效安全层的最重要步骤是建立用户权限系统。WordPress 以用户角色和功能的形式提供这一点。
每个登录 WordPress 的用户都会根据其用户角色自动分配特定的用户功能。
用户角色只是一种表示用户属于哪个组的奇特方式。每个组都有一组特定的预定义功能。
例如,网站的主要用户将具有管理员的用户角色,而其他用户可能具有编辑者或作者等角色。您可以为一个角色分配多个用户,即一个网站可能有两个管理员。
用户能力是您分配给每个用户或用户角色的特定权限。
例如,管理员具有“manage_options”功能,允许他们查看、编辑和保存网站的选项。另一方面,编辑者缺乏这种能力,这将阻止他们与选项进行交互。
然后在管理中的各个点检查这些功能。取决于分配给角色的能力;菜单、功能和 WordPress 体验的其他方面可能会被添加或删除。
当您构建插件时,请确保仅在当前用户具有必要的功能时才运行您的代码。
等级制度
用户角色越高,用户拥有的能力越多。每个用户角色都会继承层次结构中先前的角色。
例如,“管理员”是单个站点安装中的最高用户角色,它继承以下角色及其功能:“订阅者”、“贡献者”、“作者”和“编辑者”。
例子
无限制
下面的示例在前端创建一个链接,该链接提供了垃圾帖子的功能。因为此代码不检查用户能力,所以它允许该网站的任何访问者删除帖子!
/**
* Generate a Delete link based on the homepage url.
*
* @param string $content Existing content.
*
* @return string|null
*/
function wporg_generate_delete_link( $content ) {
// Run only for single post page.
if ( is_single() && in_the_loop() && is_main_query() ) {
// Add query arguments: action, post.
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
], home_url()
);
return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
}
return null;
}
/**
* Request handler
*/
function wporg_delete_post() {
if ( isset( $_GET['action'] ) && 'wporg_frontend_delete' === $_GET['action'] ) {
// Verify we have a post id.
$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );
// Verify there is a post with such a number.
$post = get_post( (int) $post_id );
if ( empty( $post ) ) {
return;
}
// Delete the post.
wp_trash_post( $post_id );
// Redirect to admin page.
$redirect = admin_url( 'edit.php' );
wp_safe_redirect( $redirect );
// We are done.
die;
}
}
/**
* Add the delete link to the end of the post content.
*/
add_filter( 'the_content', 'wporg_generate_delete_link' );
/**
* Register our request handler with the init hook.
*/
add_action( 'init', 'wporg_delete_post' );
受限于特定能力
上面的示例允许该网站的任何访问者单击“删除”链接并删除该帖子。但是,我们只希望编辑及以上级别能够单击“删除”链接。
为了实现这一点,我们将检查当前用户是否具有该能力edit_others_posts
,只有编辑者或以上人员才具有:
/**
* Generate a Delete link based on the homepage url.
*
* @param string $content Existing content.
*
* @return string|null
*/
function wporg_generate_delete_link( $content ) {
// Run only for single post page.
if ( is_single() && in_the_loop() && is_main_query() ) {
// Add query arguments: action, post.
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
], home_url()
);
return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
}
return null;
}
/**
* Request handler
*/
function wporg_delete_post() {
if ( isset( $_GET['action'] ) && 'wporg_frontend_delete' === $_GET['action'] ) {
// Verify we have a post id.
$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );
// Verify there is a post with such a number.
$post = get_post( (int) $post_id );
if ( empty( $post ) ) {
return;
}
// Delete the post.
wp_trash_post( $post_id );
// Redirect to admin page.
$redirect = admin_url( 'edit.php' );
wp_safe_redirect( $redirect );
// We are done.
die;
}
}
/**
* Add delete post ability
*/
add_action('plugins_loaded', 'wporg_add_delete_post_ability');
function wporg_add_delete_post_ability() {
if ( current_user_can( 'edit_others_posts' ) ) {
/**
* Add the delete link to the end of the post content.
*/
add_filter( 'the_content', 'wporg_generate_delete_link' );
/**
* Register our request handler with the init hook.
*/
add_action( 'init', 'wporg_delete_post' );
}
}
常见漏洞
常见漏洞
安全是一个不断变化的环境,漏洞会随着时间的推移而演变。以下讨论了您应该防范的常见漏洞,以及保护您的主题免遭利用的技术。
漏洞类型
SQL注入
当输入的值未正确清理时,就会发生 SQL 注入,从而可能执行输入数据中的任何 SQL 命令。为了防止这种情况,WordPress API 非常丰富,提供了诸如此类的功能, add_post_meta();
而无需您通过 SQL ( INSERT INTO wp_postmeta…
) 手动添加帖子元。

xkcd妈妈的功绩
强化主题抵御 SQL 注入的第一条规则是:当有 WordPress 功能时,使用它。
但有时您需要执行 API 中未考虑到的复杂查询。如果是这种情况,请始终使用这些 $wpdb
功能。这些是专门为保护您的数据库而构建的。
SQL查询中的所有数据在执行SQL查询之前都必须进行SQL转义,以防止SQL注入攻击。用于 SQL 转义的最佳函数是$wpdb->prepare()
同时支持类似sprintf()和类似vsprintf()的语法。
$wpdb->get_var( $wpdb->prepare(
"SELECT something FROM table WHERE foo = %s and status = %d",
$name, // an unescaped string (function will do the sanitization for you)
$status // an untrusted integer (function will do the sanitization for you)
) );
跨站脚本 (XSS)
当不法分子将 JavaScript 注入网页时,就会发生跨站脚本攻击 (XSS)。
通过转义输出、删除不需要的数据来避免 XSS 漏洞。由于主题的主要职责是输出内容,因此主题应 根据内容的类型使用适当的功能转义动态内容。
转义功能之一的示例是从用户配置文件中转义 URL。
<img src="<?php echo esc_url( $great_user_picture_url ); ?>" />
可以对包含 HTML 实体的内容进行清理,以仅允许指定的 HTML 元素。
$allowed_html = array(
'a' => array(
'href' => array(),
'title' => array()
),
'br' => array(),
'em' => array(),
'strong' => array(),
);
echo wp_kses( $custom_content, $allowed_html );
跨站请求伪造 (CSRF)
跨站点请求伪造或 CSRF(发音为 sea-surf)是指不法分子诱骗用户在其进行身份验证的 Web 应用程序中执行不需要的操作。例如,网络钓鱼电子邮件可能包含指向某个页面的链接,该页面在 WordPress 管理员中删除用户帐户。
如果您的主题包含任何基于 HTML 或 HTTP 的表单提交,请使用 随机数 来保证用户打算执行操作。
<form method="post">
<!-- some inputs here … -->
<?php wp_nonce_field( 'name_of_my_action', 'name_of_nonce_field' ); ?>
</form>
保持最新状态
及时了解潜在的安全漏洞非常重要。以下资源提供了一个良好的起点:
例子
例子
使用功能检查、数据验证、安全输入、安全输出和随机数的完整示例:
/**
* Generate a Delete link based on the homepage url.
*
* @param string $content Existing content.
*
* @return string|null
*/
function wporg_generate_delete_link( $content ) {
// Run only for single post page.
if ( is_single() && in_the_loop() && is_main_query() ) {
// Add query arguments: action, post, nonce
$url = add_query_arg(
[
'action' => 'wporg_frontend_delete',
'post' => get_the_ID(),
'nonce' => wp_create_nonce( 'wporg_frontend_delete' ),
], home_url()
);
return $content . ' <a href="' . esc_url( $url ) . '">' . esc_html__( 'Delete Post', 'wporg' ) . '</a>';
}
return null;
}
/**
* Request handler
*/
function wporg_delete_post() {
if ( isset( $_GET['action'] )
&& isset( $_GET['nonce'] )
&& 'wporg_frontend_delete' === $_GET['action']
&& wp_verify_nonce( $_GET['nonce'], 'wporg_frontend_delete' ) ) {
// Verify we have a post id.
$post_id = ( isset( $_GET['post'] ) ) ? ( $_GET['post'] ) : ( null );
// Verify there is a post with such a number.
$post = get_post( (int) $post_id );
if ( empty( $post ) ) {
return;
}
// Delete the post.
wp_trash_post( $post_id );
// Redirect to admin page.
$redirect = admin_url( 'edit.php' );
wp_safe_redirect( $redirect );
// We are done.
die;
}
}
/**
* Add delete post ability
*/
add_action('plugins_loaded', 'wporg_add_delete_post_ability');
function wporg_add_delete_post_ability() {
if ( current_user_can( 'edit_others_posts' ) ) {
/**
* Add the delete link to the end of the post content.
*/
add_filter( 'the_content', 'wporg_generate_delete_link' );
/**
* Register our request handler with the init hook.
*/
add_action( 'init', 'wporg_delete_post' );
}
}
挂钩
挂钩
挂钩是一段代码在特定的预定义位置交互/修改另一段代码的方式。它们构成了插件和主题如何与 WordPress Core 交互的基础,但 Core 本身也广泛使用它们。
有两种类型的钩子:Actions和Filters。要使用其中任何一个,您需要编写一个名为 a 的自定义函数Callback
,然后将其注册到 WordPress 挂钩以执行特定操作或过滤器。
操作允许您添加数据或更改 WordPress 的运行方式。操作将在 WordPress 核心、插件和主题执行的特定点运行。操作的回调函数可以执行某种任务,例如向用户回显输出或将某些内容插入数据库。Action 的回调函数不会将任何内容返回到调用 Action 挂钩。
过滤器使您能够在 WordPress 核心、插件和主题执行期间更改数据。过滤器的回调函数将接受一个变量,修改它,然后返回它。它们旨在以隔离的方式工作,并且永远不应该产生副作用,例如影响全局变量和输出。过滤器期望有一些东西返回给他们。
WordPress 提供了许多可供您使用的挂钩,但您也可以创建自己的挂钩,以便其他开发人员可以扩展和修改您的插件或主题。
操作与过滤器
动作和过滤器之间的主要区别可以总结如下:
- 动作获取它收到的信息,用它做一些事情,并且不返回任何内容。换句话说:它作用于某些东西,然后退出,不向调用钩子返回任何内容。
- 过滤器获取它接收到的信息,以某种方式修改它,然后返回它。换句话说:它过滤一些东西并将其传回钩子以供进一步使用。
另一种说法是:
- 动作会中断代码流以执行某些操作,然后返回到正常流程而不修改任何内容;
- 过滤器用于以特定方式修改某些内容,以便稍后的代码使用该修改。
所指的是通过钩子定义发送的参数列表。稍后部分将详细介绍这一点。
行动Actions
操作是两种类型的Hook之一。它们提供了一种在 WordPress 核心、插件和主题执行的特定点运行函数的方法。Action 的回调函数不会将任何内容返回到调用 Action 挂钩。它们与Filter相对应。这里回顾一下操作和过滤器之间的区别。
添加动作
添加动作的过程包括两个步骤:
创建回调函数
首先,创建一个回调函数。当它所挂接的操作运行时,该函数将运行。
回调函数就像普通函数一样:它应该有前缀,并且应该位于functions.php
可调用的位置。它应该接受的参数将由您挂钩的操作定义;大多数钩子都有明确的定义,因此请查看钩子文档以了解您选择的操作将传递给函数的参数。
分配(钩子)你的回调函数
其次,将回调函数添加到操作中。这称为挂钩,并告诉操作在运行时运行回调函数。
当您的回调函数准备就绪时,使用add_action() 将其挂钩到您选择的操作。至少add_action()
需要两个参数:
string $hook_name
这是您要挂钩的操作的名称,并且callable $callback
您的回调函数的名称。
下面的示例将在执行钩子wporg_callback()
时运行:init
function wporg_callback() {
// do something
}
add_action( 'init', 'wporg_callback' );
您可以参考Hooks章节来获取可用钩子的列表。
随着您获得更多经验,查看 WordPress 核心源代码将使您找到最合适的挂钩。
附加参数
add_action()
可以接受两个附加参数,int $priority
用于指定回调函数的优先级,以及int $accepted_args
将传递给回调函数的参数数量。
优先事项
许多回调函数可以与单个操作挂钩。例如,钩子init
就有很多用途。在某些情况下,您可能需要确保您的回调函数在其他回调函数之前或之后运行,即使这些其他函数可能尚未被挂钩。
WordPress 根据两件事确定回调函数的运行顺序:第一种方法是手动设置优先级。这是使用 的第三个参数完成的add_action()
。
以下是有关优先级的一些重要事实:
- 优先级是正整数,通常在 1 到 20 之间
priority
默认优先级(即未手动提供值时分配的优先级)为 10- 优先级值理论上没有上限,但现实上限为100
优先级为11的函数将在优先级为10的函数之后运行;优先级为 9 的函数将在优先级为 10 的函数之前运行。
确定回调函数顺序的第二种方法是简单地根据它在同一优先级值中注册的顺序。因此,如果两个回调函数以相同的优先级为同一个钩子注册,它们将按照它们注册到该钩子的顺序运行。
例如,以下回调函数都注册到init
钩子中,但具有不同的优先级:
add_action('init', 'wporg_callback_run_me_late', 11);
add_action('init', 'wporg_callback_run_me_normal');
add_action('init', 'wporg_callback_run_me_early', 9);
add_action('init', 'wporg_callback_run_me_later', 11);
在上面的例子中:
- 第一个运行的函数将是
wporg_callback_run_me_early()
,因为它的手动优先级为 9 - 接下来,
wporg_callback_run_me_normal(),
因为它没有设置优先级,所以它的优先级是10 - 接下来,
wporg_callback_run_me_late()
运行,因为它的手动优先级为 11 - 最后
wporg_callback_run_me_later()
是 run:它的优先级也为 11,但它是在wporg_callback_run_me_late()
.
参数数量
有时,回调函数需要接收一些与所挂接的操作相关的额外数据。
例如,当 WordPress 保存帖子并运行钩子时save_post
,它会向回调函数传递两个参数:正在保存的帖子的 ID 和帖子对象本身:
do_action( 'save_post', $post->ID, $post );
当为钩子注册回调函数时save_post
,它可以指定它想要接收这两个参数。它通过add_action
(在本例中)2
作为第四个参数来告诉我们期望它们:
add_action('save_post', 'wporg_custom', 10, 2);
为了在回调函数中实际接收这些参数,请修改回调函数将接受的参数,如下所示:
function wporg_custom( $post_id, $post ) {
// do something
}
过滤器filters
过滤器
过滤器是两种类型的Hook之一。
它们为函数提供了一种在 WordPress 核心、插件和主题执行期间修改数据的方法。它们与Actions相对应。
与Actions不同,过滤器旨在以隔离的方式工作,并且永远不应该产生副作用,例如影响全局变量和输出。过滤器期望有一些东西返回给他们。
添加过滤器
添加过滤器的过程包括两个步骤。
首先,您需要创建一个回调函数,该函数将在过滤器运行时调用。其次,您需要将回调函数添加到一个钩子中,该钩子将执行函数的调用。
您将使用add_filter() 函数,并传递至少两个参数:
string $hook_name
这是您要连接的过滤器的名称,并且callable $callback
您的回调函数的名称。
下面的示例将在the_title
执行过滤器时运行。
function wporg_filter_title( $title ) {
return 'The ' . $title . ' was filtered';
}
add_filter( 'the_title', 'wporg_filter_title' );
假设我们有一个帖子标题“学习 WordPress”,上面的示例将其修改为“学习 WordPress 已被过滤”。
您可以参考Hooks章节来获取可用钩子的列表。
随着您获得更多经验,查看 WordPress 核心源代码将使您找到最合适的挂钩。
附加参数
add_filter() 可以接受两个附加参数,int $priority
用于指定回调函数的优先级,以及int $accepted_args
将传递给回调函数的参数数量。
例子
要在满足特定条件时向标记添加 CSS 类:
function wporg_css_body_class( $classes ) {
if ( ! is_admin() ) {
$classes[] = 'wporg-is-awesome';
}
return $classes;
}
add_filter( 'body_class', 'wporg_css_body_class' );
定制挂钩
一个重要但经常被忽视的做法是在插件中使用自定义挂钩,以便其他开发人员可以扩展和修改它。
自定义挂钩的创建和调用方式与 WordPress 核心挂钩相同。
创建一个钩子
要创建自定义挂钩,请使用do_action()
for Actions和apply_filters()
for Filters。
这使得插件更容易根据用户的需求进行修改。
添加回调到钩子
要将回调函数添加到自定义挂钩,请使用add_action()
for Actions和add_filter()
for Filters。
命名冲突
当两个开发人员将相同的钩子名称用于完全不同的目的时,就会发生命名冲突(“冲突”)。这导致很难发现错误。因此,在钩子名称前添加一个唯一的字符串是很重要的,以避免钩子名称与其他插件发生冲突。
例如,名为的过滤器email_body
足够通用,两个或多个开发人员可以在不同的插件中出于不同的目的使用此钩子。所以为了避免这种情况,添加了一个前缀。例如,本手册中用作示例的函数使用wporg_
作为前缀。
当你选择你的前缀时,你可以使用你的公司名称、你的 wp 句柄、插件名称,任何你真正喜欢的东西。我们的目标是使其独一无二,因此请明智地选择。
例子
可扩展操作:设置表单
如果您的插件将设置表单添加到管理面板,您可以使用操作来允许其他插件向其中添加自己的设置。
do_action( 'wporg_after_settings_page_html' );
现在另一个插件可以为钩子注册回调函数wporg_after_settings_page_html
并注入新设置:
add_action( 'wporg_after_settings_page_html', 'myprefix_add_settings' );
请注意,因为这是一个操作,所以不会返回任何值。另请注意,由于未指定优先级,因此它将以默认优先级 10 运行。
可扩展过滤器:自定义帖子类型
在此示例中,当注册新的帖子类型时,定义它的参数将通过过滤器传递,因此另一个插件可以在创建帖子类型之前更改它们。
function wporg_create_post_type() {
$post_type_params = [/* ... */];
register_post_type(
'post_type_slug',
apply_filters( 'wporg_post_type_params', $post_type_params )
);
}
现在另一个插件可以为钩子注册回调函数wporg_post_type_params
并更改帖子类型参数:
function myprefix_change_post_type_params( $post_type_params ) {
$post_type_params['hierarchical'] = true;
return $post_type_params;
}
add_filter( 'wporg_post_type_params', 'myprefix_change_post_type_params' );
请注意,过滤器会获取数据、修改数据并返回数据。因此,名为 ( ) 的代码myprefix_change_post_type_params
不会使用 echo 或 html 或直接将任何其他内容输出到屏幕。另请注意,返回的值将直接使用,而register_post_type
无需先分配给变量。这很容易跳过额外的(不必要的)步骤。
另请注意,由于未指定优先级,因此它将以默认优先级 10 运行。并且由于没有预期参数数量的值,因此假定默认值为 1。
高级主题
删除操作和过滤器
有时您想从另一个插件、主题甚至 WordPress Core 已注册的挂钩中删除回调函数。
要从挂钩中删除回调函数,您需要调用remove_action()
或 remove_filter()
,具体取决于回调函数是作为操作还是过滤器添加。
remove_action()
传递给/ 的参数 必须与传递给/注册它的remove_filter()
参数相同,否则删除将不起作用。add_action()
add_filter()
例子
假设我们希望通过删除不必要的功能来提高大型主题的性能。
让我们通过查看 来分析主题的代码functions.php
。
function wporg_setup_slider() {
// ...
}
add_action( 'template_redirect', 'wporg_setup_slider', 9 );
该wporg_setup_slider
函数正在添加一个我们不需要的滑块,它可能会加载一个巨大的 CSS 文件,然后加载一个 JavaScript 初始化文件,该文件使用大小为 1MB 的自定义编写库。我们可以摆脱它。
wporg_setup_slider
由于我们希望在注册(执行)回调函数后挂钩 WordPress,因此functions.php
最好的机会就是after_setup_theme
挂钩。
function wporg_disable_slider() {
// Make sure all parameters match the add_action() call exactly.
remove_action( 'template_redirect', 'wporg_setup_slider', 9 );
}
// Make sure we call remove_action() after add_action() has been called.
add_action( 'after_setup_theme', 'wporg_disable_slider' );
删除所有回调
remove_all_actions()
您还可以使用/删除与挂钩关联的所有回调函数remove_all_filters()
。
确定当前钩子
有时您想要在多个钩子上运行一个操作或一个过滤器,但根据当前调用它的钩子而表现不同。
您可以使用 current_action()
/current_filter()
来确定当前的操作/过滤器。
function wporg_modify_content( $content ) {
switch ( current_filter() ) {
case 'the_content':
// Do something.
break;
case 'the_excerpt':
// Do something.
break;
}
return $content;
}
add_filter( 'the_content', 'wporg_modify_content' );
add_filter( 'the_excerpt', 'wporg_modify_content' );
检查钩子运行了多少次
有些钩子在执行过程中会被多次调用,但您可能只希望回调函数运行一次。
在这种情况下,您可以使用did_action()检查钩子运行了多少次。
function wporg_custom() {
// If save_post has been run more than once, skip the rest of the code.
if ( did_action( 'save_post' ) !== 1 ) {
return;
}
// ...
}
add_action( 'save_post', 'wporg_custom' );
使用“all”Hook 进行调试
如果您希望在每个钩子上触发回调函数,则可以将其注册到该all
钩子。有时,这在调试情况下很有用,可以帮助确定特定事件何时发生或页面何时崩溃。
function wporg_debug() {
echo '<p>' . current_action() . '</p>';
}
add_action( 'all', 'wporg_debug' );
隐私
隐私
您是否正在编写一个处理个人数据(例如姓名、地址和其他可用于识别个人身份的信息)的插件?您需要妥善保管这些数据并保护用户和访问者的隐私。
什么是隐私?
WordPress.org 在欧洲通用数据保护条例出台之前进行了多项改进。这项工作启动后,我们将隐私作为核心 trac 开发的永久重点,这将使我们能够在特定立法之外继续增强隐私和数据保护。
但什么样的问题可能属于“隐私”的定义,我们如何定义它呢?尽管隐私要求在不同国家、文化和法律体系之间存在很大差异,但有一些适用于任何情况的一般原则:
- 同意和选择:为用户(和网站访问者)提供对其数据使用的选择和选择,并要求明确、具体和知情的选择加入;
- 目的合法性和规范性:仅出于预期目的且事先明确告知用户的目的收集和使用个人数据;
- 收集限制:仅收集需要的用户数据;如果可以避免,请勿制作额外的数据副本或将您的数据与其他插件的数据合并
- 数据最小化:将数据处理以及有权访问数据的人数限制在最低限度的用途和必要的人员;
- 使用、保留和披露限制:删除接收者和任何第三方在主动使用和存档中不再需要的数据;
- 准确性和质量:确保收集和使用的数据是正确的、相关的和最新的,特别是如果不准确或不良的数据可能对用户产生不利影响;
- 公开、透明和通知:告知用户他们的数据如何被收集、使用和共享,以及他们对这些使用拥有的任何权利;
- 个人参与和访问:为用户提供访问或下载其数据的方式;
- 问责制:记录数据的使用情况,保护数据在传输过程中和第三方使用中的安全,并尽可能防止滥用和泄露;
- 信息安全:通过适当的技术和安全措施保护数据;
- 隐私合规性:确保作品符合收集和处理人员数据所在地的隐私法规。
(来源:ISO 29100/隐私框架标准)
虽然并非所有这些原则都适用于所有情况和用途,但在开发过程中使用它们可以帮助确保用户信任。
隐私设计
其中许多原则都在“隐私设计”框架中得到体现,该框架指出:
- 隐私应该是主动的,而不是被动的,并且必须在隐私问题到达用户之前就预见到它们。隐私还必须是预防性的,而不是补救性的。
- 隐私应该是默认设置。用户不应采取行动来保护其隐私,也不应假定用户同意数据共享。
- 隐私应作为核心功能而不是附加功能纳入设计中。
- 隐私应该是正和的:隐私与安全、隐私与安全、隐私与服务提供之间不应该有任何取舍。
- 隐私应通过数据最小化、最小化数据保留以及定期删除不再需要的数据来提供端到端的生命周期保护。
- 您的插件(和服务,如果适用)使用的隐私标准应该是可见的、透明的、开放的、记录在案的且可独立验证的。
- 隐私应该以用户为中心。应为人们提供诸如精细的隐私选择、最大化的隐私默认值、详细的隐私信息通知、用户友好的选项以及明确的更改通知等选项。
您的插件值得深思的地方
为了帮助您的插件做好准备,我们建议您针对您制作的每个插件检查以下问题列表:
- 您的插件如何处理个人数据?使用 wp_add_privacy_policy_content(链接)向您的用户披露以下任何内容:
- 该插件是否与第三方共享个人数据(例如外部 API/服务器)。如果是,它与哪些第三方共享哪些数据?他们是否有已发布的隐私政策(您可以提供链接)?
- 该插件是否收集个人数据?如果是,什么数据以及存储在哪里?考虑用户数据/元、选项、帖子元、自定义表、文件等地方。
- 该插件是否使用其他人收集的个人数据?如果是的话,什么数据?该插件是否将个人数据传递给 SDK?该 SDK 对数据有何作用?
- 该插件是否直接或间接收集遥测数据?例如,在每次安装时从第三方源加载图像可以间接记录和跟踪所有插件安装的使用数据。
- 该插件是否将 Javascript 入队、跟踪像素或嵌入来自第三方的 iframe(第三方 JS、跟踪像素和 iframe 可以收集访问者的数据/操作、留下 cookie 等)?
- 该插件是否在浏览器中存储内容?如果是这样,在哪里以及什么?考虑 cookie、本地存储等。
- 如果您的插件收集个人数据......
- 它提供个人数据导出器吗?
- 它是否提供个人数据擦除回调?
- 该插件出于什么原因(如果有)拒绝删除个人数据?(例如订单尚未完成等)——这些也应该披露。
- 该插件是否使用错误日志记录?如果可能的话,它是否会避免记录个人数据?您可以使用 wp_privacy_anonymize_data 之类的东西来最大程度地减少记录的个人数据吗?日志条目保留多长时间?谁有权访问它们?
- 在 wp-admin 中,访问/查看个人数据需要什么角色/能力?它们足够了吗?
- 该插件会在网站前端暴露哪些个人数据?它是否向登录和注销的用户显示?应该是?
- 该插件会在 REST API 端点中公开哪些个人数据?它是否向登录和注销的用户显示?需要什么角色/能力才能看到它?那些合适吗?
- 该插件是否正确删除/清理数据,特别是个人数据:
- 卸载插件期间?
- 当相关项目被删除时(例如,从帖子元数据或另一个表中的任何帖子引用行)?
- 当用户被删除时(例如,从引用表中的行的任何用户中)?
- 该插件是否提供控件来减少所需的个人数据量?
- 插件是否仅在 SDK 或 API 需要时才与 SDK 或 API 共享个人数据,还是插件也共享可选的个人数据?
- 当还安装某些其他插件时,此插件收集或共享的个人数据量是否会发生变化?
外部资源
- 隐私博客https://privacy.blog
- WordPress.org 隐私政策https://wordpress.org/about/privacy/
建议网站隐私政策的文本
每个收集、使用或存储用户数据,或将其传递给外部来源或第三方的插件,都应将一段建议文本添加到隐私政策邮箱中。这最好用 来完成 wp_add_privacy_policy_content( $plugin_name, $policy_text )
。这将允许站点管理员将该信息提取到其站点的隐私策略中。
为了使用户更简单,文本应解决默认隐私政策中提供的问题:
- 我们收集哪些个人数据以及为什么收集这些数据
- 自己手动输入信息
- WP:联系表格
- WP:评论
- WP:饼干
- WP:第三方嵌入
- 分析
- 我们与谁分享您的数据
- 我们保留您的数据多久
- 您对您的数据拥有什么权利
- 我们将您的数据发送到哪里
- 您的联系方式
- 我们如何保护您的数据
- 我们采取了哪些数据泄露程序
- 我们从哪些第三方接收数据
- 我们对用户数据进行哪些自动化决策和/或分析
- 任何行业监管披露要求
虽然并非所有这些问题都适用于所有插件,但我们建议您注意有关数据共享的部分。
代码示例
笔记:可以通过使用专门的.privacy-policy-tutorial
CSS 类来提供补充信息。复制该部分内容时,应用了此 CSS 类的 HTML 元素中包含的任何内容都将从剪贴板中省略。
/**
* Adds a privacy policy statement.
*/
function wporg_add_privacy_policy_content() {
if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
return;
}
$content = '<p class="privacy-policy-tutorial">' . __( 'Some introductory content for the suggested text.', 'text-domain' ) . '</p>'
. '<strong class="privacy-policy-tutorial">' . __( 'Suggested Text:', 'my_plugin_textdomain' ) . '</strong> '
. sprintf(
__( 'When you leave a comment on this site, we send your name, email address, IP address and comment text to example.com. Example.com does not retain your personal data. The example.com privacy policy is <a href="%1$s" target="_blank">here</a>.', 'text-domain' ),
'https://example.com/privacy-policy'
);
wp_add_privacy_policy_content( 'Example Plugin', wp_kses_post( wpautop( $content, false ) ) );
}
add_action( 'admin_init', 'wporg_add_privacy_policy_content' );
个人数据导出
将个人数据导出器添加到您的插件中
在 WordPress 4.9.6 中,添加了新工具,以便更轻松地遵守欧盟《通用数据保护条例》(简称 GDPR)等法律。添加的工具之一是个人数据导出工具,它支持将给定用户的所有个人数据导出到 ZIP 文件中。除了存储在 WordPress 评论等内容中的个人数据之外,插件还可以连接导出器功能来导出他们收集的个人数据,无论是像 postmeta 还是全新的自定义帖子类型 (CPT)。
所有导出的“关键”是用户的电子邮件地址——选择它是因为它支持导出正式注册用户和未注册用户(例如,注销的评论者)的个人数据。
但是,由于组装个人数据导出可能是一个密集的过程,并且可能包含敏感数据,因此我们不想只生成它并通过电子邮件将其发送给请求者而不确认请求,因此面向管理员的用户界面会启动所有请求让管理员输入提出请求的用户名或电子邮件地址,然后发送一个链接以单击以确认其请求。
确认请求后,管理员可以为用户生成并下载或直接通过电子邮件发送个人数据导出 ZIP 文件,或者在需要时进行导出。在用户收到的 ZIP 文件中,他们会找到一个“迷你网站”,其中包含索引 HTML 页面,其中包含按组组织的个人数据(例如评论组等)。
无论管理员下载个人数据导出 ZIP 文件还是直接将其发送给请求者,个人数据导出的组装方式都是相同的 - 并依赖挂钩“导出器”回调来完成收集所有导出数据的肮脏工作。当管理员单击下载或电子邮件链接时,AJAX 循环就会开始,一次一个地迭代系统中注册的所有导出器。除了内置于核心中的导出器之外,插件还可以注册自己的导出器回调。
导出器回调接口设计得尽可能简单。导出器回调接收我们正在使用的电子邮件地址和页面参数。page 参数(从 1 开始)用于避免插件尝试一次导出其收集的所有个人数据而可能导致超时。一个表现良好的插件会限制它尝试删除每页的数据量(例如 100 个帖子、200 个评论等)
导出器回调会回复该电子邮件地址和页面的任何数据以及是否完成。如果导出器回调报告未完成,则将再次调用它(在单独的请求中),页面参数递增 1。导出器回调应返回用于导出的项目数组。每个项目包含一个
项目所属组的组标识符(例如评论、帖子、订单等)、一个可选的组标签(已翻译)、一个项目标识符(例如 comment-133),然后是一个数组包含要为该项目导出的数据的名称、值对。
值得注意的是,该值可以是媒体路径,在这种情况下,媒体文件的链接将添加到导出中的索引 HTML 页面。
当所有导出器都完成后,WordPress 首先组装一个“索引”HTML 文档,作为导出报告的核心。如果插件报告 WordPress 或其他插件已添加的项目的附加数据,则该项目的所有数据将一起显示。
导出的内容会在服务器上缓存 3 天,然后删除。
一个插件可以注册一个或多个导出器,但大多数插件只需要一个。让我们研究一个假设的插件,它将评论者的位置数据添加到评论中。
首先,我们假设插件已使用“add_comment_meta”使用“latitude”和“longitude”的“meta_key”添加位置数据
该插件需要做的第一件事是创建一个接受电子邮件地址和页面的导出器函数,例如:
/**
* Export user meta for a user using the supplied email.
*
* @param string $email_address email address to manipulate
* @param int $page pagination
*
* @return array
*/
function wporg_export_user_data_by_email( $email_address, $page = 1 ) {
$number = 500; // Limit us to avoid timing out
$page = (int) $page;
$export_items = array();
$comments = get_comments(
array(
'author_email' => $email_address,
'number' => $number,
'paged' => $page,
'order_by' => 'comment_ID',
'order' => 'ASC',
)
);
foreach ( (array) $comments as $comment ) {
$latitude = get_comment_meta( $comment->comment_ID, 'latitude', true );
$longitude = get_comment_meta( $comment->comment_ID, 'longitude', true );
// Only add location data to the export if it is not empty.
if ( ! empty( $latitude ) ) {
// Most item IDs should look like postType-postID. If you don't have a post, comment or other ID to work with,
// use a unique value to avoid having this item's export combined in the final report with other items
// of the same id.
$item_id = "comment-{$comment->comment_ID}";
// Core group IDs include 'comments', 'posts', etc. But you can add your own group IDs as needed
$group_id = 'comments';
// Optional group label. Core provides these for core groups. If you define your own group, the first
// exporter to include a label will be used as the group label in the final exported report.
$group_label = __( 'Comments', 'text-domain' );
// Plugins can add as many items in the item data array as they want.
$data = array(
array(
'name' => __( 'Commenter Latitude', 'text-domain' ),
'value' => $latitude,
),
array(
'name' => __( 'Commenter Longitude', 'text-domain' ),
'value' => $longitude,
),
);
$export_items[] = array(
'group_id' => $group_id,
'group_label' => $group_label,
'item_id' => $item_id,
'data' => $data,
);
}
}
// Tell core if we have more comments to work on still.
$done = count( $comments ) > $number;
return array(
'data' => $export_items,
'done' => $done,
);
}
插件需要做的下一件事是通过使用“wp_privacy_personal_data_exporters”过滤器过滤导出器数组来注册回调。
注册时,您为导出提供一个友好的名称(以帮助调试 - 这个友好的名称此时不会向任何人显示)和回调,例如
/**
* Registers all data exporters.
*
* @param array $exporters
*
* @return mixed
*/
function wporg_register_user_data_exporters( $exporters ) {
$exporters['my-plugin-slug'] = array(
'exporter_friendly_name' => __( 'Comment Location Plugin', 'text-domain' ),
'callback' => 'my_plugin_exporter',
);
return $exporters;
}
add_filter( 'wp_privacy_personal_data_exporters', 'wporg_register_user_data_exporters' );
这就是全部!您的插件现在将为导出提供数据!
个人数据删除
将个人数据擦除器添加到您的插件中
在 WordPress 4.9.6 中,添加了新工具,以便更轻松地遵守欧盟《通用数据保护条例》(简称 GDPR)等法律。添加的工具之一是个人数据删除工具,它支持删除/匿名化特定用户的个人数据。它不会删除注册的用户帐户 - 这仍然是管理员可以选择是否执行的单独步骤。
除了存储在 WordPress 评论等内容中的个人数据之外,插件还可以连接到橡皮擦功能来删除他们收集的个人数据,无论是在 postmeta 之类的内容中,还是在全新的自定义帖子类型 (CPT) 中。
与导出器一样,所有擦除器的“关键”是用户的电子邮件地址 - 选择此地址是因为它支持擦除正式注册用户和未注册用户(例如,注销的评论者)的个人数据。
但是,由于执行个人数据删除是一个破坏性的过程,我们不想在没有确认请求的情况下就这样做,因此面向管理员的用户界面通过让管理员输入提出请求的用户名或电子邮件地址来启动所有请求然后发送一个链接,单击以确认他们的请求。一旦请求得到确认,管理员就可以开始删除用户的个人数据,或者在需要时强制删除。
擦除个人数据导出的方式与个人数据导出器的方式类似,并且依赖挂钩“擦除器”回调来完成擦除数据的肮脏工作。当管理员单击“删除个人数据”链接时,AJAX 循环就会开始,一次一个地迭代系统中注册的所有橡皮擦。除了核心内置的橡皮擦之外,插件还可以注册自己的橡皮擦回调。
橡皮擦回调接口设计得尽可能简单。橡皮擦回调接收我们正在使用的电子邮件地址以及页面参数。page 参数(从 1 开始)用于避免插件尝试一次性删除其收集的所有个人数据而可能导致超时。一个表现良好的插件会限制它尝试删除每页的数据量(例如 100 个帖子、200 个评论等)
橡皮擦回调会回复是否删除包含个人数据的项目、是否保留任何包含个人数据的项目、向管理员呈现的一系列消息(解释保留项目的原因)以及是否完成。如果橡皮擦回调报告尚未完成,则会再次调用它(在单独的请求中),并且页面参数递增 1。
当所有导出程序完成后,管理用户界面将更新,以显示找到的所有个人数据是否已删除,以及解释为何保留个人数据的任何消息。
让我们研究一个假设的插件,它将评论者位置数据添加到评论中。假设该插件使用ys of和add_comment_meta
添加位置数据meta_ke
latitude
longitude
该插件需要做的第一件事是创建一个接受电子邮件地址和页面的橡皮擦函数,例如:
/**
* Removes any stored location data from a user's comment meta for the supplied email address.
*
* @param string $email_address email address to manipulate
* @param int $page pagination
*
* @return array
*/
function wporg_remove_location_meta_from_comments_for_email( $email_address, $page = 1 ) {
$number = 500; // Limit us to avoid timing out
$page = (int) $page;
$comments = get_comments(
array(
'author_email' => $email_address,
'number' => $number,
'paged' => $page,
'order_by' => 'comment_ID',
'order' => 'ASC',
)
);
$items_removed = false;
foreach ( (array) $comments as $comment ) {
$latitude = get_comment_meta( $comment->comment_ID, 'latitude', true );
$longitude = get_comment_meta( $comment->comment_ID, 'longitude', true );
if ( ! empty( $latitude ) ) {
delete_comment_meta( $comment->comment_ID, 'latitude' );
$items_removed = true;
}
if ( ! empty( $longitude ) ) {
delete_comment_meta( $comment->comment_ID, 'longitude' );
$items_removed = true;
}
}
// Tell core if we have more comments to work on still
$done = count( $comments ) < $number;
return array(
'items_removed' => $items_removed,
'items_retained' => false, // always false in this example
'messages' => array(), // no messages in this example
'done' => $done,
);
}
插件需要做的下一件事是通过使用“wp_privacy_personal_data_erasers”过滤器过滤橡皮擦数组来注册回调
。
注册时,您为橡皮擦提供一个友好的名称(以帮助调试 - 这个友好的名称此时不会向任何人显示)
和回调,例如
/**
* Registers all data erasers.
*
* @param array $exporters
*
* @return mixed
*/
function wporg_register_privacy_erasers( $erasers ) {
$erasers['my-plugin-slug'] = array(
'eraser_friendly_name' => __( 'Comment Location Plugin', 'text-domain' ),
'callback' => 'wporg_remove_location_meta_from_comments_for_email',
);
return $erasers;
}
add_filter( 'wp_privacy_personal_data_erasers', 'wporg_register_privacy_erasers' );
这就是全部!您的插件现在将清理其个人数据!
高级主题
隐私相关选项、挂钩和功能
隐私工具最初是在 WordPress 4.9.6 中引入的。这些工具旨在允许(并鼓励)开发人员将它们用作隐私导出器、隐私擦除器和隐私政策指南的一部分。
从那时起,引入了几个更新的挂钩来扩展可用功能。这些挂钩允许开发人员在导出和删除请求中包含额外的个人数据,并引入隐私政策指南的建议内容。
除了控制这些工具的能力之外,还有几个用于请求和确认电子邮件的新过滤器,可以对这些通知进行更细粒度的控制。
选项
wp_page_for_privacy_policy
– 包含站点隐私页面的页面 ID
行动
user_request_action_confirmed
– 当用户确认隐私请求时触发
wp_privacy_delete_old_export_files
– 用于从个人数据导出文件夹中删除旧导出的预定操作
wp_privacy_personal_data_erased
– 最后一个橡皮擦的最后一页完成后触发
wp_privacy_personal_data_export_file
– 用于创建个人数据导出文件作为导出流程的一部分
wp_privacy_personal_data_export_file_created
– 创建个人数据导出文件后触发
过滤器
privacy_policy_url
– 过滤隐私政策页面的 URL。
the_privacy_policy_link
– 过滤隐私政策页面链接 HTML。
wp_get_default_privacy_policy_content
– 过滤隐私政策指南中建议包含的默认内容。
user_request_action_confirmed_message
– 允许修改向用户显示的操作确认消息
user_request_action_description
– 过滤用户操作描述。
user_request_action_email_content
– 过滤尝试帐户操作时发送的电子邮件文本。
user_request_action_email_headers
– 过滤尝试帐户操作时发送的电子邮件的标头。
user_request_action_email_subject
– 过滤尝试帐户操作时发送的电子邮件的主题。
user_request_confirmed_email_content
– 过滤用户请求确认电子邮件的正文。
user_request_confirmed_email_headers
– 过滤用户请求确认电子邮件的标头。
user_request_confirmed_email_subject
– 过滤用户请求确认电子邮件的主题。
user_request_confirmed_email_to
– 过滤数据请求确认通知的接收者。
user_request_key_expiration
– 过滤用户请求的确认密钥的过期时间。
wp_privacy_additional_user_profile_data
– 过滤器以扩展隐私导出器的用户配置文件数据。
wp_privacy_export_expiration
– 控制允许获取多长时间的导出文件,默认为 3 天
wp_privacy_personal_data_email_content
– 允许修改发送给用户的电子邮件及其个人数据导出文件链接
wp_privacy_personal_data_email_headers
– 过滤与个人数据导出文件一起发送的电子邮件的标题。
wp_privacy_personal_data_email_subject
– 过滤导出请求完成时发送的电子邮件的主题。
wp_privacy_personal_data_email_to
– 过滤个人数据导出电子邮件通知的收件人。
wp_privacy_personal_data_email_to
应非常谨慎使用,以避免将数据导出链接发送到错误的收件人电子邮件地址。wp_privacy_personal_data_erasers
– 支持核心和插件个人数据擦除器的注册
wp_privacy_personal_data_erasure_page
– 过滤一页个人数据擦除数据。允许除 Ajax 之外的目的地使用擦除响应。
wp_privacy_personal_data_exporters
– 支持核心和插件个人数据导出器的注册
wp_privacy_personal_data_export_page
– 过滤个人数据导出者数据的页面。用于构建导出报告。允许导出响应由 Ajax 之外的目标使用。
wp_privacy_anonymize_data
– 过滤每种类型的匿名数据。
wp_privacy_exports_dir
– 过滤用于存储个人数据导出文件的目录。
wp_privacy_exports_url
– 过滤用于存储个人数据导出文件的目录的 URL。
user_confirmed_action_email_content
– 过滤用户请求确认电子邮件的正文。确认用户请求后,电子邮件将发送给管理员。
user_erasure_fulfillment_email_to
– 过滤数据删除完成通知的收件人。
user_erasure_complete_email_subject
– 过滤删除请求完成后发送的电子邮件的主题。
user_confirmed_action_email_content
– 过滤数据删除完成通知的正文。当管理员满足用户的数据删除请求时,将向用户发送电子邮件。
user_erasure_complete_email_headers
– 过滤数据擦除完成通知的标头。
能力
对隐私工具的访问由一些新功能控制。默认情况下,管理员(在非多站点安装上)具有这些功能。这些能力是:
erase_others_personal_data
– 确定“工具”下是否提供“删除个人数据”子菜单
export_others_personal_data
– 确定“工具”下是否提供“导出个人数据”子菜单
manage_privacy_options
– 确定“设置”下的“隐私”子菜单是否可用
管理菜单
管理菜单
管理菜单
管理菜单是 WordPress 管理中显示的界面。它们允许您为插件添加选项页面。
顶级菜单和子菜单
顶级菜单沿着 WordPress 管理的左侧呈现。每个菜单可能包含一组子菜单。
在顶级菜单和子菜单之间做出决定时,请仔细考虑插件的需求以及最终用户的需求。
警报:我们建议具有单个选项页面的开发人员将其作为子菜单添加到现有的顶级菜单之一;例如设置或工具。
顶级菜单
添加顶级菜单
要将新的顶级菜单添加到 WordPress 管理,请使用add_menu_page() 函数。
add_menu_page(
string $page_title,
string $menu_title,
string $capability,
string $menu_slug,
callable $function = '',
string $icon_url = '',
int $position = null
);
例子
假设我们要添加一个名为“WPOrg”的新顶级菜单。
第一步是创建一个输出 HTML 的函数。在此函数中,我们将执行必要的安全检查并呈现我们使用Settings API注册的选项。
wrap
.第二步是注册我们的 WPOorg 菜单。注册需要在admin_menu
操作挂钩期间进行。
add_action( 'admin_menu', 'wporg_options_page' );
function wporg_options_page() {
add_menu_page(
'WPOrg',
'WPOrg Options',
'manage_options',
'wporg',
'wporg_options_page_html',
plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
20
);
}
使用 PHP 文件作为 HTML
PHP file path
作为参数。$menu_slug
null
$function
add_action( 'admin_menu', 'wporg_options_page' );
function wporg_options_page() {
add_menu_page(
'WPOrg',
'WPOrg Options',
'manage_options',
plugin_dir_path(__FILE__) . 'admin/view.php',
null,
plugin_dir_url(__FILE__) . 'images/icon_wporg.png',
20
);
}
删除顶级菜单
remove_menu_page(
string $menu_slug
);
例子
假设我们要从中删除“工具”菜单。
add_action( 'admin_menu', 'wporg_remove_options_page', 99 );
function wporg_remove_options_page() {
remove_menu_page( 'tools.php' );
}
提交表格
要处理选项页面上表单的提交,您需要做两件事:
- 使用页面的 URL 作为
action
表单的属性。 - 添加一个带有 slug 的钩子,由 . 返回
add_menu_page
。
笔记:如果您在后端手动创建表单,则只需执行这些步骤。设置API是执行此操作的推荐方法。
表单动作属性
<form action="<?php menu_page_url( 'wporg' ) ?>" method="post">
子菜单
添加子菜单
要向 WordPress 管理添加新的子菜单,请使用该add_submenu_page()
功能。
add_submenu_page(
string $parent_slug,
string $page_title,
string $menu_title,
string $capability,
string $menu_slug,
callable $function = ''
);
例子
假设我们要向“工具”顶级菜单添加一个子菜单“WPOrg Options”。
第一步是创建一个输出 HTML 的函数。在此函数中,我们将执行必要的安全检查并呈现我们使用Settings API注册的选项。
wrap
.function wporg_options_page_html() {
// check user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
?>
第二步是注册我们的 WPOorg 选项子菜单。注册需要在admin_menu
操作挂钩期间进行。
function wporg_options_page()
{
add_submenu_page(
'tools.php',
'WPOrg Options',
'WPOrg Options',
'manage_options',
'wporg',
'wporg_options_page_html'
);
}
add_action('admin_menu', 'wporg_options_page');
有关参数列表以及每个参数的作用,请参阅 参考文献中的add_submenu_page() 。
预定义子菜单
$parent_slug
如果我们有帮助函数来定义WordPress 内置顶级菜单并使我们免于通过源代码手动搜索它,那不是很好吗?
下面是父 slugs 及其辅助函数的列表:
- add_dashboard_page() –
index.php
- add_posts_page() –
edit.php
- add_media_page() –
upload.php
- add_pages_page() –
edit.php?post_type=page
- add_comments_page() –
edit-comments.php
- add_theme_page() –
themes.php
- add_plugins_page() –
plugins.php
- add_users_page() –
users.php
- add_management_page() –
tools.php
- add_options_page() –
options-general.php
- add_options_page() –
settings.php
- add_links_page() –
link-manager.php
– 从 WP 3.5+ 开始需要插件 - 自定义帖子类型 –
edit.php?post_type=wporg_post_type
- 网络管理员 –
settings.php
删除子菜单
删除子菜单的过程与删除顶级菜单的过程完全相同。
提交表格
在子菜单中处理表单提交的过程与在顶级菜单中提交表单完全相同。
function wporg_options_page() {
$hookname = add_submenu_page(
'tools.php',
'WPOrg Options',
'WPOrg Options',
'manage_options',
'wporg',
'wporg_options_page_html'
);
add_action( 'load-' . $hookname, 'wporg_options_page_html_submit' );
}
add_action('admin_menu', 'wporg_options_page');
一如既往,不要忘记检查表单是否正在提交,进行 CSRF 验证、验证和清理。
简码
简码
作为安全预防措施,禁止在 WordPress 内容中运行 PHP;为了允许与内容进行动态交互,WordPress 2.5 版本中引入了简码。
短代码是可用于与内容执行动态交互的宏。即从帖子中附加的图像创建图库或渲染视频。
为什么使用简码?
短代码是保持内容简洁和语义的一种有价值的方式,同时允许最终用户以编程方式改变其内容的呈现方式。
当最终用户使用短代码将照片库添加到他们的帖子中时,他们会使用尽可能少的数据来指示如何呈现图库。
优点:
- 帖子内容中不会添加任何标记,这意味着标记和样式可以轻松地即时或稍后进行操作。
- 短代码还可以接受参数,允许用户根据实例修改短代码的行为方式。
内置简码
默认情况下,WordPress 包含以下短代码:
[caption]
– 允许您在内容周围添加字幕[gallery]
– 允许您显示图片库[audio]
– 允许您嵌入和播放音频文件[video]
– 允许您嵌入和播放视频文件[playlist]
– 允许您显示音频或视频文件的集合[embed]
– 允许您包裹嵌入的物品
简码最佳实践
开发短代码的最佳实践包括插件开发最佳实践和以下列表:
- 总是回来!
短代码本质上是过滤器,因此创建“副作用”将导致意想不到的错误。 - 为您的短代码名称添加前缀,以避免与其他插件发生冲突。
- 清理输入并转义输出。
- 为用户提供有关所有短代码属性的清晰文档。
快速参考
请参阅使用基本短代码结构、处理自关闭和封闭场景、短代码中的短代码以及保护输出的完整示例。
外部资源
基本简码
添加简码
可以使用短代码 API 添加您自己的短代码。该过程涉及使用注册$func
对短代码的回调。$tag
add_shortcode()
add_shortcode(
string $tag,
callable $func
);
[wporg]
是您的新简码。使用短代码将触发wporg_shortcode
回调函数。
add_shortcode('wporg', 'wporg_shortcode');
function wporg_shortcode( $atts = [], $content = null) {
// do something to $content
// always return
return $content;
}
删除短代码
可以使用 Shortcode API 删除短代码。$tag
该过程涉及使用remove_shortcode()删除注册。
remove_shortcode(
string $tag
);
在尝试删除之前,请确保已注册短代码。为add_action()指定更高的优先级编号 或挂钩到稍后运行的操作挂钩。
检查短代码是否存在
要检查短代码是否已注册,请使用shortcode_exists()
.
附上简码
使用短代码有两种场景:
- 短代码是一个自闭合标签,就像我们在基本短代码部分中看到的那样。
- 短代码包含内容。
附上内容
使用短代码封装内容允许对封装的内容进行操作。
[wporg]content to manipulate[/wporg]
如上所示,为了包含一段内容,您所需要做的就是添加开始[$tag]
和结束[/$tag]
,类似于 HTML。
处理所附内容
让我们回到原来的 [wporg] 短代码:
function wporg_shortcode( $atts = array(), $content = null ) {
// do something to $content
// always return
return $content;
}
add_shortcode( 'wporg', 'wporg_shortcode' );
查看回调函数,我们发现我们选择接受两个参数,$atts
和$content
。该$content
参数将保存我们所包含的内容。我们稍后再谈$atts
。
默认值$content
设置为,null
以便我们可以使用 PHP 函数is_null()来区分自闭合标签和封闭标签。
短代码[$tag]
(包括其内容和结尾)[/$tag]
将替换为处理函数的返回值。
简码接收
短代码解析器对帖子的内容执行一次传递。
这意味着如果$content
短代码处理程序的参数包含另一个短代码,则不会对其进行解析。在本例中,[shortcode]
将不会被处理:
[wporg]another [shortcode] is included[/wporg]
通过调用处理函数的do_shortcode()
最终返回值,可以在其他短代码中使用短代码。
function wporg_shortcode( $atts = array(), $content = null ) {
// do something to $content
// run shortcode parser recursively
$content = do_shortcode( $content );
// always return
return $content;
}
add_shortcode( 'wporg', 'wporg_shortcode' );
局限性
短代码解析器无法处理相同 的封闭和非封闭形式的混合[$tag]
。
[wporg] non-enclosed content [wporg]enclosed content[/wporg]
non-enclosed content
解析器不会将其视为由文本“”分隔的两个短代码,而是将其视为包含“ non-enclosed content [wporg]enclosed content
”的单个短代码。
带参数的简码
现在我们知道如何创建基本的短代码以及如何将其用作自闭合和封闭,我们将了解在短代码[$tag]
和处理函数中使用参数。
短代码[$tag]
可以接受参数,称为属性:
[wporg title="WordPress.org"]
Having fun with WordPress.org shortcodes.
[/wporg]
简码处理函数可以接受 3 个参数:
$atts
– 数组 – [$tag] 属性$content
– 字符串 – 短代码中的内容。在上面的示例中,它将是“享受 WordPress.org 短代码的乐趣”。$tag
– string – [$tag] 的名称(即短代码的名称)
function wporg_shortcode( $atts = array(), $content = null, $tag = '' ) {}
解析属性
对于用户来说,短代码只是帖子内容中带有方括号的字符串。用户不知道哪些属性可用以及幕后发生了什么。
对于插件开发人员来说,无法强制执行属性使用策略。用户可以包括一个属性、两个属性或根本没有属性。
要控制短代码的使用方式:
- 声明处理函数的默认参数
- 使用array_change_key_case()对属性数组的键值进行标准化
- 使用提供默认值数组和用户的Shortcode_atts()解析属性
$atts
- 返回之前保护输出
完整示例
使用基本的短代码结构的完整示例,处理自关闭和封闭场景并保护输出。
一个[wporg]
短代码,它将接受标题并显示一个我们可以使用 CSS 设计样式的框。
/**
* The [wporg] shortcode.
*
* Accepts a title and will display a box.
*
* @param array $atts Shortcode attributes. Default empty.
* @param string $content Shortcode content. Default null.
* @param string $tag Shortcode tag (name). Default empty.
* @return string Shortcode output.
*/
function wporg_shortcode( $atts = [], $content = null, $tag = '' ) {
// normalize attribute keys, lowercase
$atts = array_change_key_case( (array) $atts, CASE_LOWER );
// override default attributes with user attributes
$wporg_atts = shortcode_atts(
array(
'title' => 'WordPress.org',
), $atts, $tag
);
// start box
$o = '<div class="wporg-box">';
// title
$o .= '<h2>' . esc_html( $wporg_atts['title'] ) . '</h2>';
// enclosing tags
if ( ! is_null( $content ) ) {
// $content here holds everything in between the opening and the closing tags of your shortcode. eg.g [my-shortcode]content[/my-shortcode].
// Depending on what your shortcode supports, you will parse and append the content to your output in different ways.
// In this example, we just secure output by executing the_content filter hook on $content.
$o .= apply_filters( 'the_content', $content );
}
// end box
$o .= '</div>';
// return output
return $o;
}
/**
* Central location to create all shortcodes.
*/
function wporg_shortcodes_init() {
add_shortcode( 'wporg', 'wporg_shortcode' );
}
add_action( 'init', 'wporg_shortcodes_init' );
TinyMCE 增强短代码
可以在 TinyMCE 的可视化编辑器中解析短代码并使它们呈现实际内容,而不是短代码本身。
切换到该Text
选项卡可以让您再次看到实际的短代码。
以下是使用此功能的内置 WordPress 短代码。
音频简码
短[audio]
代码允许您嵌入单个音频文件。

标题简码
短[caption]
代码将图像包装在 div 中,并
在标题周围放置标签。

画廊简码
短[gallery]
代码允许您一次在 div 中嵌入多个图像。

播放列表简码
该[playlist]
短代码允许您附加多个媒体文件并使用 html5 播放列表进行渲染。

视频简码
短[video]
代码与短代码非常相似[audio]
,它只是渲染视频而不是音频。

设置
设置
WordPress 提供了两个核心 API,使管理界面易于构建、安全并与 WordPress 管理的设计保持一致。
设置API专注于为开发人员提供一种创建表单和管理表单数据的方法。
Options API专注于使用简单的键/值系统管理数据。
快速参考
请参阅使用设置 API 和选项 API构建自定义设置页面的完整示例。
设置接口
WordPress 2.7 中添加的设置 API 允许半自动管理包含设置表单的管理页面。它允许您定义设置页面、这些页面中的部分以及这些部分中的字段。
新的设置页面可以连同其中的部分和字段一起注册。还可以通过在其中注册新的设置部分或字段来添加现有设置页面。
组织字段的注册和验证仍然需要开发人员付出一些努力,但避免了底层选项管理的大量复杂调试。
wp-admin/options.php
提供相当严格的功能检查。用户需要具备manage_options
提交表单的能力(在多站点中必须是超级管理员)。为什么要使用设置 API?
开发人员可以忽略此 API,并在没有它的情况下编写自己的设置页面。这就引出了一个问题,这个 API 给桌面带来了什么好处?以下是一些好处的简要概述。
视觉一致性
使用 API 生成界面元素可确保您的设置页面看起来与其他管理内容相似。您的界面将遵循相同的样式指南,并且看起来像它所属的,并且感谢才华横溢的 WordPress 设计师团队,它看起来会很棒!
稳健性(面向未来!)
由于 API 是 WordPress 核心的一部分,任何更新都会自动考虑您插件的设置页面。如果您在不使用设置 API 的情况下创建自己的界面,WordPress 核心更新更有可能破坏您的自定义设置。还有更广泛的受众测试和维护该 API 代码,因此它往往会更加稳定。
减少工作量!
当然,最直接的好处是 WordPress API 在幕后为您做了很多工作。除了应用美观的集成设计之外,这里还有一些设置 API 的功能示例。
- 处理表单提交 –让 WordPress 处理检索和存储您的 $_POST 提交。
- 包括安全措施 –您免费获得额外的安全措施,例如随机数等。
- 清理数据 –您可以使用 WordPress 其余部分使用的相同方法来确保字符串可以安全使用。
功能参考
设置注册/取消注册 | 添加字段/部分 |
---|---|
注册设置() 取消注册设置() |
add_settings_section() add_settings_field() |
选项表单渲染 | 错误 |
---|---|
设置字段() do_settings_sections() do_settings_fields() |
add_settings_error() get_settings_errors() settings_errors() |
使用设置 API
添加设置
您必须使用register_setting()定义新设置,它将在{$wpdb->prefix}_options
表中创建一个条目。
您可以使用add_settings_section()在现有页面上添加新部分。
您可以使用add_settings_field()将新字段添加到现有部分。
添加设置
register_setting(
string $option_group,
string $option_name,
callable $sanitize_callback = ''
);
请参阅有关register_setting()的函数参考 以获取有关所使用参数的完整说明。
添加一个部分
add_settings_section(
string $id,
string $title,
callable $callback,
string $page
);
部分是您在 WordPress 设置页面上看到的具有共享标题的设置组。在您的插件中,您可以向现有设置页面添加新部分,而不是创建一个全新的页面。这使得您的插件更易于维护,并创建更少的新页面供用户学习。
有关所用参数的完整说明,请参阅有关add_settings_section()的函数参考。
添加字段
add_settings_field(
string $id,
string $title,
callable $callback,
string $page,
string $section = 'default',
array $args = []
);
有关所用参数的完整说明,请参阅有关add_settings_field()的函数参考。
例子
function wporg_settings_init() {
// register a new setting for "reading" page
register_setting('reading', 'wporg_setting_name');
// register a new section in the "reading" page
add_settings_section(
'wporg_settings_section',
'WPOrg Settings Section', 'wporg_settings_section_callback',
'reading'
);
// register a new field in the "wporg_settings_section" section, inside the "reading" page
add_settings_field(
'wporg_settings_field',
'WPOrg Setting', 'wporg_settings_field_callback',
'reading',
'wporg_settings_section'
);
}
/**
* register wporg_settings_init to the admin_init action hook
*/
add_action('admin_init', 'wporg_settings_init');
/**
* callback functions
*/
// section content cb
function wporg_settings_section_callback() {
echo '<p>WPOrg Section Introduction.</p>';
}
// field content cb
function wporg_settings_field_callback() {
// get the value of the setting we've registered with register_setting()
$setting = get_option('wporg_setting_name');
// output the field
?>
<input type="text" name="wporg_setting_name" value="<?php echo isset( $setting ) ? esc_attr( $setting ) : ''; ?>">
<?php
}
获取设置
获取设置是通过get_option() 函数完成的。
该函数接受两个参数:选项的名称和该选项的可选默认值。
例子
// Get the value of the setting we've registered with register_setting()
$setting = get_option('wporg_setting_name');
选项 API
WordPress 1.0 中添加的选项 API 允许创建、读取、更新和删除 WordPress 选项。与设置 API结合使用,它可以控制设置页面中定义的选项。
选项存储在哪里?
选项存储在{$wpdb->prefix}_options
表中。由文件中设置的变量$wpdb->prefix
定义。$table_prefix
wp-config.php
选项如何存储?
选项可以通过以下两种方式之一存储在 WordPress 数据库中:作为单个值或作为值数组。
单值
当保存为单个值时,选项名称指的是单个值。
// add a new option
add_option('wporg_custom_option', 'hello world!');
// get an option
$option = get_option('wporg_custom_option');
值数组
当保存为值数组时,选项名称指的是一个数组,该数组本身可以由键/值对组成。
// array of options
$data_r = array('title' => 'hello world!', 1, false );
// add a new option
add_option('wporg_custom_option', $data_r);
// get an option
$options_r = get_option('wporg_custom_option');
// output the title
echo esc_html($options_r['title']);
如果您正在使用大量相关选项,将它们存储为数组可以对整体性能产生积极影响。
功能参考
添加选项 | 获取选项 | 更新选项 | 删除选项 |
---|---|---|---|
add_option() | get_option() | update_option() | delete_option() |
add_site_option() | get_site_option() | update_site_option() | delete_site_option() |
自定义设置页面
创建自定义设置页面包括以下组合:创建管理菜单、使用设置 API和选项 API。
通过遵循注释,可以使用下面的示例来快速参考这些主题。
完整示例
完整的示例添加了一个名为 的顶级菜单WPOrg
,注册了一个名为 的自定义选项wporg_options
,并使用设置 API 和选项 API(包括显示错误/更新消息)执行 CRUD(创建、读取、更新、删除)逻辑。
/**
* @internal never define functions inside callbacks.
* these functions could be run multiple times; this would result in a fatal error.
*/
/**
* custom option and settings
*/
function wporg_settings_init() {
// Register a new setting for "wporg" page.
register_setting( 'wporg', 'wporg_options' );
// Register a new section in the "wporg" page.
add_settings_section(
'wporg_section_developers',
__( 'The Matrix has you.', 'wporg' ), 'wporg_section_developers_callback',
'wporg'
);
// Register a new field in the "wporg_section_developers" section, inside the "wporg" page.
add_settings_field(
'wporg_field_pill', // As of WP 4.6 this value is used only internally.
// Use $args' label_for to populate the id inside the callback.
__( 'Pill', 'wporg' ),
'wporg_field_pill_cb',
'wporg',
'wporg_section_developers',
array(
'label_for' => 'wporg_field_pill',
'class' => 'wporg_row',
'wporg_custom_data' => 'custom',
)
);
}
/**
* Register our wporg_settings_init to the admin_init action hook.
*/
add_action( 'admin_init', 'wporg_settings_init' );
/**
* Custom option and settings:
* - callback functions
*/
/**
* Developers section callback function.
*
* @param array $args The settings array, defining title, id, callback.
*/
function wporg_section_developers_callback( $args ) {
?>
/**
* @internal never define functions inside callbacks.
* these functions could be run multiple times; this would result in a fatal error.
*/
/**
* custom option and settings
*/
function wporg_settings_init() {
// Register a new setting for "wporg" page.
register_setting( 'wporg', 'wporg_options' );
// Register a new section in the "wporg" page.
add_settings_section(
'wporg_section_developers',
__( 'The Matrix has you.', 'wporg' ), 'wporg_section_developers_callback',
'wporg'
);
// Register a new field in the "wporg_section_developers" section, inside the "wporg" page.
add_settings_field(
'wporg_field_pill', // As of WP 4.6 this value is used only internally.
// Use $args' label_for to populate the id inside the callback.
__( 'Pill', 'wporg' ),
'wporg_field_pill_cb',
'wporg',
'wporg_section_developers',
array(
'label_for' => 'wporg_field_pill',
'class' => 'wporg_row',
'wporg_custom_data' => 'custom',
)
);
}
/**
* Register our wporg_settings_init to the admin_init action hook.
*/
add_action( 'admin_init', 'wporg_settings_init' );
/**
* Custom option and settings:
* - callback functions
*/
/**
* Developers section callback function.
*
* @param array $args The settings array, defining title, id, callback.
*/
function wporg_section_developers_callback( $args ) {
?>
<p id="<?php echo esc_attr( $args['id'] ); ?>"><?php esc_html_e( 'Follow the white rabbit.', 'wporg' ); ?></p>
<?php
}
/**
* Pill field callbakc function.
*
* WordPress has magic interaction with the following keys: label_for, class.
* - the "label_for" key value is used for the "for" attribute of the <label>.
* - the "class" key value is used for the "class" attribute of the <tr> containing the field.
* Note: you can add custom key value pairs to be used inside your callbacks.
*
* @param array $args
*/
function wporg_field_pill_cb( $args ) {
// Get the value of the setting we've registered with register_setting()
$options = get_option( 'wporg_options' );
?>
<select
id="<?php echo esc_attr( $args['label_for'] ); ?>"
data-custom="<?php echo esc_attr( $args['wporg_custom_data'] ); ?>"
name="wporg_options[<?php echo esc_attr( $args['label_for'] ); ?>]">
<option value="red" <?php echo isset( $options[ $args['label_for'] ] ) ? ( selected( $options[ $args['label_for'] ], 'red', false ) ) : ( '' ); ?>>
<?php esc_html_e( 'red pill', 'wporg' ); ?>
</option>
<option value="blue" <?php echo isset( $options[ $args['label_for'] ] ) ? ( selected( $options[ $args['label_for'] ], 'blue', false ) ) : ( '' ); ?>>
<?php esc_html_e( 'blue pill', 'wporg' ); ?>
</option>
</select>
<p class="description">
<?php esc_html_e( 'You take the blue pill and the story ends. You wake in your bed and you believe whatever you want to believe.', 'wporg' ); ?>
</p>
<p class="description">
<?php esc_html_e( 'You take the red pill and you stay in Wonderland and I show you how deep the rabbit-hole goes.', 'wporg' ); ?>
</p>
<?php
}
/**
* Add the top level menu page.
*/
function wporg_options_page() {
add_menu_page(
'WPOrg',
'WPOrg Options',
'manage_options',
'wporg',
'wporg_options_page_html'
);
}
/**
* Register our wporg_options_page to the admin_menu action hook.
*/
add_action( 'admin_menu', 'wporg_options_page' );
/**
* Top level menu callback function
*/
function wporg_options_page_html() {
// check user capabilities
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// add error/update messages
// check if the user have submitted the settings
// WordPress will add the "settings-updated" $_GET parameter to the url
if ( isset( $_GET['settings-updated'] ) ) {
// add settings saved message with the class of "updated"
add_settings_error( 'wporg_messages', 'wporg_message', __( 'Settings Saved', 'wporg' ), 'updated' );
}
// show error/update messages
settings_errors( 'wporg_messages' );
?>
<div class="wrap">
<h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
<form action="options.php" method="post">
<?php
// output security fields for the registered setting "wporg"
settings_fields( 'wporg' );
// output setting sections and their fields
// (sections are registered for "wporg", each field is registered to a specific section)
do_settings_sections( 'wporg' );
// output save settings button
submit_button( 'Save Settings' );
?>
</form>
</div>
<?php
}
Metadata元数据
meta数据
meta数据是关于信息的信息。对于 WordPress,它是与帖子、用户、评论和术语相关的信息。
鉴于 WordPress 中元数据的多对一关系,您的选择相当无限。您可以拥有任意数量的元选项,并且可以在其中存储几乎任何内容。
本章将讨论管理帖子元数据、创建自定义元框以及呈现帖子元数据。
管理帖子meta数据
添加元数据
使用add_post_meta()可以非常轻松地添加元数据。该函数接受 a post_id
、 a meta_key
、 ameta_value
和一个unique
标志。
这meta_key
就是您的插件在代码中其他地方引用元值的方式。类似的东西mycrazymetakeyname
可以工作,但是与您的插件或主题相关的前缀后面跟着密钥的描述会更有用。wporg_featured_menu
可能是一个很好的。应该注意的是,meta_key
可以多次使用相同的元数据来存储元数据的变体(参见下面的唯一标志)。
可以meta_value
是字符串、整数或数组。如果是数组,在存入数据库之前会自动序列化。
该unique
标志允许您声明该键是否应该是唯一的。非唯一键是指帖子可以有多种变体,例如价格。
如果您只想要一个帖子的一个价格,您应该标记它unique
,并且该帖子meta_key
将只有一个值。
更新元数据
如果密钥已存在并且您想要更新它,请使用update_post_meta()。如果您使用此函数并且密钥不存在,那么它将创建它,就像您使用了add_post_meta()一样。
与add_post_meta()类似,该函数接受 a post_id
、 ameta_key
和meta_value
。它还接受一个可选参数prev_value
- 如果指定,将导致该函数仅使用该值更新现有元数据条目。如果未提供,该函数默认更新所有条目。
删除元数据
delete_post_meta() 接受 a post_id
、 ameta_key
和可选的meta_value
。它的作用正如其名称所暗示的那样。
字符转义
后元值在存储时通过stripslashes()函数传递,因此在传递可能包含转义字符的值(例如 JSON)时需要小心。
考虑 JSON 值{"key":"value with \"escaped quotes\""}
:
$escaped_json = '{"key":"value with \"escaped quotes\""}';
update_post_meta( $id, 'escaped_json', $escaped_json );
$broken = get_post_meta( $id, 'escaped_json', true );
/*
$broken, after stripslashes(), ends up unparsable:
{"key":"value with "escaped quotes""}
*/
解决方法
通过使用函数wp_slash() (在 WP 3.6 中引入)添加一级转义,您可以补偿对stripslashes() 的调用:
$escaped_json = '{"key":"value with \"escaped quotes\""}';
update_post_meta( $id, 'double_escaped_json', wp_slash( $escaped_json ) );
$fixed = get_post_meta( $id, 'double_escaped_json', true );
/*
$fixed, after stripslashes(), ends up as desired:
{"key":"value with \"escaped quotes\""}
*/
隐藏的自定义字段
meta_key
如果您是插件或主题开发人员,并且计划使用自定义字段来存储参数,请务必注意,WordPress 不会在自定义字段列表中显示以“_”(下划线)开头的自定义字段。发布编辑屏幕或使用the_meta() 模板函数时。
这对于使用add_meta_box() 函数以不寻常的方式显示这些自定义字段非常有用。
下面的示例将添加一个名为meta_key
“_color”和meta_value
“red”的唯一自定义字段,但此自定义字段不会显示在帖子编辑屏幕中:
add_post_meta( 68, '_color', 'red', true );
隐藏数组
另外,如果是一个数组,即使您没有在名称前添加下划线meta_value
,它也不会显示在页面编辑屏幕上。meta_key
自定义meta元数据框
什么是元框?
当用户编辑帖子时,编辑屏幕由几个默认框组成:编辑器、发布、类别、标签等。这些框是元框。插件可以将自定义元框添加到任何帖子类型的编辑屏幕。
自定义元框的内容通常是 HTML 表单元素,用户在其中输入与插件用途相关的数据,但内容实际上可以是您想要的任何 HTML。
为什么使用元框?
元框是方便、灵活、模块化的编辑屏幕元素,可用于收集与正在编辑的帖子相关的信息。您的自定义元框将与所有其他帖子相关信息位于同一屏幕上;这样就建立了明确的关系。
元框可以轻松地对不需要查看的用户隐藏,并显示给需要查看的用户。用户可以在编辑屏幕上排列元框。用户可以自由地以适合自己的方式排列编辑屏幕,从而使用户可以控制自己的编辑环境。
添加元框
要创建元框,请使用add_meta_box() 函数并将其执行插入到add_meta_boxes
操作挂钩中。
以下示例是向post
编辑屏幕和wporg_cpt
编辑屏幕添加元框。
function wporg_add_custom_box() {
$screens = [ 'post', 'wporg_cpt' ];
foreach ( $screens as $screen ) {
add_meta_box(
'wporg_box_id', // Unique ID
'Custom Meta Box Title', // Box title
'wporg_custom_box_html', // Content callback, must be of type callable
$screen // Post type
);
}
}
add_action( 'add_meta_boxes', 'wporg_add_custom_box' );
该wporg_custom_box_html
函数将保存元框的 HTML。
以下示例添加表单元素、标签和其他 HTML 元素。
function wporg_custom_box_html( $post ) {
?>
<label for="wporg_field">Description for this field</label>
<select name="wporg_field" id="wporg_field" class="postbox">
<option value="">Select something...</option>
<option value="something">Something</option>
<option value="else">Else</option>
</select>
<?php
}
笔记:请注意,元框中没有提交按钮。POST
元框 HTML 包含在编辑屏幕的表单标签内,当用户单击“发布”或“更新”按钮时,包括元框值在内的所有发布数据都会传输。
此处显示的示例仅包含一个表单字段,即一个下拉列表。您可以根据需要在任何特定元框中创建任意数量的元数据。如果您有很多字段要显示,请考虑使用多个元框,将每个元框中相似的字段分组在一起。这有助于使页面更有条理、更具视觉吸引力。
获取值
要检索保存的用户数据并使用它,您需要从最初保存它的位置获取它。如果它存储在表中,您可以使用get_post_meta()postmeta
获取数据。
以下示例使用基于保存的元框值的预填充数据增强了先前的表单元素。您将在下一节中了解如何保存元框值。
function wporg_custom_box_html( $post ) {
$value = get_post_meta( $post->ID, '_wporg_meta_key', true );
?>
<label for="wporg_field">Description for this field</label>
<select name="wporg_field" id="wporg_field" class="postbox">
<option value="">Select something...</option>
<option value="something" <?php selected( $value, 'something' ); ?>>Something</option>
<option value="else" <?php selected( $value, 'else' ); ?>>Else</option>
</select>
<?php
}
有关selected() 函数的更多信息。
保存价值
保存或更新帖子类型时,会触发多个操作,其中任何操作都可能适合挂钩以保存输入的值。在此示例中,我们使用save_post
操作挂钩,但其他挂钩可能更适合某些情况。请注意,save_post
对于单个更新事件可能会多次触发。相应地构建保存数据的方法。
您可以将输入的数据保存在任何您想要的地方,甚至可以在 WordPress 之外保存。由于您可能正在处理与帖子相关的数据,因此表postmeta
通常是存储数据的好地方。
以下示例将wporg_field
在_wporg_meta_key
元键中保存字段值,该元键是隐藏的。
function wporg_save_postdata( $post_id ) {
if ( array_key_exists( 'wporg_field', $_POST ) ) {
update_post_meta(
$post_id,
'_wporg_meta_key',
$_POST['wporg_field']
);
}
}
add_action( 'save_post', 'wporg_save_postdata' );
在生产代码中,请记住遵循信息框中概述的安全措施!
幕后花絮
您通常不需要关心幕后发生的事情。添加此部分是为了完整性。
当帖子编辑屏幕想要显示添加到其中的所有元框时,它会调用 do_meta_boxes () 函数。该函数循环遍历所有元框并调用callback
与每个元框关联的元框。
在每次调用之间,添加中间标记(例如 div、标题等)。
删除元框
要从编辑屏幕中删除现有元框,请使用remove_meta_box() 函数。传递的参数必须与用于使用add_meta_box()添加元框的参数完全匹配。
要删除默认元框,请检查所使用参数的源代码。默认的add_meta_box() 调用是从wp-includes/edit-form-advanced.php
.
实施变体
到目前为止,我们一直在使用实现元框的程序技术。许多插件开发人员发现需要使用各种其他技术来实现元框。
面向对象编程
使用 OOP 添加元框很容易,并且使您不必担心全局命名空间中的命名冲突。
为了节省内存并更容易实现,以下示例使用具有静态方法的抽象类。
abstract class WPOrg_Meta_Box {
/**
* Set up and add the meta box.
*/
public static function add() {
$screens = [ 'post', 'wporg_cpt' ];
foreach ( $screens as $screen ) {
add_meta_box(
'wporg_box_id', // Unique ID
'Custom Meta Box Title', // Box title
[ self::class, 'html' ], // Content callback, must be of type callable
$screen // Post type
);
}
}
/**
* Save the meta box selections.
*
* @param int $post_id The post ID.
*/
public static function save( int $post_id ) {
if ( array_key_exists( 'wporg_field', $_POST ) ) {
update_post_meta(
$post_id,
'_wporg_meta_key',
$_POST['wporg_field']
);
}
}
/**
* Display the meta box HTML to the user.
*
* @param WP_Post $post Post object.
*/
public static function html( $post ) {
$value = get_post_meta( $post->ID, '_wporg_meta_key', true );
?>
<label for="wporg_field">Description for this field</label>
<select name="wporg_field" id="wporg_field" class="postbox">
<option value="">Select something...</option>
<option value="something" <?php selected( $value, 'something' ); ?>>Something</option>
<option value="else" <?php selected( $value, 'else' ); ?>>Else</option>
</select>
<?php
}
}
add_action( 'add_meta_boxes', [ 'WPOrg_Meta_Box', 'add' ] );
add_action( 'save_post', [ 'WPOrg_Meta_Box', 'save' ] );
Ajax
由于元框的 HTML 元素位于form
编辑屏幕的标签内,因此默认行为是在用户提交页面后$_POST
从超级全局解析元框值。
您可以使用 AJAX 增强默认体验;这允许根据用户输入和行为执行操作;无论他们是否提交了页面。
定义触发器
首先,您必须定义触发器,这可以是链接单击、值更改或任何其他 JavaScript 事件。
在下面的示例中,我们将定义change
为执行 AJAX 请求的触发器。
/*jslint browser: true, plusplus: true */
(function ($, window, document) {
'use strict';
// execute when the DOM is ready
$(document).ready(function () {
// js 'change' event triggered on the wporg_field form field
$('#wporg_field').on('change', function () {
// our code
});
});
}(jQuery, window, document));
客户端代码
接下来,我们需要定义触发器要做什么,换句话说,我们需要编写客户端代码。
在下面的示例中,我们将发出POST
请求,响应将是成功或失败,这将表明 的值wporg_field
是有效的。
/*jslint browser: true, plusplus: true */
(function ($, window, document) {
'use strict';
// execute when the DOM is ready
$(document).ready(function () {
// js 'change' event triggered on the wporg_field form field
$('#wporg_field').on('change', function () {
// jQuery post method, a shorthand for $.ajax with POST
$.post(wporg_meta_box_obj.url, // or ajaxurl
{
action: 'wporg_ajax_change', // POST data, action
wporg_field_value: $('#wporg_field').val(), // POST data, wporg_field_value
post_ID: jQuery('#post_ID').val() // The ID of the post currently being edited
}, function (data) {
// handle response data
if (data === 'success') {
// perform our success logic
} else if (data === 'failure') {
// perform our failure logic
} else {
// do nothing
}
}
);
});
});
}(jQuery, window, document));
wporg_meta_box_obj
我们从下一步将创建的 JavaScript 自定义对象动态获取 WordPress AJAX 文件 URL 。
笔记:如果您的元框仅需要 WordPress AJAX 文件 URL;您可以使用预定义的 JavaScript 变量,而不是创建新的自定义 JavaScript 对象ajaxurl
。
仅在 WordPress 管理中可用。在执行任何逻辑之前确保它不为空。
排队客户端代码
下一步是将我们的代码放入脚本文件中并将其排队到我们的编辑屏幕上。
在下面的示例中,我们将向以下帖子类型的编辑屏幕添加 AJAX 功能:post、wporg_cpt。
脚本文件将驻留在/plugin-name/admin/meta-boxes/js/admin.js
主plugin-name
插件文件夹中,即/plugin-name/plugin.php
调用该函数的文件。
function wporg_meta_box_scripts()
{
// get current admin screen, or null
$screen = get_current_screen();
// verify admin screen object
if (is_object($screen)) {
// enqueue only for specific post types
if (in_array($screen->post_type, ['post', 'wporg_cpt'])) {
// enqueue script
wp_enqueue_script('wporg_meta_box_script', plugin_dir_url(__FILE__) . 'admin/meta-boxes/js/admin.js', ['jquery']);
// localize script, create a custom js object
wp_localize_script(
'wporg_meta_box_script',
'wporg_meta_box_obj',
[
'url' => admin_url('admin-ajax.php'),
]
);
}
}
}
add_action('admin_enqueue_scripts', 'wporg_meta_box_scripts');
服务器端代码
最后一步是编写将处理请求的服务器端代码。
// The piece after `wp_ajax_` matches the action argument being sent in the POST request.
add_action( 'wp_ajax_wporg_ajax_change', 'my_ajax_handler' );
/**
* Handles my AJAX request.
*/
function my_ajax_handler() {
// Handle the ajax request here
if ( array_key_exists( 'wporg_field_value', $_POST ) ) {
$post_id = (int) $_POST['post_ID'];
if ( current_user_can( 'edit_post', $post_id ) ) {
update_post_meta(
$post_id,
'_wporg_meta_key',
$_POST['wporg_field_value']
);
}
}
wp_die(); // All ajax handlers die when finished
}
最后提醒一下,本页所示的代码缺少考虑安全性的重要操作。确保您的生产代码包含此类操作。
有关 AJAX 的更多信息,请参阅手册的 AJAX 章节和Codex 。
更多信息
渲染后meta data元数据
以下是用于获取和呈现帖子元数据的函数和模板标签的非详尽列表:
- the_meta() – 自动列出帖子的所有自定义字段的模板标签
- get_post_custom() 和get_post_meta() – 检索帖子的一个或所有元数据。
- get_post_custom_values() – 检索自定义帖子字段的值。
自定义帖子类型
自定义帖子类型
WordPress 将帖子类型存储在表中,posts
允许开发人员注册自定义帖子类型以及已有的帖子类型。
本章将向您展示如何注册自定义帖子类型、如何从数据库检索其内容以及如何将它们呈现给公众。
注册自定义帖子类型
WordPress 附带五种默认帖子类型:post
、page
、attachment
、revision
和menu
。
在开发插件时,您可能需要创建自己的特定内容类型:例如,电子商务网站的产品、电子学习网站的作业或评论网站的电影。
使用自定义帖子类型,您可以注册自己的帖子类型。注册自定义帖子类型后,它将获得一个新的顶级管理屏幕,可用于管理和创建该类型的帖子。
要注册新的帖子类型,请使用register_post_type() 函数。
以下最小示例注册一个新的帖子类型 Products,它在数据库中标识为wporg_product
。
function wporg_custom_post_type() {
register_post_type('wporg_product',
array(
'labels' => array(
'name' => __('Products', 'textdomain'),
'singular_name' => __('Product', 'textdomain'),
),
'public' => true,
'has_archive' => true,
)
);
}
add_action('init', 'wporg_custom_post_type');
请访问register_post_type()的参考页面 以获取参数说明。
警告:register_post_type()
您必须在钩子之前admin_init
和钩子之后 调用after_setup_theme
。一个很好用的钩子是init
动作钩子。
命名最佳实践
重要的是,您要为帖子类型函数和标识符添加与您的插件、主题或网站相对应的短前缀。
警告:确保您的自定义帖子类型标识符不超过 20 个字符,因为post_type
数据库中的列当前是该长度的 VARCHAR 字段。
警告:为了确保向前兼容性,请勿使用wp_作为标识符 - WordPress 核心正在使用它。
警告:如果您的标识符太通用(例如:“ product
”),它可能会与选择使用相同标识符的其他插件或主题发生冲突。
笔记:如果不禁用冲突的帖子类型之一,就不可能解决重复的帖子类型标识符。
网址
自定义帖子类型在站点 URL 结构中拥有自己的 slug。
默认情况下,帖子类型wporg_product
将使用以下 URL 结构:http://example.com/wporg_product/%product_name%
。
wporg_product
是您的自定义帖子类型的 slug,%product_name%
也是您特定产品的 slug。
最终的永久链接是:http://example.com/wporg_product/wporg-is-awesome
。
您可以在编辑屏幕上看到自定义帖子类型的永久链接,就像默认帖子类型一样。
自定义帖子类型的自定义 Slug
要为自定义帖子类型的 slug 设置自定义 slug,您需要做的就是将 key => value 对添加到参数数组rewrite
中的键register_post_type()
。
例子:
function wporg_custom_post_type() {
register_post_type('wporg_product',
array(
'labels' => array(
'name' => __( 'Products', 'textdomain' ),
'singular_name' => __( 'Product', 'textdomain' ),
),
'public' => true,
'has_archive' => true,
'rewrite' => array( 'slug' => 'products' ), // my custom slug
)
);
}
add_action('init', 'wporg_custom_post_type');
以上将产生以下 URL 结构:http://example.com/products/%product_name%
警告:使用通用的 slugproducts
可能会与其他插件或主题发生冲突,因此请尝试使用更适合您的内容的插件或主题。
笔记:与自定义帖子类型标识符不同,重复的 slug 问题可以通过更改冲突的帖子类型之一的 slug 来轻松解决。
如果插件作者包含apply_filters()
对参数的调用,则可以通过覆盖通过函数提交的参数以编程方式完成此操作register_post_type()
。
使用自定义帖子类型
自定义帖子类型模板
您可以为自定义帖子类型创建自定义模板。single.php
以与使用和显示帖子及其档案相同的方式archive.php
,您可以创建模板:
single-{post_type}.php
– 对于自定义帖子类型的单个帖子archive-{post_type}.php
– 用于存档
其中{post_type}
是 post 类型标识符,用作函数$post_type
的参数register_post_type()
。
根据我们之前学到的知识,您可以为单个产品帖子和存档创建模板文件single-wporg_product.php
。archive-wporg_product.php
或者,您可以在任何模板文件中使用is_post_type_archive() 函数来检查查询是否显示给定帖子类型的存档页面,并使用 post_type_archive_title () 函数来显示帖子类型标题。
按帖子类型查询
post_type
您可以通过在类构造函数的参数数组中传递键来查询特定类型的帖子WP_Query
。
<?php
$args = array(
'post_type' => 'product',
'posts_per_page' => 10,
);
$loop = new WP_Query($args);
while ( $loop->have_posts() ) {
$loop->the_post();
?>
<div class="entry-content">
<?php the_title(); ?>
<?php the_content(); ?>
</div>
<?php
}
这会循环显示最新的十个产品帖子,并一一显示它们的标题和内容。
更改主查询
注册自定义帖子类型并不意味着它会自动添加到主查询中。
如果您希望自定义帖子类型帖子显示在标准存档中或将它们与其他帖子类型混合包含在主页上,请使用pre_get_posts
操作挂钩。
下一个示例将显示来自 的帖子以及post
主页上的帖子类型:page
movie
function wporg_add_custom_post_types($query) {
if ( is_home() && $query->is_main_query() ) {
$query->set( 'post_type', array( 'post', 'page', 'movie' ) );
}
return $query;
}
add_action('pre_get_posts', 'wporg_add_custom_post_types');
taxonomy分类法
分类法
分类法是一个奇特的词,用于对事物进行分类/分组。分类法可以是分层的(父级/子级)或扁平的。
WordPress 将分类法存储在term_taxonomy
数据库表中,允许开发人员沿着已有的分类法注册自定义分类法。
分类法有一些术语,可作为您对事物进行分类/分组的主题。它们存储在terms
表内。
例如:名为“艺术”的分类法可以有多个术语,例如“现代”和“18 世纪”。
本章将向您展示如何注册自定义分类法、如何从数据库检索其内容以及如何将其呈现给公众。
使用自定义分类法
分类法简介
要了解分类法是什么及其用途,请阅读分类法简介。
自定义分类法
随着分类系统的发展,“类别”和“标签”并不是非常结构化,因此开发人员创建自己的类别可能会有好处。
WordPress 允许开发人员创建自定义分类法。当人们想要创建不同的命名系统并使其以可预测的方式在幕后访问时,自定义分类法非常有用。
为什么使用自定义分类法?
您可能会问,“当我可以按类别和标签进行组织时,为什么还要创建自定义分类法呢?”
好吧……让我们举个例子。假设我们有一位厨师客户,希望您创建一个网站,在其中展示她的原创食谱。
组织网站的一种方法可能是创建一个名为“食谱”的自定义帖子类型来存储她的食谱。然后创建一个分类法“课程”以将“开胃菜”与“甜点”分开,最后创建一个分类法“成分”以将“鸡肉”与“巧克力”菜肴分开。
这些组可以使用类别或标签来定义,您可以有一个“课程”类别,其中包含“开胃菜”和“甜点”的子类别,以及一个“成分”类别,其中每种成分都有子类别。
使用自定义分类法的主要优点是您可以独立于类别和标签引用“课程”和“成分”。他们甚至在管理区域拥有自己的菜单。
此外,创建自定义分类法允许您构建自定义界面,这将简化客户的生活,并使插入数据的过程直观地了解其业务性质。
现在想象一下这些自定义分类法和接口是在插件内实现的;您刚刚构建了自己的食谱插件,可以在任何 WordPress 网站上重复使用。
示例:课程分类
以下示例将向您展示如何创建一个插件,将自定义分类“课程”添加到默认post
帖子类型。请注意,添加自定义分类法的代码不必位于其自己的插件中;如果需要,它可以包含在主题中或作为现有插件的一部分。
在尝试创建您自己的插件之前,请务必阅读插件基础知识章节。
第 1 步:开始之前
转到帖子 > 添加新页面。您会注意到您只有类别和标签。
第 2 步:创建一个新插件
使用操作挂钩为帖子类型“帖子”注册分类“课程” init
。
/*
* Plugin Name: Course Taxonomy
* Description: A short example showing how to add a taxonomy called Course.
* Version: 1.0
* Author: developer.wordpress.org
* Author URI: https://codex.wordpress.org/User:Aternus
*/
function wporg_register_taxonomy_course() {
$labels = array(
'name' => _x( 'Courses', 'taxonomy general name' ),
'singular_name' => _x( 'Course', 'taxonomy singular name' ),
'search_items' => __( 'Search Courses' ),
'all_items' => __( 'All Courses' ),
'parent_item' => __( 'Parent Course' ),
'parent_item_colon' => __( 'Parent Course:' ),
'edit_item' => __( 'Edit Course' ),
'update_item' => __( 'Update Course' ),
'add_new_item' => __( 'Add New Course' ),
'new_item_name' => __( 'New Course Name' ),
'menu_name' => __( 'Course' ),
);
$args = array(
'hierarchical' => true, // make it hierarchical (like categories)
'labels' => $labels,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'rewrite' => [ 'slug' => 'course' ],
);
register_taxonomy( 'course', [ 'post' ], $args );
}
add_action( 'init', 'wporg_register_taxonomy_course' );
第 3 步:查看结果
激活您的插件,然后转到帖子 > 添加新插件。您应该会看到“课程”分类的新元框。
代码分解
以下讨论分解了上面使用的代码,描述了函数和参数。
该函数wporg_register_taxonomy_course
包含注册自定义分类法所需的所有步骤。
该$labels
数组包含自定义分类法的标签。
这些标签将用于在管理区域中显示有关分类的各种信息。
该$args
数组包含创建自定义分类法时将使用的配置选项。
函数register_taxonomy()使用配置数组 创建一个带有帖子类型标识符的新分类法course
。post
$args
函数add_action()wporg_register_taxonomy_course
将函数执行与操作挂钩 联系起来init
。
$参数
$args 数组保存自定义分类法的重要配置,它指示 WordPress 分类法应如何工作。
查看register_taxonomy() 函数以获取可接受参数的完整列表以及每个参数的作用。
概括
通过我们的课程分类示例,WordPress 将自动为分类创建存档页面和子页面course
。
存档页面将/course/
使用 Term 的 slug ( /course/%%term-slug%%/
) 在其下方生成子页面。
使用你的分类法
WordPress 具有许多与您的自定义分类法及其中的术语进行交互的功能。
这里有些例子:
the_terms
:采用分类法参数并在列表中呈现术语。wp_tag_cloud
:采用分类法参数并呈现术语的标签云。is_taxonomy
:允许您确定给定的分类是否存在。
术语分割 (WordPress 4.2)
此信息出于历史目的而放在这里。如果您对 2015 年之前的术语如何运作不感兴趣,您可以跳过本节。
WordPress 4.2 之前的版本
具有相同 slug 的不同分类法中的术语共享一个术语 ID。例如,带有“新闻”标签的标签和类别具有相同的术语 ID。
WordPress 4.2+
从 4.2 开始,当这些共享术语之一更新时,它将被拆分:更新的术语将被分配一个新的术语 ID。
这对你来说意味着什么?
在绝大多数情况下,此更新是无缝且顺利的。但是,一些将术语 ID 存储在选项、帖子元、用户元或其他位置的插件和主题可能会受到影响。
处理分裂
WordPress 4.2 包含两种不同的工具来帮助插件和主题的作者进行过渡。
钩子split_shared_term
_
当为共享术语分配新术语 ID 时,split_shared_term
会触发新操作。
以下是插件和主题作者如何利用此挂钩来确保更新存储的术语 ID 的一些示例。
存储在选项中的术语 ID
假设您的插件存储一个名为的选项featured_tags
,其中包含一组术语 ID ( [4, 6, 10]
),用作主页精选帖子部分的查询参数。
在此示例中,您将挂钩split_shared_term
操作,检查更新的术语 ID 是否在数组中,并在必要时进行更新。
/**
* Update featured_tags option when a shared term gets split.
*
* @param int $term_id ID of the formerly shared term.
* @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
* @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
* @param string $taxonomy Taxonomy for the split term.
*/
function wporg_featured_tags_split( int $term_id, int $new_term_id, int $term_taxonomy_id, string $taxonomy ): void {
// we only care about tags, so we'll first verify that the taxonomy is post_tag.
if ( 'post_tag' === $taxonomy ) {
// get the currently featured tags.
$featured_tags = get_option( 'featured_tags' );
// if the updated term is in the array, note the array key.
$found_term = array_search( $term_id, $featured_tags, true );
if ( false !== $found_term ) {
// the updated term is a featured tag! replace it in the array, save the new array.
$featured_tags[ $found_term ] = $new_term_id;
update_option( 'featured_tags', $featured_tags );
}
}
}
add_action( 'split_shared_term', 'wporg_featured_tags_split', 10, 4 );
术语 ID 存储在帖子元中
假设您的插件在页面的帖子元数据中存储术语 ID,以便您可以显示特定页面的相关帖子。
在这种情况下,您需要使用该get_posts()
函数来获取您的页面meta_key
并更新meta_value
匹配的拆分术语 ID。
/**
* Update related posts term ID for pages
*
* @param int $term_id ID of the formerly shared term.
* @param int $new_term_id ID of the new term created for the $term_taxonomy_id.
* @param int $term_taxonomy_id ID for the term_taxonomy row affected by the split.
* @param string $taxonomy Taxonomy for the split term.
*/
function wporg_page_related_posts_split( int $term_id, int $new_term_id, int $term_taxonomy_id, string $taxonomy ): void {
// find all the pages where meta_value matches the old term ID.
$page_ids = get_posts(
array(
'post_type' => 'page',
'fields' => 'ids',
'meta_key' => 'meta_key',
'meta_value' => $term_id,
)
);
// if such pages exist, update the term ID for each page.
if ( $page_ids ) {
foreach ( $page_ids as $id ) {
update_post_meta( $id, 'meta_key', $new_term_id, $term_id );
}
}
}
add_action( 'split_shared_term', 'wporg_page_related_posts_split', 10, 4 );
功能wp_get_split_term
_
但是,在某些情况下,术语可能会被拆分,而您的插件没有机会挂钩该split_shared_term
操作。
WordPress 4.2 存储有关已拆分的分类术语的信息,并提供wp_get_split_term()
实用函数来帮助开发人员检索此信息。
考虑上面的情况,您的插件将术语 ID 存储在名为 的选项中featured_tags
。您可能想要构建一个验证这些标签 ID 的函数(可能在插件更新时运行),以确保没有任何特色标签被拆分:
/**
* Retrieve information about split terms and udpates the featured_tags option with the new term IDs.
*
* @return void
*/
function wporg_featured_tags_check_split() {
$featured_tag_ids = get_option( 'featured_tags', array() );
// check to see whether any IDs correspond to post_tag terms that have been split.
foreach ( $featured_tag_ids as $index => $featured_tag_id ) {
$new_term_id = wp_get_split_term( $featured_tag_id, 'post_tag' );
if ( $new_term_id ) {
$featured_tag_ids[ $index ] = $new_term_id;
}
}
// save
update_option( 'featured_tags', $featured_tag_ids );
}
请注意,它wp_get_split_term()
需要两个参数,$old_term_id
并且$taxonomy
返回一个整数。
如果您需要检索与旧术语 ID 关联的所有拆分术语的列表,无论分类如何,请使用wp_get_split_terms()
。
用户
用户
用户是在 WordPress安装中具有相应功能的访问帐户。每个 WordPress 用户至少都有一个用户名、密码和电子邮件地址。
创建用户帐户后,该用户可以使用 WordPress 管理员(或以编程方式)登录来访问 WordPress 功能和数据。WordPress 将用户存储在users
表中。
角色和能力
您可以创建具有自己的一组功能的新角色。还可以创建自定义功能并将其分配给现有角色或新角色。
在 WordPress 中,开发人员可以利用用户角色来限制帐户可以执行的操作集。
最小权限原则
WordPress 遵循最小权限原则,即仅授予用户执行所需工作所必需的权限。您应该尽可能遵循这一指导原则,在适当的情况下创建角色并在执行敏感任务之前检查功能。
与用户合作
添加用户
要添加用户,您可以使用wp_create_user()
或wp_insert_user()
。
wp_create_user()
仅使用用户名、密码和电子邮件参数创建用户,同时wp_insert_user()
接受描述用户及其属性的数组或对象。
创建用户
wp_create_user()
允许您创建新的 WordPress 用户。
wp_create_user()
有关所用参数的完整说明,请参阅函数参考。
创建示例
// check if the username is taken
$user_id = username_exists( $user_name );
// check that the email address does not belong to a registered user
if ( ! $user_id && email_exists( $user_email ) === false ) {
// create a random password
$random_password = wp_generate_password( 12, false );
// create the user
$user_id = wp_create_user(
$user_name,
$random_password,
$user_email
);
}
插入用户
wp_insert_user( $userdata );
笔记:该函数为大多数预定义属性调用过滤器。
user_register
该函数在创建用户(用户 ID 不存在)时执行操作。
profile_update
该函数在更新用户(用户 ID 存在)时执行操作。
wp_insert_user()
有关所用参数的完整说明,请参阅函数参考。
插入示例
下面的示例显示了如何插入填写了网站配置文件字段的新用户。
$username = $_POST['username'];
$password = $_POST['password'];
$website = $_POST['website'];
$user_data = [
'user_login' => $username,
'user_pass' => $password,
'user_url' => $website,
];
$user_id = wp_insert_user( $user_data );
// success
if ( ! is_wp_error( $user_id ) ) {
echo 'User created: ' . $user_id;
}
更新用户
wp_update_user()
更新数据库中的单个用户。更新数据在数组/对象中传递$userdata
。
要更新单个用户元数据,请update_user_meta()
改用。要创建新用户,请wp_insert_user()
改用。
笔记:如果当前用户的密码被更新,那么cookies将被清除!
wp_update_user()
有关所用参数的完整说明,请参阅函数参考。
示例更新
下面的示例显示了如何更新用户的网站配置文件字段。
$user_id = 1;
$website = 'https://wordpress.org';
$user_id = wp_update_user(
array(
'ID' => $user_id,
'user_url' => $website,
)
);
if ( is_wp_error( $user_id ) ) {
// error
} else {
// success
}
删除用户
wp_delete_user()
删除用户并可选择将关联实体重新分配给另一个用户 ID。
笔记:deleted_user
该函数在用户被删除后 执行操作。
警报:如果$reassign参数没有设置为有效的用户ID,那么属于被删除用户的所有实体都将被删除!
wp_delete_user()
有关所用参数的完整说明,请参阅函数参考。
使用用户元数据
介绍
WordPress 的users
表被设计为仅包含有关用户的基本信息。
ID
、user_login
、user_pass
、user_nicename
、user_email
、user_url
、user_registered
、user_activation_key
和。user_status
display_name
因此,为了存储额外的数据,usermeta
引入了表,它可以存储有关用户的任意数量的数据。
ID
两个表使用基于表中的一对多关系连接在一起users
。
操纵用户元数据
操作用户元数据有两种主要方法。
- 用户个人资料屏幕中的表单字段。
- 以编程方式,通过函数调用。
通过表单字段
表单字段选项适用于用户有权访问 WordPress 管理区域的情况,他可以在其中查看和编辑配置文件。
在我们深入研究示例之前,了解该过程中涉及的钩子以及它们存在的原因非常重要。
show_user_profile钩
每当用户编辑其自己的用户配置文件时,就会触发此操作挂钩。
请记住,无法编辑自己的个人资料的用户不会触发此挂钩。
edit_user_profile钩
每当用户编辑其他人的用户配置文件时,就会触发此操作挂钩。
请记住,不具备编辑第 3 方配置文件功能的用户不会触发此挂钩。
表单字段示例
在下面的示例中,我们将向所有个人资料屏幕添加生日字段。在配置文件更新时将其保存到数据库中。
/**
* The field on the editing screens.
*
* @param $user WP_User user object
*/
function wporg_usermeta_form_field_birthday( $user ) {
?>
<h3>It's Your Birthday</h3>
<table class="form-table">
<tr>
<th>
<label for="birthday">Birthday</label>
</th>
<td>
<input type="date"
class="regular-text ltr"
id="birthday"
name="birthday"
value="<?= esc_attr( get_user_meta( $user->ID, 'birthday', true ) ) ?>"
title="Please use YYYY-MM-DD as the date format."
pattern="(19[0-9][0-9]|20[0-9][0-9])-(1[0-2]|0[1-9])-(3[01]|[21][0-9]|0[1-9])"
required>
<p class="description">
Please enter your birthday date.
</p>
</td>
</tr>
</table>
<?php
}
/**
* The save action.
*
* @param $user_id int the ID of the current user.
*
* @return bool Meta ID if the key didn't exist, true on successful update, false on failure.
*/
function wporg_usermeta_form_field_birthday_update( $user_id ) {
// check that the current user have the capability to edit the $user_id
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return false;
}
// create/update user meta for the $user_id
return update_user_meta(
$user_id,
'birthday',
$_POST['birthday']
);
}
// Add the field to user's own profile editing screen.
add_action(
'show_user_profile',
'wporg_usermeta_form_field_birthday'
);
// Add the field to user profile editing screen.
add_action(
'edit_user_profile',
'wporg_usermeta_form_field_birthday'
);
// Add the save action to user's own profile editing screen update.
add_action(
'personal_options_update',
'wporg_usermeta_form_field_birthday_update'
);
// Add the save action to user profile editing screen update.
add_action(
'edit_user_profile_update',
'wporg_usermeta_form_field_birthday_update'
);
以编程方式
此选项适用于您正在构建自定义用户区域和/或计划禁用对 WordPress 管理区域的访问的情况。
可用于操作用户元数据的函数有:add_user_meta()
、update_user_meta()
和。delete_user_meta()
get_user_meta()
添加
add_user_meta(
int $user_id,
string $meta_key,
mixed $meta_value,
bool $unique = false
);
add_user_meta()
有关所用参数的完整说明,请参阅函数参考。
更新
update_user_meta(
int $user_id,
string $meta_key,
mixed $meta_value,
mixed $prev_value = ''
);
update_user_meta()
有关所用参数的完整说明,请参阅函数参考。
删除
delete_user_meta(
int $user_id,
string $meta_key,
mixed $meta_value = ''
);
delete_user_meta()
有关所用参数的完整说明,请参阅函数参考。
得到
get_user_meta(
int $user_id,
string $key = '',
bool $single = false
);
get_user_meta()
有关所用参数的完整说明,请参阅函数参考。
请注意,如果您仅传递$user_id
,该函数将以关联数组的形式检索所有元数据。
您可以在插件或主题中的任何位置呈现用户元数据。
角色和能力
角色和功能是 WordPress 的两个重要方面,可让您控制用户权限。
WordPress 将角色及其功能存储在键options
下的表中user_roles
。
角色
角色定义了用户的一组能力。例如,用户可以在他的仪表板中看到什么和做什么。
默认情况下,WordPress 有六个角色:
- 超级管理员
- 行政人员
- 编辑
- 作者
- 贡献者
- 订户
可以添加更多角色并删除默认角色。

添加角色
使用add_role()添加新角色并为其分配功能。
function wporg_simple_role() {
add_role(
'simple_role',
'Simple Role',
array(
'read' => true,
'edit_posts' => true,
'upload_files' => true,
),
);
}
// Add the simple_role.
add_action( 'init', 'wporg_simple_role' );
顺序调用不会执行任何操作:包括更改功能列表,这可能不是您期望的行为。
笔记:要批量更改功能列表:使用以下命令删除角色remove_role() 并使用再次添加add_role() 具有新功能。
确保仅当功能与您的预期不同时才执行此操作(即对此进行调节),否则您将大大降低性能!
删除角色
使用remove_role()删除角色。
function wporg_simple_role_remove() {
remove_role( 'simple_role' );
}
// Remove the simple_role.
add_action( 'init', 'wporg_simple_role_remove' );
警报:第一次致电后remove_role() ,角色及其功能将从数据库中删除!
连续调用不会执行任何操作。
笔记:如果您要删除默认角色:
- 我们建议不要删除管理员和超级管理员角色!
- 确保将代码保留在您的插件/主题中,因为将来的 WordPress 更新可能会再次添加这些角色。
- 运行
update_option('default_role', YOUR_NEW_DEFAULT_ROLE)
,因为您将删除subscriber
WP 的默认角色。
能力
能力定义角色可以做什么和不能做什么:编辑帖子、发布帖子等。
笔记:自定义帖子类型可能需要一组特定的功能。
添加功能
您可以为角色定义新的能力。
使用get_role() 获取角色对象,然后使用add_cap()
该对象的方法添加新功能。
function wporg_simple_role_caps() {
// Gets the simple_role role object.
$role = get_role( 'simple_role' );
// Add a new capability.
$role->add_cap( 'edit_others_posts', true );
}
// Add simple_role capabilities, priority must be after the initial role definition.
add_action( 'init', 'wporg_simple_role_caps', 11 );
笔记:可以向任何角色添加自定义功能。
在默认的 WordPress 管理下,它们没有任何作用,但它们可用于自定义管理屏幕和前端区域。
删除功能
您可以删除角色的能力。
该实现与“添加功能”类似,不同之处在于使用remove_cap()
角色对象的方法。
使用角色和能力
获取角色
使用get_role()获取角色对象,包括其所有功能。
get_role( $role );
用户可以
使用user_can()检查用户是否具有指定的角色或能力。
user_can( $user, $capability );
警告:有一个未记录的第三个参数 $args,它可能包括应执行测试的对象。
例如,传递一个帖子 ID 来测试该特定帖子的能力。
当前用户可以
current_user_can()是user_can() 的包装函数, 使用当前用户对象作为$user
参数。
在后端和前端区域需要一定级别的权限才能访问和/或修改的情况下使用此功能。
current_user_can( $capability );
例子
如果用户具有适当的能力,下面是在模板文件中添加编辑链接的实际示例:
if ( current_user_can( 'edit_posts' ) ) {
edit_post_link( esc_html__( 'Edit', 'wporg' ), '<p>', '</p>' );
}
站点
current_user_can_for_blog () 函数用于测试当前用户在特定博客上是否具有特定角色或能力。
current_user_can_for_blog( $blog_id, $capability );
参考
用户角色和能力的法典参考。
HTTP API
介绍
HTTP 代表超文本传输协议,是整个互联网的基础通信协议。即使这是您第一次使用 HTTP,您的理解可能比您意识到的要多。在最基本的层面上,HTTP 的工作原理如下:
- “服务器 XYZ 您好,请问有文件 abc.html”
- “你好,小客户,是的,可以,就是这里”
在 PHP 中发送 HTTP 请求有许多不同的方法。WordPress HTTP API 的目的是支持尽可能多的方法,并使用最适合特定请求的方法。
WordPress HTTP API 还可用于与其他 API(例如 Twitter API 或 Google Maps API)进行通信和交互。
HTTP 方法
HTTP 有多种方法或动词来描述特定类型的操作。虽然还有更多功能,但 WordPress 已经为其中三个最常见的功能预先构建了功能。每当发出 HTTP 请求时,也会传递一个方法来帮助服务器确定客户端请求的操作类型。
得到
GET 用于检索数据。这是迄今为止最常用的动词。每次您查看网站或从 API 提取数据时,您都会看到 GET 请求的结果。事实上,您的浏览器向您正在阅读本文的服务器发送了一个 GET 请求,并请求用于构建这篇文章的数据。
邮政
POST 用于将数据发送到服务器,以便服务器以某种方式执行操作。例如,联系表。当您在表单字段中输入数据并单击提交按钮时,浏览器会获取数据并向服务器发送 POST 请求,其中包含您在表单中输入的文本。服务器将从那里处理联系请求。
头
HEAD 的知名度远不如其他两个。HEAD 本质上与 GET 请求相同,只是它不检索数据,仅检索有关数据的信息。这些数据描述了诸如数据上次更新的时间、客户端是否应该缓存数据、数据是什么类型等信息。现代浏览器经常向您以前访问过的页面发送 HEAD 请求,以确定是否有任何更新。如果没有,您实际上可能会看到以前下载的页面副本,而不是不必要地使用带宽拉入同一副本。
所有好的 API 客户端在执行 GET 请求之前都会利用 HEAD,以节省带宽。虽然如果 HEAD 表示有新数据,则需要两个单独的 HTTP 请求,但 GET 请求的数据大小可能非常大。仅当 HEAD 表示数据是新的或不应缓存时才使用 GET 将有助于节省昂贵的带宽和加载时间。
自定义方法
还有其他 HTTP 方法,例如 PUT、DELETE、TRACE 和 CONNECT。本文不会介绍这些方法,因为在 WordPress 中没有预先构建的方法来使用它们,而且实现它们的 API 也并不常见。
根据服务器的配置方式,您还可以实现自己的其他 HTTP 方法。超出标准方法总是一场赌博,并且对创建客户端来使用您的网站或 API 的其他开发人员造成巨大的潜在限制,但是您可以在 WordPress 中使用您希望的任何方法。我们将在本文中简要介绍如何做到这一点。
响应代码
HTTP 使用数字和字符串响应代码。下面是标准响应代码,而不是对每个代码进行冗长的解释。您可以在创建 API 时定义自己的响应代码,但是除非您需要支持特定类型的响应,否则最好坚持使用标准代码。自定义代码通常在 1xx 范围内。
代码类
通过三位数代码中最左边的数字可以快速看出响应的类型。
状态码 | 描述 |
---|---|
2xx | 请求成功 |
3xx | 请求被重定向到另一个 URL |
4xx | 由于客户端错误,请求失败。通常身份验证无效或数据丢失 |
5xx | 由于服务器错误,请求失败。配置文件通常丢失或配置错误 |
通用代码
这些是您会遇到的最常见的代码。
状态码 | 描述 |
---|---|
200 | 确定 – 请求成功 |
301 | 资源已永久移动 |
第302章 | 资源已暂时移动 |
403 | 禁止 – 通常是由于无效的身份验证 |
404 | 找不到资源 |
500 | 内部服务器错误 |
503 | 暂停服务 |
从 API 获取数据
GitHub提供了一个优秀的 API,许多公共方面不需要应用程序注册,因此为了演示其中一些方法,示例将针对 GitHub API。
通过该功能,在 WordPress 中获取数据变得异常简单wp_remote_get()
。该函数采用以下两个参数:
- $url – 从中检索数据的资源。这必须是标准 HTTP 格式
- $args – 可选 – 您可以在此处传递一组参数来更改行为和标头,例如 cookie、跟随重定向等。
假定以下默认值,但可以通过 $args 参数更改它们:
- 方法——GET
- timeout – 5 – 放弃之前等待多长时间
- 重定向 – 5 – 遵循重定向的次数。
- http版本 – 1.0
- blocking – true – 页面的其余部分是否应该等待完成加载,直到此操作完成?
- 标题 – array()
- 正文 – 空
- cookie – 数组()
让我们使用 GitHub 用户帐户的 URL 看看我们可以获得什么类型的信息
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$response 将包含有关我们请求的所有标头、内容和其他元数据
Array(
[headers] => Array(
[server] => nginx
[date] => Fri, 05 Oct 2012 04:43:50 GMT
[content-type] => application/json; charset=utf-8
[connection] => close
[status] => 200 OK
[vary] => Accept
[x-ratelimit-remaining] => 4988
[content-length] => 594
[last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
[etag] => "5d5e6f7a09462d6a2b473fb616a26d2a"
[x-github-media-type] => github.beta
[cache-control] => public, s-maxage=60, max-age=60
[x-content-type-options] => nosniff
[x-ratelimit-limit] => 5000
)
[body] => {"type":"User","login":"blobaugh","gravatar_id":"f25f324a47a1efdf7a745e0b2e3c878f","public_gists":1,"followers":22,"created_at":"2011-05-23T21:38:50Z","public_repos":31,"email":"ben@lobaugh.net","hireable":true,"blog":"http://ben.lobaugh.net","bio":null,"following":30,"name":"Ben Lobaugh","company":null,"avatar_url":"https://secure.gravatar.com/avatar/f25f324a47a1efdf7a745e0b2e3c878f?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","id":806179,"html_url":"https://github.com/blobaugh","location":null,"url":"https://api.github.com/users/blobaugh"}
[response] => Array(
[preserved_text 5237511b45884ac6db1ff9d7e407f225 /] => 200
[message] => OK
)
[cookies] => Array()
[filename] =>
)
与前两个函数一样,可以在此函数上使用所有相同的辅助函数。这里的例外是 HEAD 永远不会返回主体,因此该元素将始终为空。
获得您一直想要的身体
只能使用 来取回尸体wp_remote_retrieve_body()
。该函数仅采用一个参数,即来自任何其他wp_remote_X函数的响应,其中检索不是下一个值。
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$body = wp_remote_retrieve_body( $response );
仍然使用上一个示例中的 GitHub 资源,$body 将是
{"type":"User","login":"blobaugh","public_repos":31,"gravatar_id":"f25f324a47a1efdf7a745e0b2e3c878f","followers":22,"avatar_url":"https://secure.gravatar.com/avatar/f25f324a47a1efdf7a745e0b2e3c878f?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-user-420.png","public_gists":1,"created_at":"2011-05-23T21:38:50Z","email":"ben@lobaugh.net","following":30,"name":"Ben Lobaugh","company":null,"hireable":true,"id":806179,"html_url":"https://github.com/blobaugh","blog":"http://ben.lobaugh.net","location":null,"bio":null,"url":"https://api.github.com/users/blobaugh"}
如果除了获取正文之外,您没有对响应执行任何其他操作,您可以将代码减少到一行
$body = wp_remote_retrieve_body( wp_remote_get( 'https://api.github.com/users/blobaugh' ) );
许多这些辅助函数都可以类似地在一行中使用。
获取响应代码
您可能需要检查响应代码以确保检索成功。这可以通过以下函数完成wp_remote_retrieve_response_code()
:
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$http_code = wp_remote_retrieve_response_code( $response );
如果成功$http_code
将包含200
.
获取特定标头
如果您希望检索特定标头(例如上次修改的标头),您可以使用wp_remote_retrieve_header()
. 该函数有两个参数
$response
– get 调用的响应$header
– 要检索的标头名称
检索最后修改的标头
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
$last_modified = wp_remote_retrieve_header( $response, 'last-modified' );
$last_modified
将包含[last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
您还可以使用 检索数组中的所有标头wp_remote_retrieve_headers( $response )
。
使用基本身份验证获取
更安全的 API 提供多种不同类型的身份验证中的一种或多种。一种常见但安全性不高的身份验证方法是 HTTP 基本身份验证。wp_remote_get()
通过将“授权”传递给函数的第二个参数以及其他 HTTP 方法函数,可以在 WordPress 中使用它。
$args = array(
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( YOUR_USERNAME . ':' . YOUR_PASSWORD )
)
);
wp_remote_get( $url, $args );
将数据发布到 API
相同的帮助器方法(wp_remote_retrieve_body()
等)可用于所有 HTTP 方法调用,并以相同的方式使用。
POST 数据是使用该wp_remote_post()
函数完成的,并且采用与 完全相同的参数wp_remote_get()
。这里需要注意的是,第二个参数需要传入数组中的所有元素。Codex 提供了默认的可接受值。您现在只需要关心正在发送的数据,因此其他值将被默认。
要将数据发送到服务器,您需要构建一个关联的数据数组。该数据将被分配给'body'
值。从服务器端来看,该值将$_POST
如您所期望的那样出现在变量中。即如果body => array( 'myvar' => 5 )
在服务器上$_POST['myvar'] = 5
。
由于 GitHub 不允许 POST 到上一示例中使用的 API,因此本示例将假装允许。通常,如果您想要将数据发布到 API,您需要联系 API 的维护者并获取 API 密钥或某种其他形式的身份验证令牌。这只是证明您的应用程序可以像用户登录网站一样操作 API 上的数据。
假设我们正在提交包含以下字段的联系表单:姓名、电子邮件、主题、评论。要设置主体,我们执行以下操作:
$body = array(
'name' => 'Jane Smith',
'email' => 'some@email.com',
'subject' => 'Checkout this API stuff',
'comment' => 'I just read a great tutorial. You gotta check it out!',
);
现在我们需要设置将传递给第二个参数的其余值wp_remote_post()
$args = array(
'body' => $body,
'timeout' => '5',
'redirection' => '5',
'httpversion' => '1.0',
'blocking' => true,
'headers' => array(),
'cookies' => array(),
);
然后当然要打电话
$response = wp_remote_post( 'http://your-contact-form.com', $args );
减少带宽使用
在检索资源之前使用 HEAD 检查资源状态可能非常重要,有时 API 也要求这样做。在高流量 API 上,GET 通常限制为每分钟或每小时的请求数量。甚至不需要尝试 GET 请求,除非 HEAD 请求显示 API 上的数据已更新。
如前所述,HEAD 包含有关数据是否已更新、是否应缓存数据、缓存副本何时过期以及有时对 API 请求的速率限制的数据。
回到 GitHub 示例,这里有一些需要注意的标头。大多数标头都是标准的,但您应该始终检查 API 文档,以确保您了解哪些标头的名称及其用途。
x-ratelimit-limit
– 一段时间内允许的请求数x-ratelimit-remaining
– 时间段内剩余可用请求数content-length
– 内容有多大(以字节为单位)。如果内容相当大,可以用于警告用户last-modified
– 上次修改资源的时间。对于缓存工具非常有用cache-control
– 客户端应该如何处理缓存
下面将检查我的 GitHub 用户帐户的 HEAD 值:
$response = wp_remote_head( 'https://api.github.com/users/blobaugh' );
$response 应该类似于
Array(
[headers] => Array
(
[server] => nginx
[date] => Fri, 05 Oct 2012 05:21:26 GMT
[content-type] => application/json; charset=utf-8
[connection] => close
[status] => 200 OK
[vary] => Accept
[x-ratelimit-remaining] => 4982
[content-length] => 594
[last-modified] => Fri, 05 Oct 2012 04:39:58 GMT
[etag] => "5d5e6f7a09462d6a2b473fb616a26d2a"
[x-github-media-type] => github.beta
[cache-control] => public, s-maxage=60, max-age=60
[x-content-type-options] => nosniff
[x-ratelimit-limit] => 5000
)
[body] =>
[response] => Array
(
[preserved_text 39a8515bd2dce2aa06ee8a2a6656b1de /] => 200
[message] => OK
)
[cookies] => Array(
)
[filename] =>
)
与前两个函数一样,可以在此函数上使用所有相同的辅助函数。这里的例外是 HEAD 永远不会返回主体,因此该元素将始终为空。
提出任何类型的请求
如果您需要使用上述任何功能都不支持的 HTTP 方法发出请求,请不要惊慌。开发 WordPress 的伟大人们已经想到了这一点并热情地提供了wp_remote_request()
。此函数采用与 相同的两个参数wp_remote_get()
,并且还允许您指定 HTTP 方法。您需要传递哪些数据取决于您的方法。
要发送 DELETE 方法示例,您可能有类似于以下内容的内容:
$args = array(
'method' => 'DELETE',
);
$response = wp_remote_request( 'http://some-api.com/object/to/delete', $args );
缓存简介
缓存是一种实践,将常用的对象或需要大量时间构建的对象保存到快速对象存储中,以便在以后的请求时快速检索。这样就无需花费时间再次获取和构建对象。缓存是一个广泛的主题,是网站优化的一部分,可以单独包含在整个系列文章中。下面只是对缓存的介绍,以及快速为 API 响应设置缓存的简单而有效的方法。
为什么要缓存 API 响应?好吧,房间里的大大象是因为外部 API 降低了您网站的速度。许多顾问会告诉您,利用外部 API 可以减少连接量和执行的处理量以及昂贵的带宽,从而提高网站的性能,但有时事实并非如此。
这是服务器发送数据的速度与远程服务器处理请求、构建数据并将其发回所需的时间之间的良好平衡行为。第二个明显的方面是,许多 API 在一段时间内的请求数量有限,并且可能限制应用程序一次的连接数量。缓存通过在服务器上放置数据副本直至需要刷新来帮助解决这些难题。
什么时候应该缓存?
对此的简单回答是“总是”,但有时您不应该这样做。如果您正在处理实时数据或 API 明确表示不要在标头中缓存,您可能不想缓存,但对于所有其他情况,缓存从 API 检索的任何资源通常是一个好主意。
WordPress 瞬变
WordPress Transients 提供了一种存储和使用缓存对象的便捷方法。瞬态会持续指定的时间,或者直到 API 中的资源更新后您需要它们过期为止。在 WordPress 中使用瞬态功能可能是您遇到过的最容易使用的缓存系统。只需三个函数即可为您完成所有繁重的工作。
缓存一个对象(设置一个瞬态)
缓存对象是通过该set_transient()
函数完成的。该函数采用以下三个参数:
$transient
– 瞬态名称以供将来参考$value
– 瞬态值$expiration
– 从保存瞬态到过期需要多少秒
将上面的 GitHub 用户信息响应缓存一小时的示例是
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
set_transient( 'prefix_github_userinfo', $response, 60 * 60 );
获取缓存对象(获取瞬态)
获取缓存对象比设置瞬态对象要复杂得多。您需要请求瞬态,但您还需要检查该瞬态是否已过期,如果已过期,则获取更新的数据。通常set_transient()
调用是在调用内部进行的get_transient()
。以下是获取 GitHub 用户配置文件的瞬态数据的示例:
$github_userinfo = get_transient( 'prefix_github_userinfo' );
if ( false === $github_userinfo ) {
// Transient expired, refresh the data
$response = wp_remote_get( 'https://api.github.com/users/blobaugh' );
set_transient( 'prefix_github_userinfo', $response, HOUR_IN_SECONDS );
}
// Use $github_userinfo as you will
删除缓存对象(删除瞬态)
删除缓存对象是所有瞬态函数中最简单的,只需向其传递瞬态名称的参数即可完成。
删除 Github 用户信息:
delete_transient( 'blobaugh_github_userinfo' );
JavaScript、Ajax 和 jQuery
JavaScript
JavaScript 是许多 WordPress 插件中的重要组件。WordPress 附带了与 core 捆绑在一起的各种 JavaScript 库。WordPress 中最常用的库之一是 jQuery,因为它轻量级且易于使用。jQuery 可以在您的插件中使用来操作 DOM 对象或执行 Ajax 操作。
jQuery
使用 jQuery
收到您的 WordPress 网页后,您的 jQuery 脚本将在用户的浏览器上运行。基本的 jQuery 语句有两部分:一个选择器,用于确定代码应用到哪些 HTML 元素;以及一个操作或事件,用于确定代码的作用或反应。基本的事件语句如下所示:
jQuery.(selector).event(function);
当选择器选择的 HTML 元素中发生事件(例如鼠标单击)时,将执行最后一组括号内定义的函数。
以下所有代码示例均基于此 HTML 页面内容。假设它出现在插件的管理设置屏幕上,由文件定义myplugin_settings.php
。这是一个简单的表格,每个标题旁边都有单选按钮。
<form id="radioform">
<table>
<tbody>
<tr>
<td><input class="pref" checked="checked" name="book" type="radio" value="Sycamore Row" />Sycamore Row</td>
<td>John Grisham</td>
</tr>
<tr>
<td><input class="pref" name="book" type="radio" value="Dark Witch" />Dark Witch</td>
<td>Nora Roberts</td>
</tr>
</tbody>
</table>
</form>
输出在您的设置页面上可能看起来像这样。

在有关 AJAX 的文章中,我们将构建一个 AJAX 交换,将用户选择保存在 usermeta 中,并添加标有所选标题的帖子数量。这不是一个非常实际的应用程序,但它说明了所有重要的步骤。jQuery 代码可以驻留在外部文件中,也可以输出到<script>块内的页面。我们将重点关注外部文件变体,因为从 PHP 传递值需要特别注意。如果您认为更方便,可以将相同的代码输出到页面。
选择器和事件
选择器与 CSS 选择器的形式相同:".class"或"#id"。还有更多形式,但这两种是您会经常使用的。在我们的示例中,我们将使用类“.pref”。还有许多可能的事件,您可能会经常使用的一个事件是“点击”。在我们的示例中,我们将使用“更改”来捕获单选按钮选择。请注意,jQuery 事件的命名通常与 JavaScript 事件的命名有所不同。到目前为止,在我们添加一个空的匿名函数之后,我们的示例语句如下所示:
$.(".pref").change(function(){
/*do stuff*/
});
当“pref”类的任何元素发生更改时,此代码将“执行操作”。
AJAX
什么是 AJAX?
AJAX是异步 JavaScript 和 XML 的缩写。XML是一种数据交换格式,UX 是软件开发人员用户体验的简写。Ajax 是一种 Internet 通信技术,它允许用户浏览器中显示的网页向服务器请求特定信息,并在同一页面上显示此新信息,而无需重新加载整个页面。您已经可以想象这将如何改善用户体验。
虽然 XML 是使用的传统数据交换格式,但交换实际上可以是任何方便的格式。在使用PHP代码时,许多开发人员更喜欢JSON,因为从传输的数据流创建的内部数据结构更易于交互。
要查看 AJAX 的实际效果,请转到 WordPress 管理区域并添加类别或标签。单击“添加新”按钮时请密切注意,请注意页面发生变化但实际上并未重新加载。不相信?检查浏览器的后台历史记录,如果页面已重新加载,您将看到该页面的两个条目。
AJAX 甚至不需要用户操作即可工作。Google 文档每隔几分钟就会使用 AJAX 自动保存您的文档,而无需您启动保存操作。
为什么使用 AJAX?
显然,它改善了用户体验。AJAX 允许您呈现动态、响应迅速、用户友好的体验,而不是呈现无聊的静态页面。用户可以立即得到反馈,表明他们采取的某些操作是正确还是错误。在发现某一字段存在错误之前,无需提交整个表格。输入数据后即可验证重要字段。或者可以在用户键入时提出建议。
AJAX 可以显着减少来回流动的数据量。仅需要交换相关数据,而不是所有页面内容,这就是页面重新加载时发生的情况。
特别与 WordPress 插件相关,AJAX 是迄今为止启动独立于 WordPress 内容的流程的最佳方式。如果您以前编写过 PHP,您可能会通过简单地链接到新的 PHP 页面来完成此操作。单击链接的用户启动该过程。这样做的问题是,当您链接到新的外部 PHP 页面时,您无法访问任何 WordPress 功能。过去,开发人员通过wp-load.php
在新的 PHP 页面上包含核心文件来访问 WordPress 功能。这样做的问题是您不可能再知道该文件的正确路径。WordPress 架构现在足够灵活,/wp-content/
您的插件文件可以从其通常位置移动到安装根目录的一级。你无法知道在哪里wp-load.php
是相对于你的插件文件的,你也无法知道安装文件夹的绝对路径。
您可以知道向何处发送 AJAX 请求,因为它是在全局 JavaScript 变量中定义的。您的 PHP AJAX 处理程序脚本实际上是一个操作挂钩,因此与外部 PHP 文件不同,所有 WordPress 功能都自动可用。
如何使用 AJAX?
如果您是 WordPress 新手,但有在其他环境中使用 AJAX 的经验,则需要重新学习一些内容。WordPress 实现 AJAX 的方式很可能与您习惯的不同。如果一切对你来说都是新的,没问题。您将在这里学习基础知识。一旦您开发了基本的 AJAX 交换,就可以轻而易举地扩展该基础并开发具有出色用户界面的杀手级应用程序!
WordPress 中的任何 AJAX 交换都有两个主要组件。客户端 JavaScript 或 jQuery 和服务器端 PHP。所有 AJAX 交换都遵循以下事件顺序。
- 某种页面事件会启动 JavaScript 或 jQuery 函数。该函数从页面收集一些数据并通过 HTTP 请求将其发送到服务器。因为使用 JavaScript 处理 HTTP 请求很尴尬,而且 jQuery 无论如何都捆绑到 WordPress 中,所以从现在开始我们将只关注 jQuery 代码。直接使用 JavaScript 进行 AJAX 是可能的,但当 jQuery 可用时就不值得这样做。
- 服务器接收请求并对数据执行某些操作。它可以组装相关数据并将其以 HTTP 响应的形式发送回客户端浏览器。这不是必需的,但由于需要让用户了解正在发生的事情,因此很少不发送某种响应。
- 发送初始 AJAX 请求的 jQuery 函数接收服务器响应并对其执行某些操作。它可能会更新页面上的某些内容和/或通过某种方式向用户呈现消息。
将 AJAX 与 jQuery 结合使用
现在我们将定义jQuery 文章中的代码片段中的“do stuff”部分。我们将使用$.post()方法,它有 3 个参数:发送 POST 请求的 URL、要发送的数据以及处理服务器响应的回调函数。不过,在此之前,我们需要提前做好一些计划,以免造成阻碍。我们进行以下分配以供稍后在回调函数中使用。其目的在回调部分会更加明显。
var this2 = this;
网址
所有 WordPress AJAX 请求都必须发送到wp-admin/admin-ajax.php
. 正确、完整的 URL 需要来自 PHP,jQuery 无法自行确定该值,并且您不能在 jQuery 代码中对 URL 进行硬编码并期望其他人在其站点上使用您的插件。如果页面来自管理区域,WordPress 将在全局 JavaScript 变量ajaxurl中设置正确的 URL 。对于公共区域的页面,您需要自己建立正确的 URL 并使用wp_localize_script() 将其传递给 jQuery 。PHP 部分将对此进行更详细的介绍。现在只需知道适用于前端和后端的 URL 可用作您将在 PHP 段中定义的全局对象的属性。在 jQuery 中,它的引用方式如下:
my_ajax_obj.ajax_url
数据
所有需要发送到服务器的数据都包含在数据数组中。除了应用程序所需的任何数据之外,您还必须发送操作参数。对于可能导致数据库更改的请求,您需要发送一个随机数,以便服务器知道该请求来自合法来源。我们提供给.post()方法的示例数据数组如下所示:
{
_ajax_nonce: my_ajax_obj.nonce, // nonce
action: "my_tag_count", // action
title: this.value // data
}
下面解释每个组件。
随机数
Nonce是“Numberused ONCE”的合成词。它本质上是分配给所服务的任何形式的每个实例的唯一序列号。随机数是使用 PHP 脚本建立的,并以与 URL 相同的方式传递给 jQuery,作为全局对象中的属性。在本例中,它被引用为my_ajax_obj.nonce。
笔记
每次使用真正的随机数时都需要刷新,以便下一个 AJAX 调用有一个新的、未使用的随机数来发送作为验证。事实上,WordPress 的随机数实现并不是真正的随机数。除非您注销,否则在 24 小时内可以根据需要多次使用相同的随机数。使用相同的种子短语生成随机数将在 12 小时内始终产生相同的数字,之后最终将生成新的数字。
如果您的应用程序需要严格的安全性,请实现一个真正的随机数系统,其中服务器发送一个新的随机数来响应 Ajax 请求,以便脚本用于验证下一个请求。
如果将此随机数值键入_ajax_nonce ,这是最简单的。如果它与验证随机数的 PHP 代码协调,您可以使用不同的密钥,但仅使用默认值而不用担心协调会更容易。以下是该键值对的声明方式:
_ajax_nonce: my_ajax_obj.nonce
行动
所有 WordPress AJAX 请求都必须在数据中包含操作参数。该值是一个任意字符串,部分用于构造用于挂钩 AJAX 处理程序代码的操作标记。该值作为 AJAX 调用目的的非常简短的描述很有用。毫不奇怪,这个值的关键是'action'。在此示例中,我们将使用“my_tag_count”作为我们的操作值。该键值对的声明如下所示:
action: "my_tag_count"
服务器完成其任务所需的任何其他数据也包含在该数组中。如果有很多字段需要传输,有两种常见的格式可以将数据字段组合成单个字符串以方便传输:XML 和 JSON。使用这些格式是可选的,但无论您做什么都需要与服务器端的 PHP 脚本协调。有关这些格式的更多信息可在以下回调部分中找到。接收这种格式的数据比发送数据更常见,但两种方式都可以。
在我们的示例中,服务器只需要一个值,即所选书名的单个字符串,因此我们将使用键'title'。在 jQuery 中,触发事件的对象始终包含在变量this中。因此,所选元素的值为this.value。我们对该键值对的声明如下所示:
title: this.value
回调函数
回调处理程序是在发出请求后从服务器返回响应时执行的函数。再次,我们通常会在这里看到匿名函数。该函数传递一个参数,即服务器响应。响应可以是任何内容,从是或否到庞大的 XML 数据库。JSON 格式的数据也是一种有用的数据格式。甚至不需要响应。如果没有,则不需要指定回调。为了用户体验的利益,让用户知道任何请求发生了什么总是一个好主意,因此建议始终响应并提供一些发生情况的指示。
在我们的示例中,我们将无线电输入后面的当前文本替换为服务器响应,其中包括由书名标记的帖子数量。这是我们的匿名回调函数:
function( data ) {
this2.nextSibling.remove();
$( this2 ).after( data );
}
数据包含整个服务器响应。之前我们使用var this2 = this;行将触发更改事件的对象(引用为this )分配给this2 。。这是因为闭包中的变量作用域仅扩展了一级。通过在事件处理程序中分配this2 (最初只包含“/* do stuff */”的部分),我们可以在回调中使用它,而this超出了范围。
服务器响应可以采用任何形式。应将大量数据编码到数据流中以便于处理。XML 和 JSON 是两种常见的编码方案。
XML
XML 是 AJAX 的旧数据交换格式。毕竟它是 AJAX 中的“X”。尽管使用本机 PHP 函数可能很困难,但它仍然是一种可行的交换格式。出于这个原因,许多 PHP 程序员更喜欢 JSON 交换格式。如果您确实使用 XML,则解析方法取决于所使用的浏览器。对 Internet Explorer 使用 Microsoft.XMLDOM ActiveX,对其他所有内容使用 DOMParser。请注意,自 5.8 版本以来,WordPress 不再支持 Internet Explorer。
JSON
JSON 通常因其轻量级和易用性而受到青睐。您实际上可以使用eval()解析 JSON ,但不要这样做!使用eval()会带来重大的安全风险。相反,使用专用的解析器,这也更快。使用解析器对象JSON的全局实例。为了确保它可用,请确保它与页面上的其他脚本一起排队。有关排队的更多信息将包含在稍后的PHP 部分中。
其他
只要数据格式与 PHP 处理程序协调,它可以是您喜欢的任何格式,例如逗号分隔、制表符分隔或任何适合您的结构。
客户端总结
现在我们已经将回调添加为$.post()函数的最终参数,我们已经完成了示例 jQuery Ajax 脚本。所有的部分放在一起看起来像这样:
jQuery(document).ready(function($) { //wrapper
$(".pref").change(function() { //event
var this2 = this; //use in callback
$.post(my_ajax_obj.ajax_url, { //POST request
_ajax_nonce: my_ajax_obj.nonce, //nonce
action: "my_tag_count", //action
title: this.value //data
}, function(data) { //callback
this2.nextSibling.remove(); //remove current title
$(this2).after(data); //insert server response
}
);
} );
} );
该脚本可以输出到网页上的块中,也可以包含在其自己的文件中。该文件可以驻留在 Internet 上的任何位置,但大多数插件开发人员将其放置在/js/
插件主文件夹的子文件夹中。除非你有理由不这样做,否则你最好遵循惯例。对于这个例子,我们将命名我们的文件myjquery.js
服务器端 PHP 和排队
实现 AJAX 通信需要服务器端 PHP 脚本的两个部分。首先,我们需要将 jQuery 脚本排入网页并本地化 jQuery 脚本所需的任何 PHP 值。其次是 AJAX 请求的实际处理。
入队脚本
本节介绍了 WordPress 中 AJAX 的两个主要怪癖,这些怪癖可能会让刚接触 WordPress 的经验丰富的程序员感到困惑。一是需要将脚本排入队列,以使元链接正确显示在页面的头部部分。另外就是所有的AJAX请求都需要通过wp-admin/admin-ajax.php
. 切勿直接向您的插件页面发送请求。
入队
使用该函数wp_enqueue_script()
让 WordPress 在页面部分插入指向脚本的元链接。切勿在标题模板中对此类链接进行硬编码。作为插件开发人员,您无法随时访问标头模板,但无论如何这条规则都值得一提。
enqueue 函数接受五个参数,如下所示:
- $handle是脚本的名称。
- $src定义脚本所在的位置。为了可移植性,请使用
plugins_url()
构建正确的 URL。如果您要在脚本中加入除插件之外的其他内容,请使用一些相关函数来创建正确的 URL – 切勿对其进行硬编码 - $deps是一个数组,可以处理您的新脚本所依赖的任何脚本,例如 jQuery。由于我们使用 jQuery 发送 AJAX 请求,因此您至少需要
'jquery'
在数组中列出。 - $ver可让您列出版本号。
- $args
in_footer
定义页脚打印(通过键)和脚本加载策略(通过键)的参数数组,strategy
例如defer
或async
。$in_footer
从 WordPress 版本 6.3 开始,这将替换/重载该参数。
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()
和函数通过 WordPress 6.3 中引入的新数组参数中的键wp_enqueue_script()
来指定脚本加载策略。strategy
$args
支持的策略如下:
- 推迟
'strategy' => 'defer'
通过向 $args 参数指定数组键值对来添加。- 通过 defer script 属性标记为延迟执行的脚本仅在 DOM 树完全加载后(但在 和
DOMContentLoaded
window load 事件之前)执行。与异步脚本不同,延迟脚本的执行顺序与在 DOM 中打印/添加的顺序相同。
- 异步
'strategy' => 'async'
通过向参数指定数组键值对来添加$args
。- 通过 script
async
属性标记为异步执行的脚本在浏览器加载后立即执行。异步脚本没有保证的执行顺序,因为脚本 B(尽管在脚本 A 之后添加到 DOM)可能会首先执行,因为它可能在脚本 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
。在我们的示例中,此数据是一个随机数和 的完整 URL admin-ajax.php
。分配对象属性和创建全局 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
请注意如何使用我们的脚本句柄,以便将全局对象分配给正确的脚本。该对象对于我们的脚本来说是全局的,而不是对于所有脚本来说。还可以从用于将脚本排队的同一个挂钩调用本地化。创建随机数也是如此,尽管该特定函数几乎可以在任何地方调用。所有这些组合在一个钩子回调中,如下所示:
add_action( 'admin_enqueue_scripts', 'my_enqueue' );
/**
* Enqueue my scripts and assets.
*
* @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' ),
)
);
}
current_user_can()
并记住与能力或角色一起使用来完成安全性。AJAX 操作
服务器端 PHP 代码的另一个主要部分是实际的 AJAX 处理程序,它接收 POST 数据,对其执行某些操作,然后将适当的响应发送回浏览器。它采用 WordPress操作挂钩的形式。您使用哪个挂钩标记取决于用户是否登录以及您的 jQuery 脚本作为action: value 传递的值。
笔记:$_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' );
/**
* Handles my AJAX request.
*/
function my_ajax_handler() {
// Handle the ajax request here
wp_die(); // All ajax handlers die when finished
}
AJAX 处理程序应该做的第一件事是验证 jQuery 发送的随机数check_ajax_referer()
,该随机数应该与脚本排队时本地化的值相同。
check_ajax_referer( 'title_example' );
提供的参数必须与之前提供的参数相同wp_create_nonce()
。如果随机数没有签出,该函数就会终止。如果这是一个真正的随机数,那么既然它被使用了,那么它的价值就不再有任何好处了。然后,您将生成一个新的并将其发送到回调脚本,以便它可以用于下一个请求。但由于 WordPress 随机数的有效期为 24 小时,因此您只需检查它即可。
数据
随着随机数的出现,我们的处理程序可以处理$_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
函数,允许在 JS 中触发适当的 done() 或 failed() 回调。
其他
只要发送者和接收者协调一致,您就可以以任何您喜欢的方式传输数据。逗号分隔或制表符分隔等文本格式是多种可能性之一。对于少量数据,发送原始流可能就足够了。这就是我们将在示例中执行的操作 – 我们将发送实际的替换 HTML,仅此而已。
echo esc_html( $title ) . ' (' . $the_query->post_count . ') ';
在现实世界的应用程序中,您必须考虑操作可能因某种原因失败的可能性,例如数据库服务器可能已关闭。响应应该考虑到这种意外情况,并且接收响应的 jQuery 脚本应该采取相应的行动,也许会告诉用户稍后再试。
死
当处理程序完成所有任务后,它需要死亡。如果您使用WP_Ajax_Response或 wp_send_json* 函数,则会自动为您处理。如果没有,只需使用 WordPresswp_die()
功能即可。
AJAX 处理程序摘要
我们示例的完整 AJAX 处理程序如下所示:
/**
* AJAX handler using JSON
*/
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 . ') ' );
}
/**
* AJAX handler not using JSON.
*/
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(); // All ajax handlers should die when finished
}
心跳API
Heartbeat API 是 WordPress 内置的一个简单的服务器轮询 API,允许近乎实时的前端更新。
怎么运行的
页面加载时,客户端心跳代码会设置一个间隔(称为“tick”),每 15-120 秒运行一次。当它运行时,heartbeat 收集数据并通过 jQuery 事件发送,然后将其发送到服务器并等待响应。在服务器上,admin-ajax 处理程序获取传递的数据,准备响应,过滤响应,然后以 JSON 格式返回数据。客户端接收此数据并触发最终 jQuery 事件以指示数据已收到。
自定义Heartbeat事件的基本流程是:
- 向要发送的数据添加附加字段(JS
heartbeat-send
事件) - 检测 PHP 中的发送字段,并添加额外的响应字段(
heartbeat_received
过滤器) - 在JS中处理返回的数据(JS
heartbeat-tick
)
(您可以选择仅使用其中一个或两个事件,具体取决于您需要的功能。)
使用API
使用心跳 API 需要两个独立的功能:JavaScript 中的发送和接收回调,以及 PHP 中处理传递数据的服务器端过滤器。
发送数据到服务器
当 Heartbeat 向服务器发送数据时,您可以包含自定义数据。这可以是您想要发送到服务器的任何数据,也可以是一个简单的真值来指示您正在等待数据。
jQuery( document ).on( 'heartbeat-send', function ( event, data ) {
// Add additional data to Heartbeat data.
data.myplugin_customfield = 'some_data';
});
在服务器上接收并响应
然后,您可以在服务器端检测此数据,并向响应添加其他数据。
/**
* Receive Heartbeat data and respond.
*
* Processes data received via a Heartbeat request, and returns additional data to pass back to the front end.
*
* @param array $response Heartbeat response data to pass back to front end.
* @param array $data Data received from the front end (unslashed).
*
* @return array
*/
function myplugin_receive_heartbeat( array $response, array $data ) {
// If we didn't receive our data, don't send any back.
if ( empty( $data['myplugin_customfield'] ) ) {
return $response;
}
// Calculate our data and pass it back. For this example, we'll hash it.
$received_data = $data['myplugin_customfield'];
$response['myplugin_customfield_hashed'] = sha1( $received_data );
return $response;
}
add_filter( 'heartbeat_received', 'myplugin_receive_heartbeat', 10, 2 );
处理响应
回到前端,您可以处理接收这些数据。
jQuery( document ).on( 'heartbeat-tick', function ( event, data ) {
// Check for our data, and use it.
if ( ! data.myplugin_customfield_hashed ) {
return;
}
alert( 'The hash is ' + data.myplugin_customfield_hashed );
});
并非每个功能都需要所有这三个步骤。例如,如果您不需要向服务器发送任何数据,则可以仅使用后两个步骤。
概括
以下是前面讨论中的所有示例代码片段,它们被组装成两个完整的代码页:一个用于 jQuery,另一个用于 PHP。
PHP
此代码位于您的插件页面之一。
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',
true
);
$title_nonce = wp_create_nonce( 'title_example' );
wp_localize_script(
'ajax-script',
'my_ajax_obj',
array(
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => $title_nonce,
)
);
}
add_action( 'wp_ajax_my_tag_count', 'my_ajax_handler' );
function my_ajax_handler() {
check_ajax_referer( 'title_example' );
$title = wp_unslash( $_POST['title'] );
update_user_meta( get_current_user_id(), 'title_preference', $title );
$args = array(
'tag' => $title,
);
$the_query = new WP_Query( $args );
echo esc_html( $title ) . ' (' . $the_query->post_count . ') ';
wp_die(); // all ajax handlers should die when finished
}
jQuery
js/myjquery.js
此代码位于插件文件夹下的文件中。
jQuery(document).ready(function($) { //wrapper
$(".pref").change(function() { //event
var this2 = this; //use in callback
$.post(my_ajax_obj.ajax_url, { //POST request
_ajax_nonce: my_ajax_obj.nonce, //nonce
action: "my_tag_count", //action
title: this.value //data
}, function(data) { //callback
this2.nextSibling.remove(); //remove the current title
$(this2).after(data); //insert server response
});
});
});
存储首选项后,生成的帖子计数将添加到所选标题中。
更多信息
Cron计划任务
Cron
什么是 WP-Cron
WP-Cron 是 WordPress 处理调度基于时间的任务的方式。WordPress 的一些核心功能(例如检查更新和发布预定帖子)都利用了 WP-Cron。名称中的“Cron”部分来自 UNIX 系统上可用的基于时间的 cron 任务调度系统。
WP-Cron 的工作原理是在每次页面加载时检查计划任务列表,以查看需要运行的任务。任何由于运行而产生的任务都将在该页面加载期间被调用。
如果您将任务安排在下午 2:00,并且直到下午 5:00 才会发生页面加载,则可能会出现安排错误。
为什么使用 WP-Cron
- WordPress 核心和许多插件需要一个调度系统来执行基于时间的任务。然而,许多托管服务是共享的,并且不提供对系统调度程序的访问。
- 使用 WordPress API 是设置计划任务的一种比从 WordPress 外部访问系统计划程序更简单的方法。
- 使用系统调度程序,如果时间过去而任务没有运行,则不会重新尝试。使用 WP-Cron,所有计划任务都被放入队列中,并将在下一个机会(意味着下一个页面加载)运行。因此,虽然您不能 100% 确定任务何时运行,但您可以 100% 确定它最终会运行。
了解 WP-Cron 调度
与在特定时间安排任务的传统系统 cron 不同(例如“每小时的整点后 5 分钟”),WP-Cron 使用间隔来模拟系统 cron。
WP-Cron 有两个参数:第一个任务的时间,以及重复该任务的时间间隔(以秒为单位)。例如,如果您计划任务在下午 2:00 开始,时间间隔为 300 秒(五分钟),则该任务将首先在下午 2:00 运行,然后在下午 2:05 再次运行,然后在下午 2:10 再次运行,并且依此类推,每五分钟一次。
为了简化计划任务,WordPress 提供了一些默认间隔和添加自定义间隔的简单方法。
WordPress 提供的默认间隔是:
- 每小时
- 每天两次
- 日常的
- 每周(自 WP 5.4 起)
自定义间隔
要添加自定义间隔,您可以创建一个过滤器,例如:
add_filter( 'cron_schedules', 'example_add_cron_interval' );
function example_add_cron_interval( $schedules ) {
$schedules['five_seconds'] = array(
'interval' => 5,
'display' => esc_html__( 'Every Five Seconds' ), );
return $schedules;
}
这个过滤器函数创建了一个新的间隔,允许我们每五秒运行一次 cron 任务。
注意:所有间隔均以秒为单位。
调度 WP Cron 事件
WP Cron 系统使用钩子来添加新的计划任务。
添加钩子
为了让您的任务运行,您必须创建自己的自定义挂钩,并为该挂钩指定要执行的函数的名称。这是非常重要的一步。忘记它,你的任务将永远不会运行。
以下示例将创建一个钩子。第一个参数是您正在创建的钩子的名称,第二个参数是要调用的函数的名称。
add_action( 'bl_cron_hook', 'bl_cron_exec' );
笔记:您可以在此处阅读有关操作的更多信息。
安排任务
需要注意的是,WP-Cron 是一个简单的任务调度程序。众所周知,任务是通过创建的钩子添加的,该钩子是为了调用运行所需任务的函数而创建的。但是,如果多次调用wp_schedule_event()
,即使使用相同的挂钩名称,该事件也会被安排多次。如果您的代码在每次页面加载时添加任务,则可能会导致该任务被调度数千次。这不是你想要的。
WordPress 提供了一个名为wp_next_scheduled()的便捷函数 来检查特定的钩子是否已被调度。wp_next_scheduled()
采用一个参数,即钩子名称。它将返回一个包含下一次执行的时间戳的字符串,或者返回 false,表示该任务未安排。它的使用方式如下:
wp_next_scheduled( 'bl_cron_hook' )
安排重复任务是通过wp_schedule_event()完成的。该函数采用三个必需参数和一个附加参数,该参数是一个可以传递给执行 wp-cron 任务的函数的数组。我们将重点关注前三个参数。参数如下:
$timestamp
– 该任务第一次执行的 UNIX 时间戳$recurrence
– 任务重复执行的时间间隔名称(以秒为单位)$hook
– 我们要调用的自定义挂钩的名称
我们将使用此处创建的 5 秒间隔和上面创建的挂钩,如下所示:
wp_schedule_event( time(), 'five_seconds', 'bl_cron_hook' );
请记住,我们需要首先确保任务尚未安排。所以我们将调度代码包装在一个检查中,如下所示:
if ( ! wp_next_scheduled( 'bl_cron_hook' ) ) {
wp_schedule_event( time(), 'five_seconds', 'bl_cron_hook' );
}
取消计划任务
当您不再需要计划的任务时,您可以使用wp_unschedule_event()取消计划任务 。该函数采用以下两个参数:
$timestamp
– 下一次任务发生的时间戳$hook
– 要调用的自定义钩子的名称
此函数不仅会取消安排由时间戳指示的任务,还会取消安排该任务的所有未来发生。由于您可能不知道下一个任务的时间戳,因此还有另一个方便的函数wp_next_scheduled() 可以为您找到它。wp_next_scheduled()
采用一个参数(我们关心的):
$hook
– 执行任务时调用的钩子的名称
将它们放在一起,代码如下所示:
$timestamp = wp_next_scheduled( 'bl_cron_hook' );
wp_unschedule_event( $timestamp, 'bl_cron_hook' );
当您不再需要任务时取消计划任务非常重要,因为 WordPress 将继续尝试执行这些任务,即使它们不再使用(或者甚至在您的插件已停用或删除之后)。需要记住取消任务安排的一个重要位置是插件停用时。
不幸的是,WordPress.org 插件目录中有许多插件无法自行清理。如果您发现这些插件之一,请告知作者更新其代码。WordPress 提供了一个名为register_deactivation_hook()的函数 ,允许开发人员在插件停用时运行某个函数。设置非常简单,如下所示:
register_deactivation_hook( __FILE__, 'bl_deactivate' );
function bl_deactivate() {
$timestamp = wp_next_scheduled( 'bl_cron_hook' );
wp_unschedule_event( $timestamp, 'bl_cron_hook' );
}
笔记:您可以在此处阅读有关激活和停用挂钩的更多信息。
将 WP-Cron 挂接到系统任务计划程序中
如前所述,WP-Cron 不会连续运行,如果有必须按时运行的关键任务,这可能会成为一个问题。有一个简单的解决方案。只需将系统的任务调度程序设置为按您希望的时间间隔(或在需要的特定时间)运行即可。最简单的解决方案是使用工具向文件发出 Web 请求wp-cron.php
。
在系统上安排任务后,还需要完成一个步骤。WordPress 将在每次页面加载时继续运行 WP-Cron。这不再是必要的,并且会导致服务器上额外的资源使用。可以在文件中禁用 WP-Cron wp-config.php
。打开wp-config.php
文件进行编辑并添加以下行:
define( 'DISABLE_WP_CRON', true );
视窗
Windows 将其基于时间的调度系统称为任务调度程序。可以通过控制面板中的管理工具访问它。
设置任务的方式因服务器设置而异。一种方法是使用 PowerShell 和基本任务。创建基本任务后,可以使用以下命令调用 WordPress Cron 脚本。
powershell "Invoke-WebRequest http://YOUR_SITE_URL/wp-cron.php"
MacOS 和 Linux
Mac OS X 和 Linux 都使用 cron 作为基于时间的调度系统。通常是使用命令从终端进行访问crontab -e
。应该注意的是,任务将以普通用户或 root 身份运行,具体取决于运行命令的系统用户。
Cron 有一个需要遵循的特定语法,包含以下部分:
- 分钟
- 小时
- 一个月中的哪一天
- 月
- 星期几
- 要执行的命令

如果无论哪个时间段都应运行命令,则应使用星号 (*)。例如,如果您想每 15 分钟运行一次命令,无论小时、日期或月份,它会如下所示:
*/15 * * * * command
许多服务器已wget
安装,这是一个调用 WordPress Cron 脚本的简单工具。
wget --delete-after http://YOUR_SITE_URL/wp-cron.php
每天晚上午夜触发的对站点 WordPress Cron 的每日调用可能类似于:
0 0 * * * wget --delete-after http://YOUR_SITE_URL/wp-cron.php
WP-Cron 测试
WP-CLI
可以使用WP-CLI测试 Cron 作业。wp cron event list
它提供了和等命令wp cron event run {job name}
。查看文档以获取更多详细信息。
WP-Cron 管理插件
WordPress.org 插件目录上提供了多个插件,用于查看、编辑和控制站点上计划的 cron 事件和可用计划。
_get_cron_array()
该_get_cron_array()
函数返回所有当前计划的 cron 事件的数组。如果您需要检查事件的原始列表,请使用此函数。
wp_get_schedules()
该wp_get_schedules()
函数返回可用事件重复计划的数组。如果您需要检查可用计划的原始列表,请使用此功能。
国际化
国际化
什么是国际化?
国际化是开发插件的过程,以便可以轻松地将其翻译成其他语言。国际化常缩写为i18n
(因为字母i和n之间有18个字母)。
为什么国际化很重要?
WordPress 在世界各地使用,在英语不是主要语言的国家/地区。WordPress 插件中的字符串需要以特殊方式编码,以便可以轻松翻译成其他语言。作为开发人员,您可能无法为所有用户提供本地化(即需要对文本和其他内容进行更改,例如特定于给定区域设置(位置)的数字格式);但是,翻译人员可以成功本地化主题,而无需修改源代码本身。
进一步阅读“如何国际化你的插件”。
资源
- 视频:i18n:为世界准备您的 WordPress 主题
- 视频:国际化:适用于全世界的插件和主题 幻灯片
- 视频:日本大业:主题和国际化指南
- 视频:迷失翻译 — i18n 和 WordPress
- 国际化和本地化您的 WordPress 主题
- 国际化:你可能做错了
- 更多国际化乐趣
- 使用wp_localize_script,太棒了
- 理解_n_noop()
- 语言包 101 – 准备工作
- 翻译 WordPress 插件和主题:不要太聪明
- 如何加载主题和插件翻译
- 多语言手册:插件/主题作者指南
本土化
什么是本地化?
本地化描述了翻译国际化插件的后续过程。Localization 通常缩写为l10n
(因为 l 和 n 之间有 10 个字母。)
本地化文件
(便携式对象模板)文件
该文件包含插件中的原始字符串(英文)。
(便携式对象)文件
每个翻译人员都会获取该POT
文件并将各个msgstr
部分翻译成自己的语言。结果是一个PO
与 a 格式相同的文件POT
,但带有翻译和一些特定的标头。每种语言都有一个PO
文件。
(机器对象)文件
从每个翻译的PO
文件中MO
构建一个文件。这些是 gettext 函数实际使用的机器可读的二进制文件(它们不关心.POT
或.PO
文件),并且是文件的“编译”版本PO
。转换是使用msgfmt
命令行工具完成的。一般来说,一个应用程序可能会相应地使用多个大型逻辑可翻译模块和不同的MO
文件。文本域是每个模块的句柄,每个模块都有不同的MO
文件。
生成POT
文件
该POT
文件是您需要交给翻译人员的文件,以便他们可以完成工作。和文件可以轻松地互换重命名以更改文件类型,而不会出现任何问题POT
。 PO
有几种方法可以POT
为您的插件生成文件:
WP-CLI
安装WP-CLIwp i18n make-pot
并根据文档使用命令。
诗歌
翻译时也可以在本地使用Poedit。这是适用于所有主要操作系统的开源工具。免费的Poedit默认版本支持使用Gettext功能手动扫描所有源代码。它的专业版还具有一键扫描 WordPress 插件的功能。生成PO
文件后,您可以将文件重命名为POT
. 如果MO
生成了,那么您可以删除该文件,因为不需要它。如果您没有专业版,您可以轻松获得空白 POT并将其用作POT file
. 将空白放入POT
语言文件夹后,您可以在 Poedit 中单击“更新”以POT
使用您的字符串更新文件。
繁重任务
您甚至可以使用一些繁重的任务来创建 POT。 grunt-wp-i18n 和 grunt-pot。设置 grunt 的步骤超出了本文档的范围,但请注意这是可能的。以下是一个示例 Grunt.js 和 package.json,您可以将其放置在插件的根目录中。
翻译PO
文件
将翻译后的文件另存为 my-plugin-{locale}.mo
. 区域设置是语言代码和/或国家/地区代码。例如,德语的区域设置是 de_DE
。从上面的代码示例中,文本域是“my-plugin”,因此德语 MO 和 PO 文件应命名为 my-plugin-de_DE.mo
和 my-plugin-de_DE.po
。有关语言和国家/地区代码的更多信息,请参阅 以您的语言安装 WordPress。
翻译文件的方法有多种PO
。
手动
您可以使用文本编辑器输入翻译。在文本编辑器中,它看起来像这样。
#: plugin-name.php:123
msgid "Page Title"
msgstr ""
您在引号之间输入翻译。对于德语翻译,它看起来像这样。
#: plugin-name.php:123
msgid "Page Title"
msgstr "Seitentitel"
诗歌
翻译时也可以使用Poedit。免费的Poedit默认版本支持使用Gettext功能手动扫描所有源代码。它的专业版还具有一键扫描 WordPress 插件和主题的功能。
在线服务
第三种选择是使用在线翻译服务。一般的想法是,您上传POT
文件,然后您可以授予用户或翻译人员翻译您的插件的权限。这使您可以跟踪更改,始终拥有最新的翻译并减少两次翻译。
以下是一些可用于在线翻译 PO 文件的工具:
生成MO文件
命令行
msgfmt
用于创建 MO 文件。msgfmt
是 Gettext 包的一部分。否则可以使用命令行。典型的 msgfmt
命令如下所示:
Unix操作系统
msgfmt -o filename.mo filename.po
Windows 操作系统
msgfmt -o filename.mo filename.po
如果您有很多 PO
文件需要一次转换,您可以批量运行它。例如,使用 bash
命令:
Unix操作系统
# Find PO files, process each with msgfmt and rename the result to MO
for file in `find . -name "*.po"` ; do msgfmt -o ${file/.po/.mo} $file ; done
Windows 操作系统
对于 Windows,您需要先安装 Cygwin。
创建一个名为的文件potomo.sh
并将以下内容放入其中:
#! /bin/sh
# Find PO files, process each with msgfmt and rename the result to MO
for file in `/usr/bin/find . -name '*.po'` ; do /usr/bin/msgfmt -o ${file/.po/.mo} $file ; done
您可以在命令行中运行此命令。
cd C:/path/to/language/folder/my-plugin/languages & C:/cygwin/bin/bash -c /cygdrive/c/path/to/script/directory/potomo.sh
诗歌
msgfmt
还集成在Poedit中 ,允许您使用它来生成 MO 文件。首选项中有一个设置,您可以在其中启用或禁用它。

咕噜任务
grunt-po2mo可以转换所有文件。
良好翻译的秘诀
不要逐字翻译,要有机翻译
作为双语或多语者,您无疑知道您所说的语言具有不同的结构、节奏、语气和语调变化。翻译后的消息不需要采用与英语消息相同的结构:采用所呈现的想法,并提出一条消息,以自然的方式为目标语言表达相同的内容。这就是创建同等消息和等效消息之间的区别:不要复制,而是替换。即使消息中有更多的结构性项目,如果您认为这对目标受众来说更合乎逻辑或更适合,您也可以创造性地进行调整和更改。
尝试保持相同程度的正式(或非正式)
每条消息都有不同程度的正式或非正式。您必须自己(或与您的团队)一起弄清楚目标语言中每条消息的正式或非正式程度,但 WordPress 消息(特别是信息性消息)往往有礼貌的非正式语气。英语的语气。尝试在您的文化背景下用目标语言完成同等的任务。
不要使用俚语或特定于受众的术语
博客中会出现一定数量的术语,但不要使用只有“圈内”人群才会使用的口语。如果不熟悉的博主要以您的语言安装 WordPress,他们会知道该术语的含义吗?pingback、trackback 和 feed 之类的词是该规则的例外;它们是通常难以翻译的术语,许多译者选择保留英语。
阅读其他软件的您语言的本地化版本
如果您遇到困难或需要指导,请尝试阅读其他流行软件工具的翻译,以了解常用术语、如何处理正式性等。当然,WordPress 有自己的语气和感觉,所以请保留这一点当您阅读其他本地化版本时,请记住这一点,但请随意挖掘 UI 术语等,以保持与您语言中的其他软件的一致性。
使用本地化
将本地化文件放在语言文件夹中,可以放在插件languages
文件夹中,也可以从 WordPress 3.7 开始放在插件languages
文件夹中,通常位于wp-content
. 完整路径将是wp-content/languages/plugins/my-plugin-fr_FR.mo
.
您可以在“常规设置”中更改语言。如果您没有看到此选项,或者未列出您要切换到的语言,请手动执行此操作:
- 定义
WPLANG
内部wp-config.php
为您选择的语言。例如,如果您想使用法语,则需要:define ('WPLANG', 'fr_FR');
- 转到
wp-admin/options-general.php
或“设置”->“常规” - 在“站点语言”下拉列表中选择您的语言
- 去
wp-admin/update-core.php
- 如果可用,请点击“更新翻译”
- 下载核心翻译文件(如果可用)
资源
- 为您的主题或插件创建 .pot 文件
- 如何国际化 WordPress 插件
- 翻译你的主题
- 空白 WordPress 罐子
- 改进的 i18n WordPress 工具
- 如何快速更新翻译
- GitHub/Transifex 之间的工作流程
- 要点:完成本地化 Grunt 任务
- WordPress.tv标签:i18n、国际化和翻译
如何国际化你的插件
为了使字符串在应用程序中可翻译,您必须将原始字符串包装在对一组特殊函数之一的调用中。这些函数统称为“gettext”。
Gettext简介
WordPress 使用i18n 的gettext库和工具,但不是直接使用:有一组专门为了启用字符串翻译而创建的特殊函数。下面列出了这些功能。这些是您应该在插件中使用的函数。
文本域
使用文本域来表示属于您的插件的所有文本。文本域是一个唯一标识符,以确保 WordPress 可以区分所有加载的翻译。这提高了可移植性,并且可以更好地与现有的 WordPress 工具配合使用。
文本域必须与slug
插件的文本域匹配。如果您的插件是一个名为的单个文件my-plugin.php
或它包含在一个名为的文件夹中,则my-plugin
域名必须是my-plugin
. 如果您的插件托管在 wordpress.org 上,则它必须是插件 URL 的 slug 部分 ( wordpress.org/plugins/
)。
文本域名必须使用破折号而不是下划线、小写且不含空格。
文本域还需要添加到插件标头中。即使插件被禁用,WordPress也会使用它来国际化您的插件元数据。文本域应与加载文本域时使用的文本域相同。
标头示例:
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
*/
笔记:再次将“my-plugin”更改为插件的名称。
笔记:从 WordPress 4.6 开始,Text Domain
标头是可选的,因为它必须与插件 slug 相同。包含它没有什么坏处,但不是必需的。
域路径
域路径定义插件翻译的位置。这有一些用途,特别是即使插件被禁用,WordPress 也知道在哪里可以找到翻译。默认为您的插件所在的文件夹。
例如,如果翻译位于languages
插件内调用的文件夹中,则域路径/languages
必须用第一个斜杠编写:
标头示例:
/*
* Plugin Name: My Plugin
* Author: Plugin Author
* Text Domain: my-plugin
* Domain Path: /languages
*/
笔记:Domain Path
如果插件位于官方 WordPress 插件目录中,则可以省略标头 。
基本字符串
对于基本字符串(即没有占位符或复数的字符串),请使用__()
. 它返回其参数的翻译:
__( 'Blog Options', 'my-plugin' );
警告:不要对 gettext 函数的文本域部分使用变量名或常量。例如: 不要将此作为快捷方式:
__( '翻译我。' , $text_domain );
要回显检索到的翻译,请使用_e()
. 所以不要写:
echo __( 'WordPress is the best!', 'my-plugin' );
您可以使用:
_e( 'WordPress is the best!', 'my-plugin' );
变量
如果你有一个像下面这样的字符串怎么办:
echo 'Your city is $city.'
在这种情况下,$city
是一个变量,不应该是翻译的一部分。解决方案是使用变量的占位符以及printf
函数族。尤其有帮助的是printf
和sprintf
。正确的解决方案如下所示:
printf(
/* translators: %s: Name of a city */
__( 'Your city is %s.', 'my-plugin' ),
$city
);
请注意,这里用于翻译的字符串只是 template "Your city is %s."
,它在源代码和运行时都是相同的。
另请注意,翻译人员会收到提示,以便他们了解占位符的上下文。
如果字符串中有多个占位符,建议您使用参数交换。在这种情况下,(')
字符串周围的单引号是强制性的,因为双引号(")
会告诉 php 将 the 解释$s
为s
变量,这不是我们想要的。
printf(
/* translators: 1: Name of a city 2: ZIP code */
__( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ),
$city,
$zipcode
);
这里,邮政编码显示在城市名称后面。在某些语言中,以相反的顺序显示邮政编码和城市会更合适。在上面的示例中使用%s前缀可以实现这种情况。因此可以写出一个翻译:
printf(
/* translators: 1: Name of a city 2: ZIP code */
__( 'Your zip code is %2$s, and your city is %1$s.', 'my-plugin' ),
$city,
$zipcode
);
重要的!下面的代码是不正确的:
// This is incorrect do not use.
_e( "Your city is $city.", 'my-plugin' );
用于翻译的字符串是从源中提取的,因此翻译者将得到这个短语来翻译:"Your city is $city."
。
然而,在应用程序中,_e
将使用类似的参数进行调用"Your city is London."
,并且gettext
不会找到该参数的合适翻译,并将返回其参数:"Your city is London."
。不幸的是,它的翻译不正确。
复数
基本复数
如果您的字符串随着项目数量的变化而变化,您将需要一种方法来在翻译中反映这一点。例如,在英语中,有"One comment"
和"Two comments"
。在其他语言中,您可以有多种复数形式。要在 WordPress 中处理此问题,请使用该_n()
函数。
printf(
_n(
'%s comment',
'%s comments',
get_comments_number(),
'my-plugin'
),
number_format_i18n( get_comments_number() )
);
_n()
接受 4 个参数:
- 单数 – 字符串的单数形式(请注意,在某些语言中,它可以用于除 1 之外的数字,因此
'%s item'
应该使用 代替'One item'
) - plural——字符串的复数形式
- count – 对象的数量,这将决定是否应返回单数或复数形式(有些语言具有远远超过 2 种形式)
- 文本域 – 插件文本域
函数的返回值是正确的翻译形式,对应于给定的计数。
请注意,某些语言对其他数字使用单数形式(例如 21、31 等,很像英语中的“21st”、“31st”)。如果您想对单数进行特殊处理,请特别检查:
if ( 1 === $count ) {
printf( esc_html__( 'Last thing!', 'my-text-domain' ), $count );
} else {
printf( esc_html( _n( '%d thing.', '%d things.', $count, 'my-text-domain' ) ), $count );
}
另请注意,该$count
参数通常会使用两次。首先$count
传递给_n()
以确定要使用哪个翻译字符串,然后$count
传递给printf()
将数字替换为翻译字符串。
复数稍后完成
首先使用_n_noop()
或设置复数字符串_nx_noop()
。
$comments_plural = _n_noop(
'%s comment.',
'%s comments.'
);
然后,在代码的稍后位置,您可以使用它translate_nooped_plural()
来加载字符串。
printf(
translate_nooped_plural(
$comments_plural,
get_comments_number(),
'my-plugin'
),
number_format_i18n( get_comments_number() )
);
通过上下文消除歧义
有时,一个术语会在多种上下文中使用,尽管它在英语中是同一个单词,但在其他语言中必须有不同的翻译。例如,该词Post
既可以用作动词"Click here to post your comment"
,也可以用作名词"Edit this post"
。在这种情况下,应该使用_x()
or函数。_ex()
它类似于__()
and _e()
,但它有一个附加参数 - 上下文:
_x( 'Post', 'noun', 'my-plugin' );
_x( 'Post', 'verb', 'my-plugin' );
在这两种情况下使用此方法,我们将获得原始版本的注释字符串,但译者将看到两个翻译注释字符串,每个字符串都在不同的上下文中。
请注意,与 类似__()
,_x()
也有一个echo
版本:_ex()
。前面的例子可以写成:
_ex( 'Post', 'noun', 'my-plugin' );
_ex( 'Post', 'verb', 'my-plugin' );
使用您认为可以增强可读性和易于编码的任何一种。
描述
这样翻译人员就知道如何翻译字符串,就像__( 'g:i:s a' )
您可以在源代码中添加澄清注释一样。它必须以单词开头translators:
,并且是 gettext 调用之前的最后一个 PHP 注释。这是一个例子:
/* translators: draft saved date format, see http://php.net/date */
$saved_date_format = __( 'g:i:s a' );
它还用于解释字符串中的占位符,例如_n_noop( 'Version %1$s addressed %2$s bug.','Version %1$s addressed %2$s bugs.' )
.
/* translators: 1: WordPress version number, 2: plural number of bugs. */
_n_noop( 'Version %1$s addressed %2$s bug.','Version %1$sstrong> addressed %2$s bugs.' );
换行符
Gettext 不喜欢r
可翻译字符串中的 (ASCII 代码:13),因此请避免使用它并改用它n
。
空字符串
空字符串保留供内部 Gettext 使用,您不得尝试国际化空字符串。它也没有任何意义,因为翻译者看不到任何上下文。
如果您有一个有效的用例来国际化空字符串,请添加上下文以帮助翻译人员并与 Gettext 系统保持一致。
转义字符串
最好转义所有字符串,这样翻译器就无法运行恶意代码。有一些转义函数与国际化函数集成在一起。
本地化功能
基本功能
翻译和转义功能
需要翻译并在 html 标记的属性中使用的字符串必须进行转义。
日期和数字函数
编写字符串的最佳实践
以下是编写字符串的最佳实践
- 使用得体的英语风格——尽量减少俚语和缩写。
- 使用整个句子——大多数语言的词序与英语不同。
- 在段落中拆分 - 合并相关句子,但不要在一个字符串中包含整页文本。
- 不要在可翻译短语中留下前导或尾随空格。
- 假设翻译后字符串的长度可以加倍
- 避免不寻常的标记和不寻常的控制字符 - 不要包含文本周围的标签
- 不要将不必要的 HTML 标记放入翻译后的字符串中
- 不要留下 URL 进行翻译,除非它们有其他语言的版本。
- 将变量作为占位符添加到字符串中,就像在某些语言中占位符会更改位置一样。
printf(
__( 'Search results for: %s', 'my-plugin' ),
get_search_query()
);
- 使用格式字符串而不是字符串连接——翻译短语而不是单词——
printf( __( 'Your city is %1$s, and your zip code is %2$s.', 'my-plugin' ), $city, $zipcode );
总是比:__( 'Your city is ', 'my-plugin' ) . $city . __( ', and your zip code is ', 'my-plugin' ) . $zipcode;
- 尝试使用相同的单词和相同的符号,这样就不需要翻译多个字符串,例如
__( 'Posts:', 'my-plugin' );
和__( 'Posts', 'my-plugin' );
将文本域添加到字符串
您必须将 Text 域作为参数添加到每个__()
,_e()
和__n()
gettext 调用,否则您的翻译将无法工作。
例子:
__( 'Post' )
应该成为__( 'Post', 'my-theme' )
_e( 'Post' )
应该成为_e( 'Post', 'my-theme' )
_n( '%s post', '%s posts', $count )
应该成为_n( '%s post', '%s posts', $count, 'my-theme' )
如果您的插件中的某些字符串也在 WordPress 核心中使用(例如“设置”),您仍然应该向它们添加您自己的文本域,否则如果核心字符串发生更改(这种情况发生),它们将变为未翻译。
如果在编写代码时不连续添加文本域,则手动添加文本域可能会成为一种负担,这就是为什么您可以自动执行此操作:
- 将脚本下载
add-textdomain.php
到您要添加文本域的文件所在的文件夹 - 在命令行中移动到文件所在的目录
- 运行以下命令创建一个添加了文本域的新文件:
php add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
如果您希望将其放在add-textdomain.php
不同的文件夹中,则只需在命令中定义位置即可。
php /path/to/add-textdomain.php my-plugin my-plugin.php > new-my-plugin.php
如果您不想输出新文件,请使用此命令:
php add-textdomain.php -i my-plugin my-plugin.php
如果要更改目录中的多个文件,还可以将目录传递给脚本:
php add-textdomain.php -i my-plugin my-plugin-directory
完成后,文本域将被添加到文件中所有 gettext 调用的末尾。如果存在现有文本域,则不会被替换。
加载文本域
可以使用 加载翻译load_plugin_textdomain
,例如:
add_action( 'init', 'wpdocs_load_textdomain' );
function wpdocs_load_textdomain() {
load_plugin_textdomain( 'wpdocs_textdomain', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
WordPress.org 上的插件
笔记:由于 WordPress 4.6 翻译现在以translate.wordpress.org为优先级,因此通过translate.wordpress.org翻译的插件不再需要load_plugin_textdomain()
。如果您不想添加load_plugin_textdomain()
对插件的调用,则必须将Requires at least:
readme.txt 中的字段设置为 4.6 或更高。
如果您仍然想加载自己的翻译而不是来自 translate 的翻译,则必须使用名为 的钩子过滤器load_textdomain_mofile
。插件目录中的 .mo 文件
示例,并将此代码插入到主插件文件中:/languages/
function my_plugin_load_my_own_textdomain( $mofile, $domain ) {
if ( 'my-domain' === $domain && false !== strpos( $mofile, WP_LANG_DIR . '/plugins/' ) ) {
$locale = apply_filters( 'plugin_locale', determine_locale(), $domain );
$mofile = WP_PLUGIN_DIR . '/' . dirname( plugin_basename( __FILE__ ) ) . '/languages/' . $domain . '-' . $locale . '.mo';
}
return $mofile;
}
add_filter( 'load_textdomain_mofile', 'my_plugin_load_my_own_textdomain', 10, 2 );
处理 JavaScript 文件
查看通用 API 手册的国际化 javascript部分,了解如何正确加载翻译文件。还有古腾堡插件文档页面。
语言包
如果您对语言包以及如何导入translate.wordpress.org感兴趣,请阅读有关翻译的元手册页面。
另请参阅多语言手册中的插件/主题作者指南以翻译您的项目。
国际化安全
在谈论国际化时,安全性常常被忽视,但有一些重要的事情需要牢记。
检查垃圾邮件和其他恶意字符串
当翻译人员向您提交本地化版本时,请务必检查以确保他们的翻译中不包含垃圾邮件或其他恶意词语。您可以使用谷歌翻译 将其翻译翻译回您的母语,以便您可以轻松比较原始字符串和翻译字符串。
转义国际化字符串
您不能相信翻译人员只会在其本地化中添加良性文本;如果他们愿意,他们可以添加恶意 JavaScript 或其他代码。为了防止这种情况发生,像对待任何其他不受信任的输入一样对待国际化字符串非常重要。
如果您要输出字符串,则应该对它们进行转义。
不安全:
_e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' );
安全的:
esc_html_e( 'The REST API content endpoints were added in WordPress 4.7.', 'your-text-domain' );
或者,有些人选择依赖翻译验证机制,而不是在代码中添加转义。验证机制的一个示例是WordPress Polyglots 团队用于translate.wordpress.org 的编辑者角色。这确保了不受信任的贡献者提交的任何翻译在被接受之前都已经过受信任的编辑的验证。
使用 URL 占位符
不要在国际化字符串中包含 URL,因为恶意翻译者可能会将它们更改为指向不同的 URL。相反,请使用printf()或 sprintf()的占位符。
不安全:
_e(
'Please register for a WordPress.org account.',
'your-text-domain'
);
安全的:
printf(
esc_html__( 'Please %1$s register for a WordPress.org account %2$s.', 'your-text-domain' ),
'',
''
);
编译您自己的 .mo 二进制文件
通常翻译人员会将编译后的 .mo 文件与纯文本 .po 文件一起发送,但您应该丢弃他们的 .mo 文件并编译自己的 .mo 文件,因为您无法知道它是否是从相应的 .po 文件编译的,或不同的一个。如果它是针对不同的版本进行编译的,那么它可能会在您不知情的情况下包含垃圾邮件和其他恶意字符串。
使用 PoEdit 生成二进制文件将覆盖 .po 文件中的标头,因此最好从命令行编译它:
msgfmt -cv -o /path/to/output.mo /path/to/input.po
WordPress.org 插件目录
WordPress.org 插件目录
WordPress.org 为任何希望在我们的目录中开发插件的人提供免费托管。
此处托管的所有插件都可以访问:
- 监控统计信息(另请参阅 WordPress.org 插件 API)
- 接收用户的反馈和评论
- 通过免费论坛提供支持
如果您需要在 WordPress.org 上托管的帮助,您可以通过 Slack in 联系插件团队#pluginreview
。
要求
简要概述:
- 插件必须与GNU 通用公共许可证 v2或更高版本兼容 。如果未指定许可证,代码将被视为“GPLv2 或更高版本”。
- 提供的 Subversion存储库必须仅用于功能性 WordPress 插件。
- 必须尊重版权法和商标法。
- 插件和开发者不得做任何非法、不诚实或有道德攻击的事情。这包括垃圾邮件或骚扰。
警报:所有插件和开发人员都必须遵守我们的详细插件指南。
如何 …
如果您刚刚开始,了解如何提交插件、使用 SVN 等会有所帮助。
- …规划、提交和维护您的插件
- …使用 SVN(又名 Subversion)
- …管理您的 readme.txt
- …编写正确的插件头
- ...使用插件资源(标题图像和图标)
- ...接管现有插件
- …使用支持论坛
- …授予用户特殊角色(贡献者、支持者、提交者等)
- …将您的插件转让给新所有者
- …将您的插件添加到块目录中
- 。..管理插件的安全性
- …报告不安全的插件
详细的插件指南
笔记:添加仅阻止插件?请阅读区块特定指南
插件目录
WordPress 插件目录的目标是为所有 WordPress 用户(从非技术人员到开发人员)提供一个安全的地方,以下载与 WordPress 项目目标一致的插件。
为此,我们希望确保开发人员为该目录提交插件的过程简单透明。作为我们不断努力使插件目录包含过程更加透明的一部分,我们创建了一个开发人员指南列表。我们努力为所有开发者创造一个公平的竞争环境。
如果您有改进指南的建议或对此有疑问,请发送电子邮件plugins@wordpress.org
告知我们。
开发商期望
开发人员、所有具有提交访问权限的用户以及所有正式支持插件的用户都应遵守以下准则:
违规可能会导致插件或插件数据(对于先前批准的插件)从目录中删除,直到问题得到解决。根据违规的性质和同行评审的结果,插件数据(例如用户评论和代码)可能无法恢复。重复违规可能会导致作者的所有插件被删除,并且开发人员被禁止在 WordPress.org 上托管插件。
插件开发人员有责任确保他们在 WordPress.org 上的联系信息是最新且准确的,以便他们收到来自插件团队的所有通知。不允许自动回复和路由到支持系统的电子邮件,因为它们历来会阻止人们及时处理电子邮件。
目录中的所有代码都应尽可能安全。安全性是插件开发人员的最终责任,插件目录会尽我们最大的努力强制执行这一点。如果发现插件存在安全问题,它将被关闭,直到问题得到解决。在极端情况下,WordPress 安全团队可能会更新该插件并进行传播,以确保公众的安全。
虽然我们试图尽可能多地解释指南的相关解释,但期望明确涵盖每种情况是不合理的。如果您不确定插件是否可能违反准则,请联系plugins@wordpress.org
并询问。
指南
1.插件必须兼容GNU通用公共许可证
尽管任何与 GPL 兼容的许可证都是可以接受的,但强烈建议使用与 WordPress 相同的许可证——“GPLv2 或更高版本”。所有代码、数据和图像(存储在 WordPress.org 上托管的插件目录中的任何内容)都必须符合 GPL 或 GPL 兼容许可证。包含的第三方库、代码、图像或其他内容必须兼容。有关兼容许可证的具体列表,请阅读gnu.org 上的GPL 兼容许可证列表。
2. 开发者对其插件的内容和操作负责。
插件开发人员有责任确保其插件中的所有文件都符合准则。禁止故意编写代码来规避准则,或恢复他们被要求删除的代码(请参阅#9 非法/不诚实行为)。
开发人员在上传到 SVN 之前应确认所有包含文件(从原始源代码到图像和库)的许可。此外,他们必须遵守其插件使用的所有第三方服务和 API 的使用条款。如果无法验证库的许可或 API 的条款,则无法使用它们。
3. 插件的稳定版本必须可从 WordPress 插件目录页面获取。
WordPress.org 分发的唯一版本的插件是该目录中的版本。尽管人们可能在其他地方开发代码,但用户将从目录下载,而不是从开发环境下载。
通过替代方法分发代码,同时不使此处托管的代码保持最新,可能会导致插件被删除。
4. 代码必须(大部分)是人类可读的。
目录中不允许使用类似于 的p,a,c,k,e,r
混淆功能、uglify 的 mangle 或不清楚的命名约定(例如 )的技术或系统$z12sdf813d
来隐藏代码。使代码变得非人类可读会迫使未来的开发人员面临不必要的障碍,并且成为隐藏的恶意代码的常见载体。
我们要求开发人员通过以下方式之一提供对其源代码和任何构建工具的公共、维护的访问权限:
- 将源代码包含在已部署的插件中
- 自述文件中指向开发位置的链接
我们强烈建议您记录如何使用任何开发工具。
5. 不允许使用试用软件。
插件可能不包含受限制或锁定的功能,只能通过付费或升级才能使用。试用期或达到配额后,不得禁用功能。此外,仅提供对 API 和服务的沙箱访问的插件也是试用或测试插件,不允许使用。
允许服务中的付费功能(请参阅准则 6:服务软件),前提是插件内的所有代码完全可用。我们建议使用托管在 WordPress.org 外部的附加插件,以排除高级代码。插件仅作为开发人员工具的情况将根据具体情况进行审查。
尝试向用户追加销售临时产品和功能是可以接受的,只要它符合准则 11(劫持管理体验)的范围。
6. 允许软件即服务。
允许充当某些外部第三方服务(例如视频托管网站)接口的插件,即使对于付费服务也是如此。服务本身必须提供实质功能,并清楚地记录在随插件提交的自述文件中,最好带有指向服务使用条款的链接。
不允许的服务和功能包括:
- 不允许仅出于验证许可证或密钥而存在的服务,而插件的所有功能方面都包含在本地。
- 禁止通过将任意代码移出插件来创建服务,以使该服务可能错误地显示为提供补充功能。
- 不是服务的店面。仅充当从外部系统购买的产品前端的插件将不被接受。
7.未经用户同意,插件不得跟踪用户。
为了保护用户隐私,未经明确和授权同意,插件不得联系外部服务器。这通常是通过“选择加入”方法完成的,需要注册服务或插件设置中的复选框。有关如何收集和使用任何用户数据的文档应包含在插件的自述文件中,最好有明确规定的隐私政策。
禁止跟踪的一些示例包括:
- 自动收集用户数据,无需用户明确确认。
- 故意误导用户提交信息作为使用插件本身的要求。
- 卸载与服务无关的资产(包括图像和脚本)。
- 未记录(或记录不充分)的外部数据(例如黑名单)的使用。
- 跟踪使用情况和/或观看次数的第三方广告机制。
此策略的一个例外是软件即服务,例如 Twitter、Amazon CDN 插件或 Akismet。通过安装、激活、注册和配置使用这些服务的插件,即获得对这些系统的同意。
8. 插件不得通过第三方系统发送可执行代码。
允许从记录的服务外部加载代码,但所有通信必须尽可能安全。不允许在不充当服务时在插件内执行外部代码,例如:
- 从 WordPress.org 以外的服务器提供更新或以其他方式安装插件、主题或附加组件
- 安装同一插件的高级版本
- 出于字体包含以外的原因调用第三方 CDN;所有非服务相关的 JavaScript 和 CSS 必须包含在本地
- 当服务的使用条款未明确允许时,使用第三方服务来管理定期更新的数据列表
- 使用 iframe 连接管理页面;应使用 API 来最大程度地降低安全风险
允许与站点交互并将软件推送到站点的管理服务,前提是该服务处理其自己的域上的交互,而不是 WordPress 仪表板内的交互。
9. 开发者及其插件不得做出任何非法、不诚实或有道德攻击的行为。
虽然这是主观且相当广泛的,但其目的是防止插件、开发人员和公司滥用最终用户以及其他插件开发人员的自由和权利。
这包括(但不限于)以下示例:
- 通过关键词堆砌、黑帽 SEO 或其他方式人为操纵搜索结果
- 为使用该插件的网站带来更多流量
- 补偿、误导、施压、勒索或勒索他人以获得评论或支持
- 暗示用户必须付费才能解锁包含的功能
- 创建帐户以生成虚假评论或支持票(即马甲)
- 采用其他开发者的插件并将其作为原创作品呈现
- 暗示插件可以创建、提供、自动化或保证法律合规性
- 未经许可使用用户的服务器或资源,例如僵尸网络或加密货币挖矿的一部分
- 违反WordPress.org 社区行为准则
- 违反WordCamp 行为准则
- 违反论坛准则
- 针对 WordPress 社区任何其他成员的骚扰、威胁或辱骂
- 伪造个人信息以故意掩饰身份并避免因之前的违规行为而受到制裁
- 故意试图利用指南中的漏洞
10. 未经用户明确许可,插件不得在公共网站上嵌入外部链接或积分。
插件代码中包含的所有“Powered By”或信用显示和链接都必须是可选的,并且默认情况下不会显示在用户的前端网站上。用户必须通过明确说明且易于理解的选择来选择显示任何及所有积分和链接,而不是隐藏在使用条款或文档中。插件可能不需要信用或显示链接即可运行。服务可以按照自己认为合适的方式对其输出进行品牌化,前提是代码是在服务而不是插件中处理的。
11. 插件不应劫持管理仪表板。
用户更喜欢并期望插件感觉像是 WordPress 的一部分。不断的唠叨和不必要的警报压倒了管理仪表板,会降低这种体验。
升级提示、通知、警报等必须限制范围并谨慎使用,无论是根据上下文还是仅在插件的设置页面上。站点范围内的通知或嵌入式仪表板小部件在解决后必须是可驳回或自行驳回的。错误消息和警报必须包含有关如何解决问题的信息,并在完成后自行删除。
应避免在 WordPress 仪表板内投放广告,因为它通常无效。用户通常仅在尝试解决问题时才访问设置页面。增加插件的使用难度通常不会带来好的评论,我们建议限制其中放置的任何广告。请记住:不允许通过这些广告跟踪推荐(请参阅准则 7),并且大多数第三方系统不允许后端广告。滥用广告系统的指导方针将导致开发者被上游举报。
欢迎并鼓励开发人员添加指向自己网站或社交网络的链接,以及本地(在插件内)包含图像的链接,以增强体验。
12. WordPress.org 上的公开页面(自述文件)不得发送垃圾邮件。
面向公众的页面,包括自述文件和翻译文件,不得用于发送垃圾邮件。垃圾邮件行为包括(但不限于)不必要的附属链接、竞争对手插件的标签、总共使用超过 12 个标签、黑帽 SEO 和关键字填充。
在适度的范围内允许链接到直接需要的产品,例如主题或插件使用所需的其他插件。同样,相关产品可以在标签中使用,但竞争对手不能使用。如果插件是 WooCommerce 扩展,它可能会使用标签“woocommerce”。但是,如果该插件是 Akismet 的替代品,则它可能不会使用该术语作为标签。重复使用标签或特定术语被视为关键字堆砌,是不允许的。
自述文件是为人编写的,而不是为机器人编写的。
在所有情况下,必须披露联属链接,并且必须直接链接到联属服务,而不是重定向或隐藏的 URL。
13.插件必须使用WordPress的默认库。
WordPress 包含许多有用的库,例如 jQuery、Atom Lib、SimplePie、PHPMailer、PHPass 等。出于安全和稳定性原因,插件可能不会在自己的代码中包含这些库。相反,插件必须使用与 WordPress 打包的这些库的版本。
有关 WordPress 中包含的所有 javascript 库的列表,请查看WordPress 包含和注册的默认脚本。
14. 应避免频繁提交插件。
SVN 存储库是一个发布存储库,而不是开发存储库。所有提交、代码或自述文件都将触发与插件关联的 zip 文件的重新生成,因此只有准备好部署的代码(无论是稳定版本、测试版还是 RC)才应推送到 SVN。强烈建议在每次提交时包含描述性和信息性的消息。频繁的“垃圾”提交消息(例如“更新”或“清理”)使得其他人很难跟踪更改。多次快速提交仅调整插件的次要方面(包括自述文件),会对系统造成过度压力,并且可以被视为游戏最近更新列表。
一个例外是当自述文件更新只是为了表明支持最新版本的 WordPress 时。
15. 每个新版本的插件版本号必须递增。
仅当插件版本增加时,用户才会收到更新提醒。主干 readme.txt 必须始终反映插件的当前版本。有关标记的更多信息,请阅读我们有关标记的 SVN 说明以及readme.txt 的工作原理。
16. 提交时必须有完整的插件。
所有插件在批准之前都会经过检查,这就是需要 zip 文件的原因。名称不能“保留”以供将来使用或保护品牌(参见#17:尊重品牌)。未使用的已批准插件的目录名称可能会提供给其他开发人员。
17. 插件必须尊重商标、版权和项目名称。
禁止使用商标或其他项目作为插件 slug 的唯一或初始术语,除非可以确认合法所有权/代表的证明。例如,WordPress 基金会已将“WordPress”一词注册为商标,在域名中使用“wordpress”属于违法行为。此政策适用于插件 slugs,我们不允许 slug 以其他产品的术语开头。
例如,只有 Super Sandbox 的员工才能使用“super-sandbox”一词,或者在“Super Sandbox Dancing Sloths”等上下文中使用他们的品牌。非员工应使用“Dancing Sloths for Superbox”等格式,以避免潜在误导用户相信该插件是由 Super Sandbox 开发的。同样,如果您不代表“MellowYellowSandbox.js”项目,则使用它作为插件的名称是不合适的。
建议使用原创品牌,因为它不仅有助于避免混淆,而且对用户来说更容易记住。
18. 我们保留尽最大努力维护插件目录的权利。
我们的目的是尽可能公平地执行这些准则。我们这样做是为了确保插件的整体质量和用户的安全。为此,我们保留以下权利:
- ...随时更新这些指南。
- …从目录中禁用或删除任何插件,即使是出于指南中未明确涵盖的原因。
- ...授予例外情况并让开发人员有时间解决问题,甚至是与安全相关的问题。
- …删除开发人员对插件的访问权限,以代替新的、活跃的开发人员。
- ...为了公共安全的利益,在未经开发人员同意的情况下对插件进行更改。
作为回报,我们承诺谨慎使用这些权利,并尽可能尊重最终用户和开发人员。
规划、提交和维护插件
您已经编写了下一个Hello Dolly,并且希望全世界都使用它。你该怎么办?
1. 测试一次并再次测试
运气好的话,您的插件将被许多人在许多不同的情况和托管环境中使用。您需要确保已经测试了您的插件,以确保它可以在任何情况下工作并且不会让您的用户感到沮丧。
2. 取一个好名字
插件名称应该反映您和您的工作的独特性。当您选择名称时,请确保您没有侵犯商标或践踏他人的产品名称。如果您不是为 FaceRange(一家假公司)工作,那么您就不应该将您的插件命名为“FaceRange's Dancing Squirrels”。例如,更好的名称是“Dancing Squirrels for FaceRange”。想出一个好名字可能很困难,所以慢慢来。您的插件 URL 在提交后无法更改,但显示名称可以更改一千次。
显示名称是从主插件文件中的标头生成的,因此请注意您的 P 和 Q。
3. 编写出色的文档
README.txt文件是最好的起点,因为它是所有插件的标准参考点。您需要确保包括:
- 对您的插件实际功能的简洁描述。如果它的功能很多,那么作为两个插件可能会更好。
- 安装说明,尤其是需要完成特殊配置的情况。如果用户需要注册您的服务,请确保您链接到它。
- 有关如何获得支持以及您支持和不支持哪些内容的说明。
4. 提交您的插件
为了提交插件,需要三个步骤:
- 使用定期检查的有效电子邮件地址在 WordPress.org 上注册。如果您代表公司提交插件,请使用公司官方电子邮件进行验证。
- 在您的电子邮件客户端中加入白名单
plugins@wordpress.org
,以确保您收到电子邮件通信。 - 提交您的插件,并简要概述其功能以及完整的、随时可用的插件 zip。zip 必须是插件的完整版本,就像您通过插件安装程序手动上传一样。
一旦插件排队等待审核,我们将在 14 个工作日内审核代码是否存在任何问题。遵循指南可以避免大多数问题。如果我们确实发现问题,我们将联系开发人员并努力寻求解决方案。获得批准后,您将收到一封电子邮件,其中包含有关如何访问存储插件的Subversion 存储库的详细信息。
通过 SVN将插件(和自述文件)上传到该存储库后,它将出现在 插件目录中。
5. 推出第一个版本
WordPress.org 插件目录是潜在用户下载和安装插件的最简单方法。WordPress 与插件目录的集成意味着用户只需点击几下即可更新您的插件。
当您准备好发布第一个版本时,您需要注册。审核过程成功完成后,您将获得代码的 Subversion 存储库。我们有关于在 WordPress.org 上使用 SVN 的文档,这与您使用 GIT 时可能熟悉的工作流程略有不同。
6.拥抱开源
开源是我们这个时代最强大的理念之一,因为它促进了跨境协作。通过鼓励贡献,您可以让其他人像您一样热爱您的代码。开源代码有多种选择:
- Github可以让其他人轻松参与您的项目。其他开发人员和用户可以轻松提交错误修复或报告、功能请求或全新贡献。Github 有一个很棒的文档门户,如果您以前从未使用过 Git,甚至还有一个交互式演示。
- WordPress.org 插件目录提供并要求您使用Subversion 存储库。
7. 倾听用户的声音
您经常会发现您的用户对您的代码进行的测试用例比您想象的要多得多。这可能是非常有价值的反馈。
通过 WordPress.org 发布您的代码意味着您的插件自动拥有一个支持论坛。用它!您可以通过电子邮件订阅接收新帖子并及时回复您的用户。他们只是想像您一样喜欢您的插件。
Jetpack 有一篇关于如何编写出色的错误报告的文章,您可以参考一下。
8.定期推送新版本
最好的插件是那些随着时间的推移不断迭代、不断推动微小变化的插件。不要因为等待太久才更新而让您的辛勤工作变得乏味。请记住,持续升级可能会导致“更新疲劳”,用户将停止升级。在太少的更新和太多的更新之间保持平衡很重要。
9. 冲洗并重复
就像生活的其他部分一样,最好的事情来自耐心和努力。
使用 Subversion
SVN,即 Subversion,是一个类似于 Git 的版本控制系统。它可以通过命令行或众多 GUI 应用程序之一使用,例如Tortoise SVN、SmartSVN等。如果您是 SVN 新手,我们建议您先对 SVN 客户端进行比较,然后再决定哪一个最适合您。
本文档并不是对使用 SVN 的完整而有力的解释,而更像是开始使用 WordPress.org 上的插件的快速入门指南。有关更全面的文档,请参阅SVN 书籍。
我们将在这里描述一些有关使用 SVN 的基础知识,因为它与 WordPress.org 托管相关。SVN 和几乎所有代码存储库服务的基本概念保持不变。
有关更多信息,请参阅以下文档:
概述
您的所有文件将集中存储在我们服务器上的svn 存储库中。从该存储库中,任何人都可以将插件文件的副本签入到其本地计算机上,但是,作为插件作者,只有您有权签入. 这意味着您可以更改本地计算机上的文件、添加新文件和删除文件,并将这些更改上传回中央服务器。正是这个签入过程更新了存储库中的文件以及 WordPress.org 插件目录中显示的信息。
Subversion 会跟踪所有这些更改,以便您可以在需要时返回并查看旧版本或修订版本。除了记住每个单独的修订之外,您还可以告诉 subversion 标记存储库的某些修订以方便参考。标签非常适合标记插件的不同版本,并且是确保在 WordPress.org 上看到正确版本并为用户更新的唯一完全受支持的方法。
你的帐户
您的 SVN 帐户将与您提交插件时使用的帐户的用户名(而不是电子邮件)相同。这也是您用于 WordPress 论坛的用户 ID。
请记住,大小写很重要- 如果您的用户名是 JaneDoe,那么您必须使用大写 J 和 D,否则 SVN 将失败。您可以在以下位置查看您姓名的具体大小写: https://profiles.wordpress.org/me/profile/edit/group/1/
如果您需要重置密码,请访问login.wordpress.org
SVN 文件夹
所有 SVN 存储库中默认创建三个目录。
/assets/
/tags/
/trunk/
默认情况下不再创建 /branches/ 目录,因为它未被使用。
- 用于屏幕
assets
截图、插件标题和插件图标。 - 开发工作属于
trunk
. - 发布进去
tags
。 - 不同的代码分支进入
branches
.
树干
警告:不要将主插件文件放在 trunk 的子文件夹中,否则/trunk/my-plugin/my-plugin.php
会中断下载。您可以使用包含文件的子文件夹。
该/trunk
目录是您的插件代码所在的位置。trunk可以被认为是最新最好的代码,但这不一定是最新的稳定代码。Trunk 用于开发版本。希望主干中的代码应该始终是工作代码,但它可能会不时出现错误,因为它不一定是“稳定”版本。对于简单的插件,主干可能是唯一存在的代码版本,这也很好。
即使您在其他地方(例如 git 存储库)进行开发工作,我们也建议您使 trunk 文件夹与您的代码保持最新,以便于 SVN 进行比较。
标签
我们强烈鼓励使用语义软件版本控制。
资产
笔记:另请参阅:您的插件资产如何工作
资产是您的屏幕截图、标题图像和插件图标所在的位置。该目录中的一些较旧的插件可能在 /trunk 中具有屏幕截图文件,但不建议这样做。所有新插件都应将其屏幕截图放在 /assets 中。这可以使插件的文件大小保持较小,因为无需将屏幕截图与插件本身一起发送到 WordPress 安装。
分支机构
默认情况下不再创建 /branches/ 目录,因为它基本上未被使用。本节可被视为已弃用,仅供参考。
该/branches
目录是您可以用来存储插件分支的地方。也许是正在开发的版本,或者测试代码等。
WordPress.org 系统根本不使用分支目录做任何事情,它被认为是严格供开发人员根据需要使用的。由于默认情况下不再创建它,因此您可以忽略它,因为您不再需要它。
最佳实践
为了使您的代码最容易被其他开发人员访问,以下实践被认为是最佳的。
不要使用SVN进行开发
这常常令人困惑。与 GitHub 不同,SVN 是一个发布系统,而不是开发系统。您不需要提交并推动每一个小的更改,事实上这样做对系统有害。每次您将代码推送到 SVN 时,它都会为 SVN 中的所有版本重建所有zip 文件。这就是为什么有时您的插件更新长达 6 小时才显示的原因。相反,当你准备好时,你应该推一次。
使用 trunk 文件夹来获取代码
许多人用作trunk
占位符。虽然可以简单地更新readme.txt
主干中的文件并将所有内容放入标记文件夹中,但这样做会使比较代码中的任何更改变得更加困难。相反,主干应该包含最新版本的代码,即使该版本是测试版。
始终标记版本
虽然可以使用 trunk 作为插件的稳定标签,但实际上不支持也不推荐此功能。相反,版本应该被正确标记为迭代。这将确保与任何自动更新程序完全兼容,并允许在代码出现问题时进行回滚。
从主干创建标签
您不应将代码直接推送到标签文件夹,而应该在主干中编辑代码,在自述文件中填写稳定版本,然后将代码从主干复制到新标签。
这不仅可以让您更容易地看到任何更改,而且您可以进行更小的提交,因为 SVN 只会更新更改的代码。这将节省您的时间并减少潜在的错误(例如更新到错误的稳定标签并将错误代码推送给用户)。
不用担心标签文件夹暂时不存在。您可以使用svn cp
复制 trunk 到标签,然后同时将它们推送到 SVN。
如果您在本地操作,那么您可以一次性更新主干并从中创建标签。检查存储库的根目录,更新 /trunk 中的文件,然后svn copy /trunk /tags/1.2.3
(或无论版本号是什么)然后一次性提交整个内容。SVN是一个基于差异的系统,只要你使用svn进行复制操作,那么它就保留了历史记录,并且让其他人可以轻松跟进。
删除旧版本
由于 SVN 是一个发布存储库,许多开发人员选择删除其插件的旧版本(不受支持)。截至 2019 年,这不再加快发布速度,因为构建过程仅处理具有更改文件的标签。
例子
启动一个新插件
要启动您的插件,您需要将已有的文件添加到新的 SVN 存储库中。
首先在您的计算机上创建一个本地目录来存放 SVN 存储库的副本:
$ mkdir my-local-dir
接下来,查看预先构建的存储库
$ svn co https://plugins.svn.wordpress.org/your-plugin-name my-local-dir
> A my-local-dir/trunk
> A my-local-dir/branches
> A my-local-dir/tags
> Checked out revision 11325.
在我们的示例中,Subversion 已将中央 SVN 存储库中的所有目录添加到本地副本(“A”表示“添加”)。
要添加代码,请导航到my-local-dir
文件夹:$ cd my-local-dir
trunk/
现在,您可以通过命令行使用复制/粘贴命令或拖放将文件添加到存储库本地副本的目录中。无论你觉得舒服什么。
警告:不要将主插件文件放在 trunk 的子文件夹中,否则/trunk/my-plugin/my-plugin.php
会中断下载。您可以使用包含文件的子文件夹。
一旦您的文件位于 trunk 文件夹中,您必须让 subversion 知道您想要将这些新文件添加回中央存储库。
$ cd my-local-dir
my-local-dir/ $ svn add trunk/*
> A trunk/my-plugin.php
> A trunk/readme.txt
添加所有文件后,您将把更改检入中央存储库。
my-local-dir/ $ svn ci -m 'Adding first version of my plugin'
> Adding trunk/my-plugin.php
> Adding trunk/readme.txt
> Transmitting file data .
> Committed revision 11326.
所有签入都需要包含提交消息。
如果提交因“禁止访问”而失败,并且您知道自己具有提交访问权限,请将您的用户名和密码添加到签入命令中。
my-local-dir/ $ svn ci -m 'Adding first version of my plugin' --username your_username --password your_password
请记住您的用户名区分大小写。
编辑现有文件
一旦您的插件位于目录中,您可能需要在某个时候编辑代码。
首先进入存储库的本地副本并确保它是最新的。
$ cd my-local-dir/
my-local-dir/ $ svn up
> At revision 11326.
在上面的例子中,我们都是最新的。如果中央存储库中有更改,它们将被下载并合并到您的本地副本中。
现在您可以使用您喜欢的任何编辑器编辑需要更改的文件。
如果您不使用 SVN GUI 工具(如 SubVersion 或 Coda),您仍然可以在进行更改后检查并查看本地副本和中央存储库之间有什么不同。首先我们检查本地副本的状态:
my-local-dir/ $ svn stat
> M trunk/my-plugin.php
这告诉我们,我们的本地副本trunk/my-plugin.php
与我们从中央存储库下载的副本不同(“M”代表“修改”)。
让我们看看该文件到底发生了什么变化,这样我们就可以检查它并确保一切看起来正确。
my-local-dir/ $ svn diff
> * What comes out is essentially the result of a
* standard `diff -u` between your local copy and the
* original copy you downloaded.
如果一切看起来都不错,那么就可以将这些更改签入中央存储库了。
my-local-dir/ $ svn ci -m "fancy new feature: now you can foo *and* bar at the same time"
> Sending trunk/my-plugin.php
> Transmitting file data .
> Committed revision 11327.
现在你已经成功更新了主干。
“标记”新版本
每次正式发布插件时,都应该标记该版本代码的副本。这可以让您的用户轻松获取最新(或较旧)版本,让您更轻松地跟踪更改,并让 WordPress.org 插件目录知道应该告诉人们下载哪个版本的插件。
我们希望使用svn cp
而不是常规的cp
,以便利用 SVN 的功能。
my-local-dir/ $ svn cp trunk tags/2.0
> A tags/2.0
与往常一样,检查更改。
my-local-dir/ $ svn ci -m "tagging version 2.0"
> Adding tags/2.0
> Adding tags/2.0/my-plugin.php
> Adding tags/2.0/readme.txt
> Committed revision 11328.
标记新版本时,请记住将字段更新为Stable Tag
新trunk/readme.txt
版本。
恭喜!您已更新代码!
笔记
不要在 SVN 中放入任何您不愿意且不准备部署给使用您插件的每个人的内容。这 包括供应商文件.gitignore
和其他所有内容。
您也不应该上传 zip 文件。与大多数代码存储库系统一样,SVN 希望您上传单个文件。
也可以看看
警报和警告
当您访问 WordPress.org 上的插件页面时,您可能会注意到特殊警报或警告。这些的存在是为了帮助访问者了解各种插件的状态。
已批准和待处理的数据
已获得批准但尚未上传代码的插件将看到此消息:此消息仅向插件所有者显示,一旦通过 SVN 推送代码,该消息就会消失。
关闭
截至 2017 年 11 月,关闭的插件会显示一条通知:

所有访问者都可以看到该信息,表明插件已关闭。2018 年 1 月之后关闭的插件将包含一个日期:

60 天后,警报将更新以解释插件被关闭的原因:

插件提交者将看到以下附加注释:

插件被关闭的原因
- 作者请求 – 作者已要求关闭该插件
- 违反指南 – 违反任何指南
- 许可/商标违规 – 使用非 GPL 代码,或滥用商标
- 合并到核心 – 该插件现在是核心的一部分(为功能项目保留)
- 安全问题 – 此插件中发现了一个安全问题
除非出现极端情况,否则不会向 WordPress.org 安全团队或插件作者之外的任何人提供有关插件被关闭原因的其他详细信息。
已过时
不支持 WordPress 最近 3 个主要版本的插件有以下通知:

此前,此消息提醒用户最近 2 年内未更新的插件。2018 年,它被修改为依赖更相关的数据。由于 WordPress 每年更新主要版本 2 到 3 次,并且维护的插件应该使用最新版本进行测试,因此可以通过在 WordPress 新版本发布时更新插件自述文件来避免此警报。
在 WordPress 的每个主要版本发布之前,开发人员都会收到电子邮件并要求更新此值。他们 不需要推送新版本,只需更新自述文件并将“Tested up to:”的值编辑为 WordPress 的最新版本。
管理插件的安全性
WordPress 插件中代码的安全性受到非常重视。
警告:如果您发现存在安全问题的插件,请阅读报告插件安全问题
当 WordPress 安全团队验证插件漏洞时,他们会联系插件作者并指导他们如何修复和发布插件的安全版本。如果插件作者没有做出回应或者漏洞严重,插件/主题将从公共目录中提取,在某些情况下,由安全团队直接修复和更新。
解决安全问题
当您收到插件安全问题的报告时,可能会很可怕。首先,不要惊慌。每个人都会犯错误。最重要的是安全、及时地修复它。
- 确保您理解该报告。如果您不确定这意味着什么,请询问详细信息。即使是第三方记者通常也愿意花时间解释问题所在并指导您研究正确的解决方案。
- 让您的更改尽可能小。这将使您以后复习起来更加容易。
- 测试你的插件。确保安全修复不会破坏任何其他内容。确保升级不会导致奇怪的错误。继续
WP_DEBUG
并记录任何错误。 - 在更改日志中记录问题。您不需要详细说明所发生的情况,但要记录安全问题已解决。
- 在你的自述文件中注明记者的身份。这很好,让人们以后更愿意免费帮助你。
- 修改你的版本号。我们推荐SemVer,因此插件版本 3.9 的安全版本会将版本更改为 3.9.1 等等。
自动插件安全更新
自 WordPress 3.7 起,我们能够推送 插件的自动安全更新,以修复插件中的关键漏洞。许多网站都使用了插件自动更新功能,要么通过过滤器直接选择加入,要么使用 WordPress 可用的众多远程管理服务之一。
在极端情况下,插件审核团队和 WordPress 安全团队可能会确定插件问题严重到必须为所有用户进行更新。这种情况极为罕见,因为发生冲突的可能性很高。
批准自动更新插件并将其推广给 WordPress 用户的过程是高度手动的。安全团队审查版本中的所有代码更改,验证问题和修复,并确认插件可以安全地触发更新。推出自动更新需要修改和部署 API 代码。这与核心安全版本的标准和流程相同。
标准
我们当前考虑的安全推送标准是一个简单的列表:
- 安全团队是否已意识到该问题?
- 问题有多严重?它会对WordPress 安装的安全性以及更大的互联网产生什么影响?
- 该问题的修复是独立的还是添加了大量额外的多余代码?
- 如果插件的多个分支受到影响,是否已准备好每个分支的版本?
- 更新可以安全地自动安装吗?
这些要求的定义方式使得任何人都应该能够勾选每个框。
第一个标准——让安全团队意识到这个问题——至关重要。由于这是一个严格控制的过程,因此需要尽早通知WordPress安全团队。让我们知道就像通过电子邮件向我们发送plugins@wordpress.org
详细信息一样简单。
插件和安全团队将与插件作者(以及报告者,如果不同)合作研究漏洞及其确切暴露情况,验证建议的修复方案,并确定将发布哪些版本以及何时发布。
常问问题
如何请求自动更新我的插件?
如果您觉得您的插件有足够大的用户群或者问题非常重要,请在推送代码plugin@wordpress.org
之前发送电子邮件。在电子邮件中包含供审核的更改补丁,并解释为什么您认为这应该自动化。
除了与安全相关的更改之外,我还可以包含自动更新的更改吗?
除了少数例外,没有。安全推送应该 只与安全相关。我们更喜欢(并且很多时候需要)仅修复安全问题的插件版本,只需最少的代码更改,并且没有不相关的更改。
这使得每个人都可以快速查看更改并对其更加有信心。这也意味着用户受到的干扰最小。
为什么插件 A 会自动更新,而插件 B 却没有?
这不是 WordPress.org 的偏见,这只是我们一直使用的手动流程的倒退。如果我们收到有关问题的警报,我们将尽力处理。如果我们几天后发现,推出修复程序的机会窗口通常已经过去,并且不会那么有效。
如何禁用自动更新?
有多个选项可以禁用此功能。禁用核心自动更新的文章 适用于此处。任何禁用所有自动更新功能的操作都会阻止插件更新。如果您只想禁用插件更新,无论是所有插件还是单个插件,您都可以通过单个过滤器调用来实现。
如果我无法(或不想)修复我的代码怎么办?
你不必这样做。您的插件将保持关闭状态,2 或 3 个月后,插件页面将报告它因安全问题而关闭。如果您想推送修复但保持插件关闭,我们也可以这样做。只需回复电子邮件并与我们联系即可。
我只需修复报告的问题吗?
是和不是。您 确实 必须修复报告的问题,但是当您完成后, 整个插件将被重新审查,如果发现更多问题,您也将需要修复这些问题。最终目标是确保重新打开的插件是安全的。
如果我遇到指导方针问题怎么办?
当人们违反其他准则(例如包含自己的 jQuery 副本或进行未记录的外部服务调用)时,就会出现这种情况。这取决于其他问题的严重性。如果它只是您自己的 jQuery,我们可能会重新打开它并允许您按照自己的进度修复该问题。如果您要记录插件的所有安装,则需要在我们重新打开插件之前更正该问题。
插件开发者常见问题解答
托管 WordPress 插件有很多细节。在寻求帮助之前,请花一点时间查看您的问题是否在这里得到解答。
最后更新日期:2023 年 7 月 10 日
插件审核团队
如何联系插件审核团队?
您可以通过电子邮件联系我们plugins@wordpress.org
– 我们会在 7 个工作日内回复所有电子邮件。
审核团队是否为 Automattic 工作?
不会。审核小组由 100% 志愿者组成。有些人得到全职雇主的报酬,但没有人受 WordPress.org、Automattic 或 WordPress.com 雇用
我可以加入团队吗?
请查看本手册页面。
提交和评论
我在哪里提交我的插件?
转到“添加”页面并上传您的 zip。您的文件应该小于10 兆并且是一个完整的插件。我们不接受尚未准备好使用的占位符或插件。
如果我的插件超过 10 兆怎么办?
仔细检查您是否不包含未使用的文件(例如测试文件夹、文档和完整节点/供应商文件夹)。大多数面临此问题的插件都包含了最终代码中没有位置的各种开发内容。
提交后会发生什么?
您将立即收到一封自动电子邮件,告知您有关提交的信息。那时,有人会手动下载并审查您的代码。如果我们发现安全性、文档或演示没有问题,您的插件将获得批准。如果我们确定存在问题,您将收到第二封电子邮件,其中详细说明了需要修复的问题。
我的插件永久链接 (slug) 是什么?
当您提交插件时,您会收到一封自动电子邮件,告诉您插件是什么。这是根据主插件文件(带有插件标头的文件)中插件名称的值填充的。如果你设置你的,Plugin Name: Boaty McBoatface
那么你的 URL 将会是wordpress.org/plugins/boaty-mcboatface
,你的 slug 将会是boaty-mcboatface
。如果现有插件包含您的名字,那么您将在提交时收到警告。
这也是 您的插件和文本域的文件夹名称(在 SVN 中并安装在 WordPress 上),因此请仔细注意。
一旦您的插件获得批准,该名称 就无法重命名。请明智地选择。
为什么我得到的蛞蝓与我被告知的不同?
如果我们必须更改您的永久链接(slug),我们将始终通过电子邮件向您解释原因。一般来说,当您有明显的拼写错误或错误(例如 foundre 而不是Founder)或者与现有商标或其他插件存在冲突时,我们会更改您的永久链接。请务必仔细阅读您的评论电子邮件,因为我们会解释为什么我们要这样做。
为什么我的提交失败并提示我的插件名称已存在?
出现这种情况有两个原因:
- 您正在尝试使用 WordPress.org 上已存在的带有永久链接的插件
- 您正在尝试使用带有永久链接的插件,该插件存在于 WordPress.org之外并且拥有大量用户群。
第一个是显而易见的。您不能拥有两个具有相同永久链接的插件,因此您需要选择一个新插件。
第二个令人困惑,因为它告诉您该永久链接正在使用中,而不是在 WordPress.org 上。重要的是要了解插件更新 API 的工作方式是将插件文件夹名称(即永久链接)与其托管在 WordPress.org 上的每个插件进行比较。如果匹配,则会检查更新并提示用户升级。
当这种情况发生时,“原始”插件(我们不托管的插件)的用户将升级到来自 WordPress.org 的插件,如果这不是您真正想要做的,您可能会破坏他们的网站。
有时,当公司或个人私下发布其插件(例如通过 Github)并决定在 WordPress.org 上重新发布时,就会出现这种情况。在这些情况下,我们建议您给我们发送电子邮件,我们将引导您完成如何克服错误。
为什么我收到一条错误消息,提示我无法以术语开头插件名称?
该错误是为了通知您,您的显示名称不得以他人的商标用语开头。这是为了保护您和目录免受有关商标滥用的法律问题。要解决此问题,您必须更改插件的自述文件和主 PHP 文件中的显示名称。
请不要尝试通过巧妙地重命名您的插件(例如 WuuCommerce)来“解决”这个问题。所有这些只会让我们担心您将来将无法遵循指南。
为什么我收到一条错误消息,提示我无法在插件名称中完全使用某个术语?
一些商标所有者要求我们不再允许在插件名称中完全使用特定术语。如果您看到此错误,则必须从插件名称中删除该术语。
要继续提交,您必须从主插件文件和自述文件中的插件名称:行中删除“[TERM]”。
如果您尝试通过将术语从“Facerange”更改为“Face-Range”来解决此问题,我们将等待您的提交并重申您不能使用该术语。请不要试图偷偷摸摸或耍小聪明来突破这个限制。
如何提交官方插件?
以官方公司用户帐户登录并 仅使用该帐户提交。
我们不能接受个人开发者帐户提交的插件,除非它们显然也是公司的。例如,使用拥有 Gmail 地址的用户提交官方 Facerange 插件可能会被标记为商标侵权。
如果我使用错误的用户 ID 提交插件怎么办?
只需立即回复电子邮件并告知我们即可。我们可以为您转让所有权。如果您忘记执行此操作,您可以通过添加正确的帐户作为提交者然后让该帐户删除您自己的帐户来自行修复。
不要 重新提交您的插件。请立即告诉我们,我们会修复它。
插件获得批准需要多长时间?
没有官方平均值,因为没有两个插件是相同的。如果您的插件很小并且所有代码都正确,则应该在初次审核后的十四天内获得批准。
如果您的插件有任何代码问题,您将需要很长时间才能纠正这些问题。无论哪种方式,您都会plugins@wordpress.org
收到一封包含状态的电子邮件,因此请将其添加到您的电子邮件白名单中并耐心等待我们的回复。
我发送了修复程序,但没有人回复。我应该等多久?
我们的目标是在七 (7) 个工作日内回复所有评论。如果少于这个数字,就说明我们真的很忙。如果已经过去两天了,例如周末或假期,那么您不应该期待回复。
请记住,审核团队由 100% 志愿者组成,他们都有全职工作和其他志愿者职责。我们确实会及时回复,但我们也有 WordPress 之外的生活。
如果我的插件有问题,我需要多长时间修复它?
没有时间表,只要我们知道您正在努力并且我们认为您正在取得进展,我们就会开放审核。您的插件将在 3 个月后被拒绝,但审核将保持开放状态。
为什么我的插件在三个月后被拒绝?
如果您的插件审核在三 (3) 个月后仍未完成,我们将拒绝您的提交,以保持队列的可维护性。在任何时间点,我们都有 700 人进行中期审核,我们认为 3 个月是一个相当合理的时间范围。
我终于修复了我的插件。我应该重新提交吗?
否。回复电子邮件。即使已经过去18个月了。迄今为止最长的时间已经有3年了。我们不介意是否需要一段时间。
我一次可以提交多少个插件供审核?
只有一个。
为什么我不能一次提交多个插件?
事实证明,允许人们同时提交多项内容不利于审核过程。所有插件中经常发现错误,导致相同的电子邮件被发送多次。此外,人们常常对自己正在进行的审查感到困惑,从而使需要解决的问题变得混乱。通过将其更改为一次一次,这些问题的混乱程度显着下降。
此外,许多新用户不知道如何使用 SVN,最终提交了多个插件但从未使用过任何一个。这可能会消耗我们的资源,所以我们确实限制人员。
由于所有插件都会在两周内获得初步审核,因此这应该不是什么难事。
我可以使用多个帐户提交多个插件吗?
不可以。如果您这样做,我们将暂停您的所有辅助帐户。请不要试图绕过一次一次的规则。
我需要在特定日期批准我的插件,我该怎么办?
尽早提交。除非该插件旨在解决安全或法律问题,否则我们不允许插队。如果 与其中之一相关,请发送电子邮件并plugins@wordpress.org
说明情况。
有哪些具体事情是我应该避免做的?
我们寻找一些非常明显的事情,所有这些都列在我们的指南中。大多数可以概括为“不要成为垃圾邮件发送者”,但我们来谈谈人们最常做的事情:
readme.txt
作为服务时不包含文件- 不测试插件
WP_DEBUG
- 包括打包的 JavaScript 库的自定义版本
- 不必要地调用外部文件
- “由”链接提供支持
- 打电话回家
再次强调,这是一个简短的概述。请阅读指南,因为完整列表非常详细。
有您不接受的插件吗?
我们不接受“不做任何事情”、非法或鼓励不良行为的插件。这包括黑帽 SEO 垃圾邮件、内容旋转器、仇恨插件等。
同样,我们不接受框架插件或库插件。如果您的插件必须要求其他插件或主题进行自身编辑才能使用您的插件,那么它就是一个库。如果您的插件是一个模板,可以通过直接自定义文件来构建更多代码,那么它就是一个框架或样板。框架和库应该与每个插件一起打包(希望不会与使用框架或库的其他插件冲突)。至少在核心支持插件依赖之前。
我们也不接受其他人作品的 100% 副本或重复 WordPress 核心中的功能的插件。基本上,你的插件应该做一些新的事情,或者以新的方式,或者解决一个特定的问题。
我想重做、升级或重新命名我现有的插件。我就再提交一次吧?
不,你应该重写并升级现有的插件。使其成为主要版本发布。我们无法重命名插件或转移用户,因此新插件不会继承任何现有用户、评论、支持主题、评级、下载、收藏夹等。基本上,您会将所有当前用户排除 在外,这太卑鄙了。
我的提交犯了一个错误。我该如何修复它?
每次提交都会收到带有说明的自动回复。回复该邮件或电子邮件plugins@wordpress.org
并解释情况。
我们可以在批准之前更正插件,因此我们通常能够为您解决该问题。如果没有,我们会让您知道该怎么做。在批准任何事情之前,我们会尝试找出名称中的拼写错误,但我们也会犯错误。
插件名称中有一些我不能做的事情吗?
我们有以下限制:
- 插件不得在名称或 slug 中使用粗俗内容
- 插件不得在其 slugs 中使用“WordPress”或“Plugin”,除非在极端情况下
- 插件不得在插件 slugs 中使用版本号
- 由于系统限制,slug中只允许使用英文字母和阿拉伯数字
- 插件不得以商标术语或特定项目/库/工具的名称开头,除非由官方代表提交
我们鼓励每个人发挥创造力,想出独特的鼻涕虫。我们会自动更正任何具有不可接受的 slug 的插件。如果对最佳选择有疑问,我们将与您联系以确定。
使用 SVN 存储库
我把我的文件放在哪里?
将代码文件直接放在trunk/
存储库的目录中。每当您发布新版本时,通过将当前主干修订版复制到该目录的新子目录来标记该版本tags/
。
确保您进行更新trunk/readme.txt
以反映新的稳定标签。
自述文件的图像(例如屏幕截图、插件标题和插件图标)位于assets/
SVN 结帐根目录中的目录(您可能需要创建)。例如,这将与tags/
和处于同一水平。trunk/
我可以将文件放在 的子目录中吗trunk/
?
不可以。这样做会导致拉链生成器损坏。
如果您有包含大量文件的复杂插件,您当然可以将它们组织到子目录中,但readme.txt 文件 和根插件文件应直接放入trunk/
.
我应该如何命名我的标签(又名发布)?
您的 Subversion 标签应该看起来像版本号。具体来说,它们应该只包含数字和句点。2.8.4
是一个好看的标签,一个my neato releaso
是一个糟糕的标签。我们建议您使用语义版本控制来跟踪版本,但我们不强制执行此操作。
请注意,我们在这里讨论的是Subversion标签,而不是 readme.txt 搜索类型标签。
我应该在 SVN 中保留多少个旧版本?
尽可能少。很少有人需要发布存储库中的旧代码。请记住,SVN 不适用于您的代码版本控制。你可以使用 Github 来做类似的事情。SVN 应该有您当前的发行版本,但您不需要所有以前版本的所有次要版本。对他们来说,最后一两个就很好了。
我可以在我的插件中包含 SVN 外部组件吗?
不可以。您可以将svn externals添加到您的存储库,但它们不会添加到可下载的 zip 文件中。
我可以将 zip 和其他压缩文件放入我的插件中吗?
不。
我可以包含缩小的 JS 吗?
是的!但是,您要么必须在插件中保留非缩小文件,要么通过自述文件指导人们在哪里可以获得非缩小文件。
缩小可以,但是隐藏就不行了。所有代码都必须是人类可读的才能包含在该目录中。
您的 WordPress.Org 页面
我的插件什么时候“上线”?
一旦您将代码推送到 SVN 文件夹,您的插件就会上线。如果您还没有准备好,请不要推送代码,因为除了关闭插件 之外没有“关闭”开关。由于关闭插件是永久性的,因此我们建议您在准备好上线之前不要推送代码。
WordPress.org 插件目录从哪里获取数据?
来自您在插件文件和readme.txt 文件中指定的信息,以及来自 Subversion 存储库本身。阅读readme.txt 如何工作以获取更多信息。
您还应该充分利用主插件文件中的插件标头。这些将定义您的用户名如何显示在 WordPress.org 托管页面以及 WordPress 管理员中。我们建议使用所有这些标头来完整记录您的插件。
我可以指定 WordPress.org 插件目录应使用我的插件的哪个版本吗?
是的,通过指定Stable Tag
trunk 目录的readme.txt 文件中的字段。
我们要求您不要使用“trunk”作为稳定标签,因为这会使回滚变得比需要的更加复杂。
“已测试”值应该是哪个版本的 WordPress?
从逻辑上讲,无论您测试到什么版本。但是,切勿超出当前候选版本。如果没有,请不要高于活动版本。所以如果WordPress的稳定版本是6.0.9,你可以使用6.0到6.0.9,一切都会好起来的。如果有 6.1-RC 版本,那么您可以使用 6.1,但不能再更高了。
不要试图耍小聪明而使用 6.5 或 7。这将导致您的页面出现错误。
每次更新自述文件时是否都需要发布新版本的插件?
不需要。如果您只是对自述文件或图标/标题进行外观更改,则无需发布新版本。只需确保更新 trunk 和 tag 文件夹即可。
每次更新代码时都需要发布新版本的插件吗?
是的。不然没人更新。
我的变更日志中应该包含哪些内容?
更改日志是对插件所做的所有或所有显着更改的日志或记录,包括错误修复、新功能等更改的记录。如果您需要帮助格式化更改日志,我们建议保留更改日志,因为这是使用的格式通过许多产品。
我的变更日志中应该保留多少个版本?
始终将当前的主要版本保留在更改日志中。例如,如果您当前的版本是 3.9.1,您将需要在更改日志中包含该版本和 3.9。应删除旧版本并将其迁移到changelog.txt
文件中。这将使用户能够访问它们,同时使您的自述文件更短、更相关。最多,将插件的最新版本和一个主要版本保留在自述文件的更改日志中。您的插件在 WordPress.org 插件目录中将changelog.txt
不可见,但这没关系。大多数用户只是想知道有什么新鲜事。
如何在插件描述页面上包含视频?
对于 YouTube 和 Vimeo 视频,只需将视频链接单独粘贴到您的描述中。请注意,视频必须设置为允许嵌入,嵌入过程才能正常工作。对于由 WordPress.com VideoPress 服务托管的视频,请使用[wpvideo]
短代码。如果需要,短代码也可用于 YouTube 和 Vimeo,就像在 WordPress 中一样。
为什么我的插件说它没有经过最新 WordPress 版本的测试?
当您忽略在自述文件的标题中使用正确的“已测试”值时,就会发生这种情况。该值应该是您测试插件的最新版本的 WordPress。如果最新的WordPress主要4.9
版本是 4.9,那么您应该具有指示兼容性的值。您不需要更新次要版本(如果您的自述文件与 4.9 兼容,那么它将涵盖 4.9 到 4.9.1000)。
请记住,如果您安装未发布的 WordPress 版本(例如 6.0),您将看到相同的消息。
插件目录需要多长时间才能反映我的更改?
WordPress.org 插件目录每隔几分钟更新一次。但是,根据更新队列的大小,您的更改可能需要更长的时间才会显示。请至少提前6 小时联系我们。
如何为我的插件页面制作那些很酷的横幅之一?
您可以通过将正确命名的文件上传到文件夹中来制作自己的插件标头assets
。阅读插件标头以获取更多信息。
如何制作插件图标?
您可以通过将正确命名的文件上传到文件夹中来制作自己的插件图标assets
。阅读插件图标以获取更多信息。
我可以在我的插件横幅/图标中使用官方徽标吗?
通常不会。
您的插件图标 永远不应该是未更改的官方徽标,例如 Facerange。这将侵犯他们的财产。您不得在横幅或图标中使用官方徽标作为您的品牌。即使您有权在自己的网站上这样做,我们在这里也没有该权限。
就像您的插件名称一样,我们建议您的图标和标题对您来说是独一无二的。这样它们往往更容易被记住。
我的自述文件中可以使用多少个标签?
根据指南,插件的自述文件中的标签仅限于 12 个。这是为了控制垃圾邮件。也就是说,只有前五个标签会显示在 WordPress.org 上,原因大致相同。前 12 个标签用于搜索,其余标签将被忽略,因此标签填充根本没有帮助。
此外,任何只有您使用的标签都不会显示,因为它们不会帮助任何人找到另一个类似的插件。
插件名称
获得批准后我可以更改插件名称吗?
是和不是。您可以更改显示名称,但插件(插件 URL 中属于您的部分)一旦获得批准,就无法更改。这就是为什么我们在提交时多次警告您。
要更改显示名称,请编辑主插件文件并将“插件名称:”的值更改为新名称。您还需要编辑 readme.txt 中的标题以匹配。
为什么我不能使用某人的商标/品牌作为我的插件名称?
简而言之,因为你不是他们。
如果您为 BooCommerce 编写了附加插件,则不得将其命名为“BooCommerce 改进的产品搜索”,因为这会生成 slug,boocommerce-improved-product-search
并且会与“BooCommerce”的商标发生冲突。也就是说,提交名称“Boo Improved Product Search”是可以接受的,该名称将使用 slug bc-improved-product-search
(“bc”不是商标,您会看到)。
另一个例子,如果您有一个插件与一家名为 Amazorn 的流行云托管公司集成了服务,您可以将其称为“My Service Integration for Amazorn”,但您不能使用“Amazorn – My Service Integration ”。
考虑一下 Keurig 现实生活中的例子。如果您制作了环保酿造杯,您可以将其营销为“EcoBrew Pod for Keurig”,但您不能尝试将其营销为“Keurig EcoBrew Pod”。后者意味着与 Keurig 有直接关系,并且在某些国家/地区实际上是违法的。为了保护您,我们需要您谨慎对待公认的品牌名称和商标。总是谨慎行事;如果他们来告诉我们关闭您的插件,因为您将他们的术语用作 显示名称中的第一个单词,我们就必须这样做。
注意:我们不再有权允许新插件用作woo
其永久链接的开头,并且需要强制使用wc
。
公司可以允许我在我的永久链接中使用他们的商标吗?
不。
虽然我们知道公司可以并且确实授予使用权限,但我们不接受它们作为永久链接,原因非常重要:一旦插件获得批准,我们就无法更改您的永久链接。这意味着,如果公司后来改变主意并撤销批准,该插件将被关闭,所有用户都将被放弃。
为了对插件在目录中的长期生存具有前瞻性和主动性,我们不接受“许可”。永久链接不得以商标(或众所周知的品牌/术语)开头,除非是由官方所有者创建的。
我可以更改插件的 URL/slug 吗?
插件的 URL 一旦获得批准就无法更改,我们会在整个过程中的多个地方警告您这一点。
因此,我们拒绝了大多数使用“新”插件来替换旧插件的请求,只是为了获得更好的插件。
这是因为我们无法在插件之间迁移用户,也无法重定向流量。这意味着提交一个新插件来更改 slug 对插件的 SEO 和声誉非常不利,因为用户将被放弃。大多数插件实际上并不需要新的 URL,而只是想编辑其显示名称。
除非存在与您的 slug 相关的严重拼写错误、语言或法律问题,否则我们不太可能批准新的 slug。如果这样做,我们将标记您的帐户,以表明将来的重命名请求将被拒绝。
如何更改插件的显示名称?
您需要在自述文件和 插件主文件中更改它。
我可以随意设置我的显示名称吗?
请勿使用粗俗、诽谤或其他故意辱骂性的语言。如果您不是官方消息来源,则您不能声称自己是官方消息来源,或者看似声称自己是官方消息来源。例如,如果您制作了一个连接到 Frozbaz 服务的插件,您应该将您的插件称为“Frozbaz 服务连接器”——这样,您就已经明确表示您正在为服务制作插件,而不是服务。
如果您要组合多种服务(例如,流行电子商务插件的支付网关),我们强烈建议您提供一个原始的、唯一的显示名称。
我可以在显示名称中使用 WordPress 或插件吗?
目前是的,但你不应该。这是非常多余的,并且实际上不会以任何方式、形式或形式帮助您的 SEO。我们已经将 WordPress 和插件放入您的页面标题中。
我应该在插件名称中使用商标或注册符号吗?
假设您确实申请了商标,您当然 可以,但这种情况并不常见。甚至谷歌或 Facebook 也没有这样做。只需使用您的商标术语并拥有其日志(如您的 SVN 日志),您通常就已经完成了保护您的品牌所需的法律行动。详情请咨询律师。
搜索
我的插件需要多长时间才会出现在搜索中?
通常在插件提交到 SVN 后 6 到 14 天。这是因为我们必须添加您的数据、解析它并将其共享到我们所有 高度缓存的服务器。这不是瞬时的。另外作为一个新插件,我们没有使用数据,因此您可能需要稍等一下。
我怎样才能排名更高?
为该语言编写一份好的自述文件,及时回复支持帖子,获得好评。
我的网址和显示名称哪个更重要?
两者都不。使您的显示名称易于记忆且具有描述性,同时将其控制在 5 个单词以内,以获得最大效益。
支持论坛
我如何收到论坛帖子的通知?
转到https://wordpress.org/support/plugin/YOURPLUGIN
并查看右侧的侧边栏。单击“订阅此插件”按钮以获取电子邮件警报。
我如何收到所有插件的通知?
每个插件支持论坛页面的顶部都有一个“订阅”按钮。单击该按钮,您将收到电子邮件。您可以在以下位置查看您订阅了哪些插件论坛集 https://wordpress.org/support/users/YOURID/subscriptions
对于 RSS,访问https://wordpress.org/support/view/plugin-committer/YOURID
将列出您有权提交访问的任何插件的所有支持请求和评论。不是提交者,只是被列为作者的人?使用 https://wordpress.org/support/view/plugin-contributor/YOURID
您还可以前往https://profiles.wordpress.org/YOURID/profile/notifications/
并输入您希望通过电子邮件发送的任何条款。请小心,如果您使用通用术语,情况可能会升级。
如何授予支持帐户访问我的插件的权限?
您可以将支持代表添加到您的插件中。支持代表可以将论坛主题标记为已解决或粘性(与插件作者和贡献者相同),但没有对该插件的提交访问权限。
用于管理插件支持代表的 UI 可以在插件页面上的高级视图中找到,位于管理提交者旁边。一旦某人被添加为支持代表,他们在回复插件支持主题或评论时将获得插件支持徽章。
你们会删除对我的插件的差评或评论吗?
一般不会。评论反映了个人对您的产品的体验。如果他们不喜欢,我们就无法改变。如果您认为评论无效(例如针对不同的插件),请使用 modlook
帖子上的按钮。论坛团队的一名成员将进行调查。
滥用 modlook 功能可能会导致您的插件被暂停。请明智地使用它。
什么是“马甲”?
当有人在论坛上创建多个帐户时就会发生这种情况,通常是为了给自己一些五星级评论,或者创建虚假的支持票以显得反应更快。马甲行为违反了我们的准则,将导致评论和帖子被删除,还可能导致您的帐户和所有插件被删除。不要这样做,也不要明目张胆地指责别人这样做。
封闭插件
如何关闭我的插件?
自 2020 年 4 月起,您可以随时关闭自己的插件。为此,请转到插件页面上的高级https://wordpress.org/plugins/myplugin/advanced/
选项卡(即)并向下滚动到关闭此插件部分。在那里您将看到一条警告消息和一个按钮。

如果您同意警告并想要关闭插件,请按按钮。
请记住,除非您能证明自己的情况合理,否则您将无法恢复插件。按请求关闭插件是永久性的。
如果我不小心关闭了插件怎么办?
发送电子邮件plugins@wordpress.org
并要求重新打开您的插件。不过,我们会询问您是如何做到这一点的,以便我们改进该功能的功能。
为什么它不允许我关闭我自己的插件?
假设您以正确的帐户登录,可能是因为您的用户太多。如果您的插件有超过 10,000 个用户,您需要发送电子邮件plugins@wordpress.org
并请求我们关闭它。
我可以暂时关闭我的插件吗?
不。
我们不允许这样做,因为这会给用户带来糟糕的体验。隐藏插件会让用户认为该插件已因安全或指南问题而被删除,这导致他们不再信任您。我们无法阻止他们的想法,因此我们禁止“临时”关闭。
一般来说,当他们的插件有一个正在修复的错误,或者当他们无法支持它时,人们希望这样做。我们建议您尽快修复错误,或者如果您不支持该插件,请更新自述文件以说明它当前不受支持以及原因。
如果这是一个全新的插件,您应该将其称为“公共测试版”,以便人们了解其状态。
插件关闭时会发生什么?
当插件关闭时,页面显示为已关闭并且不再生成 zip。没有人能够通过网站下载该插件,也无法通过 WordPress 管理员安装它。SVN 存储库将保持可访问性,以允许其他人根据目录的原则下载和派生代码(如果需要)。
60 天后,关闭消息将发生变化,以提醒人们 关闭的原因,但仅限于最广泛的术语(违反指南、安全等),而不是明确的详细信息。
为什么我的插件被关闭了?
插件因违反准则、安全问题或应作者请求而被关闭。如果出现活跃问题(例如版权侵权、滥用和安全),所有具有插件提交访问权限的帐户都会收到通知。
如果某个插件在 6 个月内从未使用过(即没有代码推送到 SVN)、SVN 已损坏超过 12 个月,或者插件的自述文件表明其已弃用,我们可能会关闭,恕不另行通知。
为什么别人的插件被关闭了?
截至 2017 年,插件数据库中跟踪了插件关闭原因。插件关闭六十天后,关闭原因将被公开:

请注意:我们不会公开披露插件被关闭的确切原因的详细信息。
我可以关闭别人的插件吗?
如果您向 报告插件中的安全问题或违反准则的行为plugins@wordpress.org
,我们将进行审查并采取适当的措施。大多数时候,这涉及关闭插件。除非您提出要求,否则我们不会公开您的姓名,以保护您免受强烈反对。
有人发布了我的插件的副本!我该怎么办?
plugins@wordpress.org
包含被盗插件链接的电子邮件。请附上我们可以下载您的文件的链接或附上 zip 文件。我们将比较这两个文件以及我们拥有的所有编码历史记录,以确定该插件是否确实是盗窃,或者只是一个未经认可的分叉。
请记住,如果您将插件许可为 GPLv2 或更高版本,那么只要版权保持完整并且您获得认可,就完全可以分叉您的作品。
如果有人复制了我的部分代码但没有注明来源,我该怎么办?
plugins@wordpress.org
立即发送电子邮件!特别是如果您的代码不是 GPL。虽然我们确实允许人们分叉其他插件并将该代码包含在他们自己的插件中,但它必须始终被记入。版权和学分是一个要求。
您会关闭另一个侵犯品牌/商标的插件吗?
我们尽最大努力维护版权和商标要求,并防止品牌混淆。在插件获得批准之前,我们经常要求他们进行一些更明显的更改。也就是说,当目录中有 60,000 个插件,并且某些术语非常常见(例如“弹出”或“一体化”)时,URL 或名称的“不同”程度是有限的。因此,我们要求开发人员更改插件的 显示名称,以免再造成冲突或混乱。
如果有人明显侵犯您的版权、商标或现有品牌,无论是通过显示名称还是使用商标图像,请向我们发送电子邮件并提供plugins@wordpress.org
一些证据,我们将联系开发商并要求更改。
我们确实希望这些都是 合理的要求。也就是说,如果您向我们发送投诉并列出 12 个插件,这些插件都使用术语“最佳联系表单”(因为这是您的插件名称),我们将审核这些插件,并且仅在它们过度使用该短语时才将其关闭。如果他们使用一次(即“这是法罗群岛最好的联系表单插件”),那么这是可以接受的。如果他们在短语中填充关键字,我们更有可能因关键字填充而关闭它们。简而言之,如果您的插件名称非常通用,就会发生这种情况,而且通常不是 侵权案件。
另请注意,如果这不是您的商标,我们将无法接受您的报告。管理和维护商标所有者的责任,而不是用户的责任。
如何发送安全报告?
通过电子邮件plugins@wordpress.org
清晰简洁地描述问题。请阅读我们有关报告安全问题的文档以了解详细信息。
你们是否为发现插件中的错误提供奖励?
不会。我们与任何错误赏金计划没有关系,因此我们不会向他们提交您的报告等。我们唯一合作的网站是hackerone.com/automattic,该网站针对与 Automattic 属性相关的错误。其他的一切都是你自己的,不要要求我们提交东西。
您是否帮助提交或提供 CVE?
不可以。我们没有能力协助解决 CVE。
我的插件被关闭了,我可以重新打开它吗?
或许。如果它因安全原因而关闭,请修复问题,回复电子邮件,大多数时候我们会重新打开该插件,除非它存在更多安全问题或严重的指南问题。如果因违反准则而被关闭,则取决于违规的严重程度和性质。例如,与初次犯罪者相比,惯犯重新打开插件的可能性较小。
如果您要求关闭该插件,您将需要解释为什么改变主意。插件旨在在开发人员请求时保持关闭状态,并且在一个月后不会再次重新打开。
所有插件必须通过当前标准和安全审查才能恢复。这不是可选的。与您解决所有潜在问题的一次较长时间的关闭相比,用户会因为多次关闭您的插件而对您失去更多信心。
当我的员工/同事违反准则时,为什么我的插件被关闭?
代表插件的每个人,从支持技术到开发人员,都是插件所有者的责任。如果他们严重违反准则,那么业主应该接受这些后果并采取纠正措施。如果没有发生这种情况,插件就会关闭。在这些情况下,我们会通知插件所有者并解释原因,并尽力保持插件开放。
我所有的插件都被关闭了!我怎样才能把它们找回来?
我们关闭开发人员的所有插件的情况非常罕见。一般来说,发生这种情况的原因如下:
- 您要求我们关闭您的所有插件
- 电子邮件问题
- 电子邮件被退回,我们无法取得联系
- 该电子邮件向我们发送了自动回复,并且至少发送了两次警告来解决这个问题
- 指南问题
- 之前曾对行为进行谴责和/或发出过最终警告
- 向名录和/或志愿者发出法律威胁
- 该违规行为被视为“极其严重”(死亡威胁、数百个袜子木偶、骚扰等)
如果您要求我们关闭它们,您必须解释 为什么改变主意。
如果您遇到电子邮件问题,则必须解决它们,并且需要使所有插件符合当前的安全标准和准则。
至于最后一个……一般来说你不会再回来了。如果我们针对您的行为向您发出最终警告,并且在不到一年的时间内您再次启动时遇到问题(或未能解决我们提到的所有问题),我们将不会重新开放您的插件。
我刚刚收到最后警告。我该怎么办?
首先,也是最重要的一点,要认真对待。该电子邮件将准确列出问题所在以及我们选择升级为最终警告的原因。插件所有者应解决所有问题,停止造成新的违反准则的行为,并密切监视任何同事的行为。简而言之,停止违反准则,停止找借口,为任何不当行为道歉,并改正方向。
我们最不想做的就是禁止某人并禁用他们的所有插件。这对社区来说并不健康。同时,如果开发人员无法或不愿意遵守与其他人相同的规则,那么将其保留在目录中是有害的,并且对其他人不尊重。
插件所有权
如何让其他人访问我的插件?
要将用户添加为提交者,即授予他们更新代码的权限,请转到https://wordpress.org/plugins/YOURPLUGIN/advanced
并将其用户名添加为提交者。
要让他们显示为作者,请将他们的用户名添加到readme.txt
文件中。
不要将普通用户添加为作者。它仅适用于帮助开发的人。这意味着如果有人“启发”了您,您不应该将他们添加为作者。
如何从我的插件中删除某人的访问权限?
任何具有提交访问权限的人都可以执行此操作。转到https://wordpress.org/plugins/YOURPLUGIN/advanced
他们的 ID 并将其悬停在其上。将出现删除链接。点击它。
请不要删除自己。
如何更改插件所有者?
转到“高级”选项卡并向下滚动到“危险区域”。在那里您将看到“传输您的插件”部分。从下拉列表中选择某人,然后单击按钮。
有关更多详细信息,请阅读有关传输插件的文档。
我尝试转移我的插件,但它说我不能。为什么不?
拥有大量用户(超过 10,000 名)的插件或被认为对 WordPress 项目至关重要的插件(例如特色插件或测试版插件)只能通过向插件团队提出书面请求来转移。请阅读有关传输插件的文档以了解详细信息。
如何接管废弃的插件?
我们要求您首先尝试与原始开发人员联系,以便他们可以添加您。在某些情况下,这是不可能的,您应该从修复插件开始。确保它符合编码标准、安全,并更新版权信息以包含您自己。然后您可以联系我们了解插件采用事宜。
我们不保证您会获得任何人的插件,即使在成功审核之后也是如此。
这些购买我的插件的优惠合法吗?
简短回答:可能不会。
许多开发人员会收到未经请求的电子邮件或购买其插件的报价。我们发现其中绝大多数都是欺诈性的,不 建议您跟进。
虽然合法的优惠确实存在,但它们通常来自与插件相关的官方公司,或者来自成熟的插件公司。那些以“我们正在联系 WordPress 社区……”或“我们正在寻求获取现有的 WordPress 插件……”开头的内容不应被信任。此类购买通常会通过跟踪用户或其他严重违反准则等卑鄙策略来破坏插件(以及原始开发者)的声誉。
如果您确实选择出售您的插件(或将其赠送给其他人),请确保新所有者了解存储库的所有准则。如果他们违反了我们的条款,该插件将被删除,并且我们可能不会根据违规程度将其返还。任何拥有插件访问权限的人都对其用户行为拥有所有权和责任。垃圾邮件、插入跟踪数据和添加垃圾功能是破坏插件的最快方法。
我们主张只将您的插件提供给您亲自审查过的人,并且您信任他们会对您的代码和用户负责。
当插件开发者去世时会发生什么?
当开发人员被确定死亡时,他们将从自己的插件中删除,以防止不道德的人获得访问权限并伤害用户。如果他们是唯一的开发者,则该插件可能会被关闭。我们尽一切努力寻找他们的朋友和同事,为他们提供首先采用代码的机会,但如果找不到可靠或愿意的人,则插件将被关闭。
开发者工具
开发者工具
有多种工具可用于帮助插件开发。其中一些在您的开发环境中运行(xdebug、PHPCS等),但也有一些优秀的工具可以直接在 WordPress 中运行,以帮助您正确构建内容并诊断问题。本章讨论浏览器内工具。
调试栏和附加组件
调试栏是一个插件,它将调试菜单添加到管理栏,显示查询、缓存和其他有用的调试信息。
调试栏
这是主插件,添加了由本页列出的其余插件扩展的基本功能。
当启用 WP_DEBUG 时,它还会跟踪 PHP 警告和通知,以便更容易找到它们。
当启用 SAVEQUERIES 时,将跟踪并显示 mysql 查询。
调试栏控制台
该插件添加了一个控制台,您可以在其中运行任意 PHP。这非常适合测试变量的内容以及许多其他用途。
调试栏简码
该插件向调试栏添加了一个新面板,显示当前请求的已注册短代码。
此外,它还会向您显示:
- 短代码调用哪个函数/方法。
- 是否在当前帖子/页面/帖子类型上使用短代码以及如何使用(仅当为单数时)。
- 有关短代码的任何可用附加信息,例如描述、它采用哪些参数、是否是自动关闭的。
- 找出使用短代码的所有页面/帖子/等。
调试栏常量
该插件向调试栏添加了三个新面板,显示您作为开发人员可用于当前请求的已定义常量:
- 工作常数
- WP 类常量
- PHP 常量
调试栏帖子类型
该插件向调试栏添加了一个新面板,显示有关站点注册帖子类型的详细信息。
调试栏 Cron
该插件在调试栏中添加了一个新面板,显示有关 WordPress 计划事件的信息。
安装后,您将可以访问以下信息:
- 预定活动数量
- 如果 cron 当前正在运行
- 下次活动时间
- 当前时间
- 自定义预定事件列表
- 核心预定活动列表
- 时间表清单
调试栏操作和过滤器插件
该插件在调试栏中添加了两个选项卡,以显示附加到当前请求的挂钩(操作和过滤器)。“操作”选项卡显示与当前请求挂钩的操作。过滤器选项卡显示过滤器标签以及附加到其上的具有各自优先级的功能。
调试栏瞬变
该插件将有关 WordPress 瞬态的信息添加到调试栏中的新面板。
安装后,您将可以访问以下信息:
- 现有瞬变数量
- 自定义瞬态列表
- 核心瞬态列表
- 自定义站点瞬态列表
- 核心站点瞬变列表
- 删除瞬态的选项
调试栏列表脚本和样式依赖项
该插件列出了加载的脚本和样式、加载顺序以及存在的依赖项。
调试栏远程请求
该插件将向调试栏添加一个新面板,该面板将显示和分析通过 HTTP API 发出的远程请求。
安装后,您将可以访问以下信息:
- 请求方法(GET、POST 等)
- 网址
- 每个请求的时间
- 所有请求的总时间
- 请求总数
或者,您可以添加?dbrr_full=1
到 URL 以获取其他信息,包括所有请求参数和带标头的响应的完整转储。
辅助插件
查询监控器
Query Monitor 是一个调试插件,适合使用 WordPress 进行开发的任何人。您可以查看有关数据库查询、挂钩、条件、HTTP 请求、重定向等的调试和性能信息。它具有其他调试插件所不具备的一些高级功能,包括自动 AJAX 调试以及通过插件或主题缩小范围的能力。