学习 WordPress 插件开发是一个非常有益且实用的技能,可以帮助你扩展和定制 WordPress 网站的功能。以下是一个详细的指南,帮助你从零开始学习 WordPress 插件开发。
1. 基础知识准备
在开始插件开发之前,你需要具备一些基本知识:
- HTML/CSS:了解基本的网页结构和样式。
- JavaScript:掌握基本的编程逻辑和DOM操作。
- PHP:WordPress 插件主要使用 PHP 编写,需要了解 PHP 的基本语法和功能。
- MySQL:了解基本的数据库操作,因为 WordPress 使用 MySQL 作为数据库。
2. 设置开发环境
为插件开发设置一个合适的开发环境:
- 本地服务器:使用工具如 XAMPP、MAMP 或 Local by Flywheel 来设置一个本地开发服务器。
- 文本编辑器或 IDE:选择一个代码编辑器,如 Visual Studio Code、Sublime Text 或 PHPStorm。
- WordPress 安装:在本地服务器上安装最新版本的 WordPress。
3. 了解 WordPress 插件结构
一个简单的 WordPress 插件通常包含以下文件和文件夹:
- 插件主文件:通常命名为
plugin-name.php
,包含插件的主要功能代码。 - README 文件:包含插件的基本信息和说明。
- 其他文件:如 CSS、JavaScript、图片等资源文件。
4. 创建第一个插件
下面是创建一个简单插件的步骤:
4.1 创建插件目录和主文件
在 wp-content/plugins
目录下创建一个新文件夹,例如 my-first-plugin
。在该文件夹中创建一个 PHP 文件,例如 my-first-plugin.php
。
4.2 插件头部信息
在 my-first-plugin.php
文件中添加插件的头部信息:
<?php
/*
Plugin Name: My First Plugin
Plugin URI: https://example.com/my-first-plugin
Description: This is my first WordPress plugin.
Version: 1.0
Author: Your Name
Author URI: https://example.com
License: GPL2
*/
4.3 插件的基本功能
添加一些基本功能,如在 WordPress 后台显示一个简单的信息:
function my_first_plugin_admin_notice() {
echo '<div class="notice notice-success is-dismissible">
<p>My First Plugin is activated!</p>
</div>';
}
add_action('admin_notices', 'my_first_plugin_admin_notice');
5. 学习 WordPress 钩子(Hooks)
WordPress 插件开发的核心是钩子(Hooks),它包括动作钩子(Action Hooks)和过滤器钩子(Filter Hooks)。通过钩子,你可以在特定的时间点执行代码:
- 动作钩子(Actions):允许你在 WordPress 的执行过程中插入自定义代码。
- 过滤器钩子(Filters):允许你修改 WordPress 数据或输出。
例子:
// 动作钩子
add_action('wp_footer', 'my_first_plugin_footer_message');
function my_first_plugin_footer_message() {
echo '<p>Thank you for visiting my website!</p>';
}
// 过滤器钩子
add_filter('the_content', 'my_first_plugin_content_filter');
function my_first_plugin_content_filter($content) {
return $content . '<p>Custom content added by My First Plugin.</p>';
}
6. 学习 WordPress 插件 API
WordPress 提供了丰富的 API 供插件开发者使用:
- Settings API:管理插件设置页面。
- Shortcode API:创建短代码。
- Widgets API:创建自定义小工具。
- REST API:创建自定义 REST 端点。
7. 学习插件国际化和本地化
为了使你的插件可以被翻译成不同的语言,了解如何国际化和本地化插件是很重要的:
// 加载插件文本域
function my_first_plugin_load_textdomain() {
load_plugin_textdomain( 'my-first-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
}
add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
8. 探索插件开发资源
- 官方文档:阅读 WordPress 插件开发手册。
- 教程和书籍:查找相关的在线教程和书籍,如《Professional WordPress Plugin Development》。
9. 配置插件设置页面
为插件添加一个设置页面是常见的需求。以下是如何使用 Settings API 创建一个简单的插件设置页面的步骤:
9.1 添加菜单项
首先,需要在 WordPress 管理菜单中添加一个新的菜单项:
function my_first_plugin_menu() {
add_menu_page(
'My First Plugin Settings', // 页面标题
'My First Plugin', // 菜单标题
'manage_options', // 用户权限
'my-first-plugin', // 菜单别名
'my_first_plugin_settings_page', // 回调函数
'dashicons-admin-generic' // 图标
);
}
add_action('admin_menu', 'my_first_plugin_menu');
9.2 创建设置页面
在回调函数 my_first_plugin_settings_page
中定义设置页面的内容:
function my_first_plugin_settings_page() {
?>
<div class="wrap">
<h1>My First Plugin Settings</h1>
<form method="post" action="options.php">
<?php
settings_fields('my_first_plugin_options_group');
do_settings_sections('my-first-plugin');
submit_button();
?>
</form>
</div>
<?php
}
9.3 注册设置
使用 Settings API 注册插件设置:
function my_first_plugin_settings_init() {
register_setting('my_first_plugin_options_group', 'my_first_plugin_option_name');
add_settings_section(
'my_first_plugin_settings_section', // ID
'My First Plugin Settings', // 标题
'my_first_plugin_settings_section_callback', // 回调函数
'my-first-plugin' // 页面
);
add_settings_field(
'my_first_plugin_field', // ID
'Sample Field', // 标签
'my_first_plugin_field_callback', // 回调函数
'my-first-plugin', // 页面
'my_first_plugin_settings_section' // 部分
);
}
add_action('admin_init', 'my_first_plugin_settings_init');
function my_first_plugin_settings_section_callback() {
echo 'Enter your settings below:';
}
function my_first_plugin_field_callback() {
$option = get_option('my_first_plugin_option_name');
echo '<input type="text" name="my_first_plugin_option_name" value="' . esc_attr($option) . '" />';
}
10. 创建短代码
短代码允许用户在文章和页面中插入自定义内容。以下是如何创建一个简单的短代码:
function my_first_plugin_shortcode($atts) {
$atts = shortcode_atts(
array(
'text' => 'Hello, World!',
),
$atts,
'my_shortcode'
);
return '<div class="my-shortcode">' . esc_html($atts['text']) . '</div>';
}
add_shortcode('my_shortcode', 'my_first_plugin_shortcode');
11. 创建小工具
小工具可以在 WordPress 侧边栏等小工具区域显示内容。以下是如何创建一个简单的小工具:
```php
class My_First_Plugin_Widget extends WP_Widget {
function construct() { parent::_construct( 'my_first_plugin_widget', // ID ('My First Plugin Widget', 'text_domain'), // 名称
array('description' => _('A simple widget', 'text_domain')) // 描述
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
echo '<p>' . esc_html($instance['text']) . '</p>';
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : __('New title', 'text_domain');
$text = !empty($instance['text']) ? $instance['text'] : __('Hello, World!', 'text_domain');
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('Title:'); ?></label>
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('text')); ?>"><?php _e('Text:'); ?></label>
11. 创建小工具(续)
11.1 完成小工具的表单和更新方法
在上面的代码基础上,继续完成小工具的表单和更新方法:
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('text')); ?>" name="<?php echo esc_attr($this->get_field_name('text')); ?>"><?php echo esc_attr($text); ?></textarea>
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
$instance['text'] = (!empty($new_instance['text'])) ? strip_tags($new_instance['text']) : '';
return $instance;
}
}
// 注册小工具
function register_my_first_plugin_widget() {
register_widget('My_First_Plugin_Widget');
}
add_action('widgets_init', 'register_my_first_plugin_widget');
12. 使用 WordPress REST API
WordPress REST API 允许你创建和访问自定义端点,从而使你的插件能够与外部应用程序进行通信。
12.1 创建自定义 REST 端点
以下是如何创建一个简单的自定义 REST 端点:
function my_first_plugin_register_routes() {
register_rest_route('my-first-plugin/v1', '/data/', array(
'methods' => 'GET',
'callback' => 'my_first_plugin_get_data',
));
}
add_action('rest_api_init', 'my_first_plugin_register_routes');
function my_first_plugin_get_data($request) {
return new WP_REST_Response(array('message' => 'Hello, World!'), 200);
}
13. 安全性和最佳实践
确保你的插件是安全的,并遵循最佳实践:
13.1 数据验证和清理
在处理用户输入时,确保对数据进行验证和清理:
// 验证和清理输入数据
function my_first_plugin_validate_data($data) {
return sanitize_text_field($data);
}
13.2 使用非ces (Nonces) 进行安全验证
在处理表单提交时,使用非ces 来防止跨站请求伪造 (CSRF) 攻击:
// 创建一个 nonce
function my_first_plugin_create_nonce() {
return wp_create_nonce('my_first_plugin_nonce_action');
}
// 验证 nonce
function my_first_plugin_verify_nonce($nonce) {
return wp_verify_nonce($nonce, 'my_first_plugin_nonce_action');
}
13.3 避免直接文件访问
在插件的每个文件顶部添加以下代码,防止直接访问:
if (!defined('ABSPATH')) {
exit; // 退出如果直接访问
}
14. 插件国际化和本地化(续)
确保你的插件可以被翻译成不同的语言:
14.1 创建语言文件
在插件目录下创建一个 languages
文件夹,并使用 .pot
文件保存插件的翻译字符串。例如,创建一个 my-first-plugin.pot
文件。
14.2 加载语言文件
在插件初始化时加载语言文件:
function my_first_plugin_load_textdomain() {
load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
15. 发布和分发插件
当你完成插件开发后,可以考虑将插件发布到 WordPress 插件目录中。
15.1 准备插件
确保你的插件包含以下文件:
- 主插件文件:包含插件头部信息和主要功能代码。
- README 文件:详细描述插件的功能、安装方法和使用说明。
- 语言文件:包含
.pot
文件和翻译文件。 - 其他资源文件:如 CSS、JavaScript、图片等资源文件。
15.2 创建 README 文件
使用 Markdown 编写 README 文件,并确保它包含以下部分:
- Plugin Name: 插件名称
- Description: 插件描述
- Installation: 安装说明
- Frequently Asked Questions: 常见问题解答
- Changelog: 更新日志
15.3 提交插件
访问 WordPress 插件提交页面 并按照指南提交你的插件。一旦审核通过,你的插件将出现在 WordPress 插件目录中。
16. 进一步学习资源
要深入学习 WordPress 插件开发,以下资源非常有用:
16.1 官方文档
- WordPress Plugin Handbook: 官方插件开发手册,包含详细的开发指南和示例代码。
- WordPress Code Reference: 提供了 WordPress 核心函数、类、钩子的详细文档。
16.2 在线课程和教程
- Udemy: 提供了许多关于 WordPress 插件开发的在线课程。
- Lynda/LinkedIn Learning: 提供了高质量的 WordPress 开发视频教程。
16.3 开发者社区
- WordPress Stack Exchange: 专门针对 WordPress 开发问题的问答社区。
- WordPress.org 支持论坛: 官方支持论坛,可以在这里提问和回答问题。
16.4 博客和文章
- Tuts+: 提供了大量关于 WordPress 开发的文章和教程。
- Smashing Magazine: 定期发布关于 WordPress 的开发技巧和最佳实践的文章。
17. 高级插件开发技巧
17.1 面向对象编程 (OOP)
使用面向对象编程风格可以使你的插件代码更具可维护性和可扩展性。以下是一个简单的 OOP 插件示例:
class My_First_Plugin {
public function __construct() {
add_action('init', array($this, 'init'));
}
public function init() {
// 初始化代码
}
public function register_post_type() {
$args = array(
'public' => true,
'label' => 'Custom Post',
);
register_post_type('custom_post', $args);
}
}
$my_first_plugin = new My_First_Plugin();
17.2 使用命名空间
使用命名空间可以避免类名和函数名冲突:
namespace MyFirstPlugin;
class Main {
public function __construct() {
add_action('init', array($this, 'init'));
}
public function init() {
// 初始化代码
}
public function register_post_type() {
$args = array(
'public' => true,
'label' => 'Custom Post',
);
register_post_type('custom_post', $args);
}
}
$main = new Main();
17.3 自动加载
使用自动加载器可以简化类文件的加载:
spl_autoload_register(function ($class) {
$prefix = 'MyFirstPlugin\\';
$base_dir = __DIR__ . '/inc/';
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
return;
}
$relative_class = substr($class, $len);
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
if (file_exists($file)) {
require $file;
}
});
17.4 单例模式
使用单例模式确保插件的主类只实例化一次:
class My_First_Plugin {
private static $instance = null;
private function __construct() {
add_action('init', array($this, 'init'));
}
public static function get_instance() {
if (self::$instance == null) {
self::$instance = new self();
}
return self::$instance;
}
public function init() {
// 初始化代码
}
}
My_First_Plugin::get_instance();
18. 性能优化
确保你的插件不会对网站性能产生负面影响:
18.1 使用缓存
利用 WordPress 内置的缓存功能:
function get_cached_data() {
$cache_key = 'my_first_plugin_cached_data';
$data = wp_cache_get($cache_key);
if ($data === false) {
// 获取数据
$data = '...';
wp_cache_set($cache_key, $data, '', 3600);
}
return $data;
}
19. 数据库操作
19.1 创建自定义数据库表
有时候,插件需要创建自定义数据库表来存储特定的数据。以下是创建自定义表的示例:
function my_first_plugin_create_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name tinytext NOT NULL,
email text NOT NULL,
date datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'my_first_plugin_create_table');
19.2 插入数据
function my_first_plugin_insert_data($name, $email) {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$wpdb->insert(
$table_name,
array(
'name' => $name,
'email' => $email,
'date' => current_time('mysql'),
)
);
}
19.3 获取数据
function my_first_plugin_get_data() {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$results = $wpdb->get_results("SELECT * FROM $table_name", ARRAY_A);
return $results;
}
20. AJAX 操作
使用 AJAX 可以在不刷新页面的情况下与服务器进行交互。
20.1 在前端调用 AJAX
jQuery(document).ready(function($) {
$('#my-button').click(function() {
$.ajax({
url: my_first_plugin_ajax_object.ajax_url,
type: 'POST',
data: {
action: 'my_first_plugin_action',
data: 'some_data'
},
success: function(response) {
console.log(response);
}
});
});
});
20.2 在后端处理 AJAX 请求
function my_first_plugin_ajax_handler() {
$data = $_POST['data'];
// 处理数据
$response = array('message' => 'Success', 'data' => $data);
wp_send_json_success($response);
}
add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');
add_action('wp_ajax_nopriv_my_first_plugin_action', 'my_first_plugin_ajax_handler');
21. 使用自定义钩子和过滤器
创建自定义钩子和过滤器,使其他开发者可以扩展你的插件功能。
21.1 创建自定义动作钩子
function my_first_plugin_do_custom_action() {
do_action('my_first_plugin_custom_action');
}
function my_first_plugin_add_custom_action() {
echo 'Custom Action Triggered!';
}
add_action('my_first_plugin_custom_action', 'my_first_plugin_add_custom_action');
21.2 创建自定义过滤器
function my_first_plugin_apply_custom_filter($content) {
return apply_filters('my_first_plugin_custom_filter', $content);
}
function my_first_plugin_modify_content($content) {
return $content . ' - Modified by custom filter';
}
add_filter('my_first_plugin_custom_filter', 'my_first_plugin_modify_content');
22. 使用第三方库
有时,你可能需要在插件中使用第三方库。你可以通过 Composer 来管理依赖关系。
22.1 安装 Composer
确保在你的开发环境中安装了 Composer。
22.2 创建 composer.json
文件
在插件根目录下创建一个 composer.json
文件:
{
"name": "yourname/yourplugin",
"description": "A description of your plugin",
"require": {
"some/package": "^1.0"
}
}
22.3 安装依赖
在终端中运行以下命令:
composer install
22.4 在插件中加载 Composer 自动加载器
在插件的主文件中添加以下代码:
require_once __DIR__ . '/vendor/autoload.php';
23. 测试和调试(续)
23.1 使用 WP_DEBUG
在 wp-config.php
文件中启用调试模式:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);
此设置会将错误记录到 wp-content/debug.log
文件中,而不是直接显示在页面上。
23.2 使用 PHPStorm 和 Xdebug 进行调试
- 安装 Xdebug: 根据你的 PHP 版本和操作系统安装 Xdebug。
- 配置 php.ini: 添加以下行到你的
php.ini
文件:zend_extension="path/to/xdebug.so" xdebug.remote_enable=1 xdebug.remote_host=127.0.0.1 xdebug.remote_port=9000 xdebug.remote_handler=dbgp
- 配置 PHPStorm: 在 PHPStorm 中配置 Xdebug 远程调试。在 “Preferences” -> “Languages & Frameworks” -> “PHP” -> “Debug” 中设置 Xdebug 端口为 9000。
- 设置断点: 在 PHPStorm 中打开你的插件代码并设置断点。
- 启动调试: 启动 PHPStorm 的调试监听,然后在浏览器中访问你的插件页面。代码将在断点处暂停,你可以逐步调试。
24. 国际化与本地化
24.1 准备插件以进行翻译
使用 __()
和 _e()
函数准备你的插件代码:
echo __('Hello, World!', 'my-first-plugin');
_e('Hello, World!', 'my-first-plugin');
24.2 创建 POT 文件
使用 xgettext
或 Poedit 等工具生成 POT 文件:
xgettext --from-code=UTF-8 -k__ -k_e -o languages/my-first-plugin.pot $(find . -name "*.php")
24.3 加载翻译文件
在插件初始化时加载翻译文件:
function my_first_plugin_load_textdomain() {
load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
25. 安全性
25.1 数据验证和清理
确保所有用户输入的数据都经过验证和清理:
$cleaned_data = sanitize_text_field($_POST['data']);
25.2 权限检查
在处理敏感操作之前检查用户权限:
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
}
25.3 防止 SQL 注入
使用 $wpdb->prepare()
函数来防止 SQL 注入:
global $wpdb;
$sql = $wpdb->prepare("SELECT * FROM {$wpdb->prefix}my_table WHERE id =
$results = $wpdb->get_results($sql);
26. 创建设置页面
26.1 添加设置页面
function my_first_plugin_add_admin_menu() {
add_options_page(
__('My First Plugin Settings', 'my-first-plugin'),
__('My First Plugin', 'my-first-plugin'),
'manage_options',
'my-first-plugin',
'my_first_plugin_options_page'
);
}
add_action('admin_menu', 'my_first_plugin_add_admin_menu');
26.2 创建设置页面内容
function my_first_plugin_options_page() {
?>
<div class="wrap">
<h1><?php _e('My First Plugin Settings', 'my-first-plugin'); ?></h1>
<form action="options.php" method="post">
<?php
settings_fields('my_first_plugin_options');
do_settings_sections('my-first-plugin');
submit_button();
?>
</form>
</div>
<?php
}
26.3 注册设置
```php
function my_first_plugin_settings_init() {
register_setting('my_first_plugin_options', 'my_first_plugin_options');
add_settings_section(
'my_first_plugin_section',
__('General Settings', 'my-first-plugin'),
'my_first_plugin_section_callback',
'my-first-plugin'
);
add_settings_field(
'my_first_plugin_field_text',
__('Sample Text Field', 'my-first-plugin'),
'my_first_plugin_field_text_callback',
'
26. 创建设置页面(续)
26.3 注册设置(续)
add_settings_field(
'my_first_plugin_field_text',
__('Sample Text Field', 'my-first-plugin'),
'my_first_plugin_field_text_callback',
'my-first-plugin',
'my_first_plugin_section'
);
}
add_action('admin_init', 'my_first_plugin_settings_init');
function my_first_plugin_section_callback() {
echo __('Enter your settings below:', 'my-first-plugin');
}
function my_first_plugin_field_text_callback() {
$options = get_option('my_first_plugin_options');
?>
<input type="text" name="my_first_plugin_options[my_first_plugin_field_text]" value="<?php echo isset($options['my_first_plugin_field_text']) ? esc_attr($options['my_first_plugin_field_text']) : ''; ?>">
<?php
}
27. 创建自定义小工具
27.1 定义小工具类
class My_First_Plugin_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'my_first_plugin_widget', // Base ID
__('My First Plugin Widget', 'my-first-plugin'), // Name
array('description' => __('A sample widget for showing text', 'my-first-plugin')) // Args
);
}
public function widget($args, $instance) {
echo $args['before_widget'];
if (!empty($instance['title'])) {
echo $args['before_title'] . apply_filters('widget_title', $instance['title']) . $args['after_title'];
}
echo __('Hello, World!', 'my-first-plugin');
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : __('New title', 'my-first-plugin');
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('Title:', 'my-first-plugin'); ?></label>
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = (!empty($new_instance['title'])) ? strip_tags($new_instance['title']) : '';
return $instance;
}
}
27.2 注册小工具
function my_first_plugin_register_widgets() {
register_widget('My_First_Plugin_Widget');
}
add_action('widgets_init', 'my_first_plugin_register_widgets');
28. 使用 REST API
28.1 创建自定义 REST API 路由
function my_first_plugin_register_rest_route() {
register_rest_route('my-first-plugin/v1', '/data/', array(
'methods' => 'GET',
'callback' => 'my_first_plugin_get_data',
));
}
add_action('rest_api_init', 'my_first_plugin_register_rest_route');
function my_first_plugin_get_data($request) {
$data = array('message' => 'Hello, World!');
return new WP_REST_Response($data, 200);
}
28.2 使用 REST API
在前端使用 JavaScript 访问自定义 REST API 路由:
fetch('/wp-json/my-first-plugin/v1/data/')
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
29. 自定义短代码
29.1 注册短代码
function my_first_plugin_shortcode($atts = [], $content = null) {
$atts = array_change_key_case((array)$atts, CASE_LOWER);
$a = shortcode_atts([
'title' => 'Default Title',
], $atts);
return '<div class="my-first-plugin">' . esc_html($a['title']) . '</div>';
}
add_shortcode('my_first_plugin', 'my_first_plugin_shortcode');
29.2 使用短代码
在文章或页面中使用短代码:
[my_first_plugin title="Hello, World!"]
30. 单元测试(续)
30.1 设置 PHPUnit(续)
在 tests
目录中创建 bootstrap.php
文件以初始化测试环境:
<?php
$_tests_dir = getenv('WP_TESTS_DIR');
if (!$_tests_dir) {
$_tests_dir = rtrim(sys_get_temp_dir(), '/\\') . '/wordpress-tests-lib';
}
require_once $_tests_dir . '/includes/functions.php';
function _manually_load_plugin() {
require dirname(__DIR__) . '/my-first-plugin.php';
}
tests_add_filter('muplugins_loaded', '_manually_load_plugin');
require $_tests_dir . '/includes/bootstrap.php';
在 tests
目录中创建 test-sample.php
文件以编写第一个测试:
<?php
class SampleTest extends WP_UnitTestCase {
function test_sample() {
$this->assertTrue(true);
}
}
30.2 运行测试
使用以下命令运行 PHPUnit 测试:
phpunit --bootstrap tests/bootstrap.php tests/test-sample.php
31. 使用自定义表单
31.1 创建自定义表单
在插件中使用 HTML 和 PHP 创建自定义表单:
function my_first_plugin_form() {
?>
<form action="<?php echo esc_url(admin_url('admin-post.php')); ?>" method="post">
<input type="hidden" name="action" value="my_first_plugin_form_action">
<?php wp_nonce_field('my_first_plugin_form_nonce', 'my_first_plugin_nonce'); ?>
<label for="name"><?php _e('Name:', 'my-first-plugin'); ?></label>
<input type="text" name="name" id="name" required>
<input type="submit" value="<?php _e('Submit', 'my-first-plugin'); ?>">
</form>
<?php
}
31.2 处理表单提交
在插件中处理表单提交:
function my_first_plugin_form_handler() {
if (!isset($_POST['my_first_plugin_nonce']) || !wp_verify_nonce($_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {
wp_die(__('Nonce verification failed', 'my-first-plugin'));
}
if (!current_user_can('manage_options')) {
wp_die(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
}
$name = sanitize_text_field($_POST['name']);
// 处理表单数据,例如保存到数据库
wp_redirect(admin_url('options-general.php?page=my-first-plugin&message=success'));
exit;
}
add_action('admin_post_my_first_plugin_form_action', 'my_first_plugin_form_handler');
32. 使用 AJAX
32.1 注册 AJAX 动作
function my_first_plugin_ajax_handler() {
check_ajax_referer('my_first_plugin_nonce', 'security');
if (!current_user_can('manage_options')) {
wp_send_json_error(__('You do not have sufficient permissions to access this page.', 'my-first-plugin'));
}
$response = array('message' => __('Hello, World!', 'my-first-plugin'));
wp_send_json_success($response);
}
add_action('wp_ajax_my_first_plugin_action', 'my_first_plugin_ajax_handler');
32.2 在前端使用 AJAX
在前端脚本中使用 AJAX 进行请求:
jQuery(document).ready(function($) {
$('#my-button').on('click', function() {
$.ajax({
url: ajaxurl,
type: 'POST',
data: {
action: 'my_first_plugin_action',
security: my_first_plugin_vars.nonce
},
success: function(response) {
if (response.success) {
alert(response.data.message);
} else {
alert('Error: ' + response.data);
}
}
});
});
});
在插件中注册和本地化脚本:
function my_first_plugin_enqueue_scripts() {
wp_enqueue_script('my-first-plugin-script', plugins_url('js/my-first-plugin.js', __FILE__), array('jquery'), null, true);
wp_localize_script('my-first-plugin-script', 'my_first_plugin_vars', array(
'nonce' => wp_create_nonce('my_first_plugin_nonce')
));
}
add_action('admin_enqueue_scripts', 'my_first_plugin_enqueue_scripts');
33. 创建自定义数据库表
33.1 激活时创建表
在插件激活时创建自定义数据库表:
function my_first_plugin_create_db_table() {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE $table_name (
id mediumint(9) NOT NULL AUTO_INCREMENT,
name tinytext NOT NULL,
value text NOT NULL,
PRIMARY KEY (id)
) $charset_collate;";
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
dbDelta($sql);
}
register_activation_hook(__FILE__, 'my_first_plugin_create_db_table');
33.2 插入数据
在插件中插入数据到自定义表:
function my_first_plugin_insert_data($name, $value) {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$wpdb->insert(
$table_name,
array(
'name' => $name,
'value' => $value,
)
);
}
33.3 查询数据
从自定义表中查询数据:
function my_first_plugin_get_data() {
global $wpdb;
$table_name = $wpdb->prefix . 'my_first_plugin_table';
$results = $wpdb->get_results("SELECT * FROM $table_name", OBJECT);
return $results;
}
34. 本地化
34.1 加载插件的翻译文件
在插件中加载翻译文件:
function my_first_plugin_load_textdomain() {
load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
34.2 创建翻译文件
使用 Poedit 或其他翻译工具创建 .po
和 .mo
文件,并将它们放在 languages
目录中。例如,创建 my-first-plugin-zh_CN.po
和 my-first-plugin-zh_CN.mo
文件。
35. 安全性
35.1 数据验证和清理
在处理用户输入时,确保数据的验证和清理:
$name = sanitize_text_field($_POST['name']);
35.2 非可信数据的输出
在输出非可信数据时,使用适当的 WordPress 函数进行转义:
echo esc_html($name);
35.3 使用非ces
使用 Nonces 来验证请求的合法性:
if (!isset($_POST['my_first_plugin_nonce']) || !wp_verify_nonce($_POST['my_first_plugin_nonce'], 'my_first_plugin_form_nonce')) {
wp_die(__('Nonce verification failed', 'my-first-plugin'));
}
36. 优化性能
36.1 减少数据库查询
缓存频繁使用的数据以减少数据库查询次数:
$data = wp_cache_get('my_first_plugin_data');
if ($data === false) {
$data = my_first_plugin_get_data();
wp_cache_set('my_first_plugin_data', $data);
}
36.2 异步处理
使用异步方式处理耗时操作:
function my_first_plugin_enqueue_async_script($url) {
if (strpos($url, '#async') === false) {
return $url;
} else if (is_admin()) {
return str_replace('#async', '', $url);
} else {
return str_replace('#async', '', $url) . "' async='async";
}
}
add_filter('clean_url', 'my_first_plugin_enqueue_async_script', 11, 1);
37. 与第三方服务集成
37.1 使用 API 密钥
存储和使用 API 密钥:
$api_key = get_option('my_first_plugin_api_key');
$response = wp_remote_get("https://api.example.com/data?api_key={$api_key}");
37.2 处理 API 响应
处理第三方 API 的响应:
if (is_wp_error($response)) {
$error_message = $response->get_error_message();
echo "Something went wrong: $error_message";
} else {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body);
}
38. 版本控制
38.1 使用 Git
使用 Git 进行版本控制是现代开发的最佳实践。以下是一些基本步骤来初始化和使用 Git:
- 初始化 Git 仓库:
git init
- 添加
.gitignore
文件: 创建一个.gitignore
文件,以忽略不需要版本控制的文件和目录。例如:/vendor/ /node_modules/ /wp-content/uploads/ .env
- 添加并提交文件:
git add . git commit -m "Initial commit"
- 推送到远程仓库:
git remote add origin <remote-repository-URL> git push -u origin master
38.2 使用 GitHub Actions 进行 CI/CD
GitHub Actions 可以帮助你自动化测试、构建和部署插件。
- 创建 GitHub Actions 工作流: 在你的仓库中创建
.github/workflows/main.yml
文件:name: CI on: push: branches: - master pull_request: branches: - master jobs: build: runs-on: ubuntu-lateststeps: - name: Checkout code uses: actions/checkout@v2 - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' - name: Install dependencies run: composer install - name: Run tests run: vendor/bin/phpunit</code></pre></li>
39. 钩子和过滤器
39.1 自定义钩子
创建自定义钩子,让其他开发者可以扩展你的插件功能:
do_action('my_first_plugin_custom_action', $arg1, $arg2);
使用自定义钩子:
add_action('my_first_plugin_custom_action', 'my_custom_function', 10, 2);
function my_custom_function($arg1, $arg2) {
// 处理自定义钩子
}
39.2 自定义过滤器
创建自定义过滤器以便于修改数据:
$value = apply_filters('my_first_plugin_custom_filter', $value);
使用自定义过滤器:
add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function');
function my_custom_filter_function($value) {
// 修改并返回数据
return $value . ' modified';
}
40. REST API
40.1 注册自定义 REST API 路由
使用 WordPress REST API 注册自定义路由:
function my_first_plugin_register_rest_route() {
register_rest_route('my-first-plugin/v1', '/data', array(
'methods' => 'GET',
'callback' => 'my_first_plugin_rest_callback',
));
}
add_action('rest_api_init', 'my_first_plugin_register_rest_route');
40.2 处理 REST API 请求
处理 REST API 请求并返回响应:
function my_first_plugin_rest_callback($request) {
$data = array(
'message' => __('Hello, World!', 'my-first-plugin'),
);
return new WP_REST_Response($data, 200);
}
41. Gutenberg 块
41.1 创建自定义 Gutenberg 块
继续创建和注册自定义 Gutenberg 块。
- 创建编辑器脚本(续): 在
src
目录中继续创建index.js
文件的内容:import { registerBlockType } from '@wordpress/blocks'; import { useBlockProps, RichText } from '@wordpress/block-editor'; registerBlockType('my-first-plugin/my-custom-block', { edit({ attributes, setAttributes }) { const blockProps = useBlockProps(); return ( <div {...blockProps}> <RichText tagName="p" onChange={(content) => setAttributes({ content })} value={attributes.content} placeholder="Write your content here..." /> </div> ); }, save({ attributes }) { const blockProps = useBlockProps.save(); return ( <div {...blockProps}> <RichText.Content tagName="p" value={attributes.content} /> </div> ); }, attributes: { content: { type: 'string', source: 'html', selector: 'p' } } });
- 编译脚本: 更新
package.json
以添加构建脚本:{ "scripts": { "build": "wp-scripts build", "start": "wp-scripts start" }, "devDependencies": { "@wordpress/scripts": "^23.0.0" } }
然后运行构建命令:npm run build
- 加载脚本: 在插件的 PHP 文件中加载编译后的 JavaScript 文件:
function my_first_plugin_register_block() { wp_register_script( 'my-first-plugin-editor-script', plugins_url('build/index.js', __FILE__), array('wp-blocks', 'wp-element', 'wp-editor') );register_block_type('my-first-plugin/my-custom-block', array( 'editor_script' => 'my-first-plugin-editor-script', ));} add_action('init', 'my_first_plugin_register_block');
42. 使用 WP-CLI
WP-CLI 是一个强大的命令行工具,可以用于管理 WordPress 安装,包括插件的开发和调试。
42.1 创建自定义 WP-CLI 命令
- 注册自定义命令: 在插件的主文件中注册一个自定义 WP-CLI 命令:
if (defined('WP_CLI') && WP_CLI) { WP_CLI::add_command('my_first_plugin', 'My_First_Plugin_CLI_Command'); } class My_First_Plugin_CLI_Command { public function hello($args, $assoc_args) { WP_CLI::success('Hello, World!'); } }
- 运行命令: 在终端中运行自定义 WP-CLI 命令:
wp my_first_plugin hello
43. 单元测试
43.1 使用 PHPUnit 进行单元测试
- 安装 PHPUnit: 使用 Composer 安装 PHPUnit:
composer require --dev phpunit/phpunit
- 设置测试环境: 创建
phpunit.xml.dist
文件以配置测试环境:<phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My First Plugin Test Suite"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
- 编写测试: 在
tests
目录中创建测试文件,例如test-sample.php
:class SampleTest extends WP_UnitTestCase { public function test_sample() { $this->assertTrue(true); } }
- 运行测试: 在终端中运行 PHPUnit:
vendor/bin/phpunit
44. 代码质量和静态分析
44.1 使用 PHPCS 检查代码规范
- 配置 PHPCS: 创建
phpcs.xml.dist
文件,以定义代码检查规则:<?xml version="1.0"?> <ruleset name="My First Plugin"> <description>WordPress Plugin Coding Standards</description> <rule ref="WordPress-Core"/> <rule ref="WordPress-Docs"/> <rule ref="WordPress-Extra"/> <file>./</file> <exclude-pattern>*/vendor/*</exclude-pattern> </ruleset>
- 运行 PHPCS: 在终端中运行 PHP_CodeSniffer 以检查代码规范:
vendor/bin/phpcs
44.2 使用 PHPStan 进行静态分析
- 安装 PHPStan: 使用 Composer 安装 PHPStan:
composer require --dev phpstan/phpstan
- 配置 PHPStan: 创建
phpstan.neon
文件:includes: - vendor/phpstan/phpstan/conf/bleedingEdge.neon parameters: level: max paths: -
- 运行 PHPStan: 在终端中运行 PHPStan 以进行静态分析:
vendor/bin/phpstan analyse
- 运行 PHPStan: 在终端中运行 PHPStan 以进行静态分析:
45. 安全性
45.1 数据验证和清理
为了防止安全漏洞,必须对所有用户输入进行验证和清理。
- 验证输入: 使用
sanitize_*
系列函数来清理输入数据:$email = sanitize_email($_POST['email']); $url = esc_url($_POST['url']);
- 非ces验证: 使用
wp_verify_nonce
和check_admin_referer
来验证非ces:if (!wp_verify_nonce($_POST['_wpnonce'], 'my_action')) { die('Security check failed'); }
45.2 SQL 注入防护
使用 prepare
方法来防止 SQL 注入:
global $wpdb;
$wpdb->query($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_title =
46. 国际化和本地化
46.1 准备插件进行翻译
- 加载文本域: 在插件主文件中加载文本域:
function my_first_plugin_load_textdomain() { load_plugin_textdomain('my-first-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages'); } add_action('plugins_loaded', 'my_first_plugin_load_textdomain');
- 使用翻译函数: 使用
__
, _e
, _n
等函数进行国际化: $message = __('Hello, World!', 'my-first-plugin'); _e('Welcome to my plugin!', 'my-first-plugin');
- 生成 POT 文件: 使用
wp i18n
工具来生成 POT 文件: npx wp i18n make-pot . languages/my-first-plugin.pot
47. 优化性能
47.1 缓存
- 使用 Transients API: 缓存临时数据,以减少数据库查询:
$data = get_transient('my_first_plugin_data'); if ($data === false) { $data = costly_database_query(); set_transient('my_first_plugin_data', $data, 12 * HOUR_IN_SECONDS); }
- 使用对象缓存: 使用
wp_cache_set
和 wp_cache_get
进行对象缓存: wp_cache_set('my_cache_key', $data); $cached_data = wp_cache_get('my_cache_key');
47.2 优化数据库查询
确保数据库查询高效,避免不必要的查询和数据加载:
global $wpdb;
$results = $wpdb->get_results($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE post_status =
48. 高级插件架构
48.1 使用面向对象编程(OOP)
通过面向对象编程(OOP),可以使插件的代码更加模块化、可维护和可扩展。
- 创建基础类: 创建一个基础类来管理插件的初始化和加载:
class MyFirstPlugin { protected static $instance = null;public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { $this->setup_hooks(); } private function setup_hooks() { add_action('init', array($this, 'init')); } public function init() { // 初始化代码 }} MyFirstPlugin::get_instance();
- 模块化插件功能: 将插件的不同功能模块化,以便更好地管理和扩展:
class MyFirstPlugin_Admin { public function __construct() { add_action('admin_menu', array($this, 'add_admin_menu')); }public function add_admin_menu() { add_menu_page('My First Plugin', 'My First Plugin', 'manage_options', 'my-first-plugin', array($this, 'admin_page')); } public function admin_page() { echo '<h1>My First Plugin Settings</h1>'; }} if (is_admin()) { new MyFirstPlugin_Admin(); }
48.2 使用依赖注入(DI)
依赖注入(DI)是一种设计模式,它可以使类的依赖更显式,并且更容易进行单元测试。
- 创建依赖注入容器: 使用一个简单的 DI 容器来管理类的实例:
class DIContainer { protected $instances = array();public function set($name, $instance) { $this->instances[$name] = $instance; } public function get($name) { return $this->instances[$name]; }} $container = new DIContainer();
- 使用 DI 容器: 注册和使用依赖:
$container->set('admin', new MyFirstPlugin_Admin()); $admin = $container->get('admin');
49. REST API
49.1 创建自定义 REST API 端点
WordPress 提供了一个内置的 REST API,可以用来创建自定义端点。
- 注册自定义端点: 使用
register_rest_route
函数注册一个自定义 REST API 端点: function my_first_plugin_register_api_routes() { register_rest_route('my-first-plugin/v1', '/data', array( 'methods' => 'GET', 'callback' => 'my_first_plugin_get_data', )); } add_action('rest_api_init', 'my_first_plugin_register_api_routes'); function my_first_plugin_get_data($request) { return new WP_REST_Response(array('data' => 'Hello, World!'), 200); }
- 测试端点: 访问
http://your-site/wp-json/my-first-plugin/v1/data
以测试自定义端点。
50. 多站点支持
50.1 多站点激活和停用
```php
foreach ($blog_ids as $blog_id) {
switch_to_blog($blog_id);
my_first_plugin_single_deactivate();
restore_current_blog();
}
} else {
my_first_plugin_single_deactivate();
}
}
register_deactivation_hook(__FILE__, 'my_first_plugin_deactivate');
```
50.2 处理多站点特定功能
- 存储全局设置: 在多站点环境中,可以使用
get_site_option
和 update_site_option
来存储和检索全局设置: function my_first_plugin_get_global_setting($key) { return get_site_option($key); } function my_first_plugin_set_global_setting($key, $value) { update_site_option($key, $value); }
- 跨站点数据同步: 在多站点网络中,可以通过
switch_to_blog
和 restore_current_blog
函数在不同站点之间切换,以同步数据: function my_first_plugin_sync_data_across_sites($data) { global $wpdb; $blog_ids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs"); foreach ($blog_ids as $blog_id) { switch_to_blog($blog_id); update_option('my_first_plugin_data', $data); restore_current_blog(); } }
51. 单元测试
51.1 使用 PHPUnit 进行测试
- 安装 PHPUnit: 使用 Composer 安装 PHPUnit:
composer require --dev phpunit/phpunit
- 配置 PHPUnit: 创建
phpunit.xml.dist
文件: <phpunit bootstrap="tests/bootstrap.php"> <testsuites> <testsuite name="My Plugin Test Suite"> <directory>tests/</directory> </testsuite> </testsuites> </phpunit>
- 编写测试用例: 创建测试用例文件,例如
tests/test-sample.php
: class SampleTest extends WP_UnitTestCase { public function test_sample() { $this->assertTrue(true); } }
- 运行测试: 运行 PHPUnit 以执行测试:
vendor/bin/phpunit
52. 钩子和过滤器
52.1 创建自定义钩子
- 创建自定义动作钩子: 在插件代码中创建一个自定义动作钩子:
do_action('my_first_plugin_custom_action', $arg1, $arg2);
- 创建自定义过滤器钩子: 在插件代码中创建一个自定义过滤器钩子:
$value = apply_filters('my_first_plugin_custom_filter', $value, $arg1, $arg2);
52.2 使用钩子和过滤器
- 添加动作钩子: 使用
add_action
函数来挂载自定义动作钩子: add_action('my_first_plugin_custom_action', 'my_custom_action_function', 10, 2); function my_custom_action_function($arg1, $arg2) { // 自定义动作处理逻辑 }
- 添加过滤器钩子: 使用
add_filter
函数来挂载自定义过滤器钩子: add_filter('my_first_plugin_custom_filter', 'my_custom_filter_function', 10, 3); function my_custom_filter_function($value, $arg1, $arg2) { // 自定义过滤逻辑 return $value; }