插件开发入门
插件开发概述
什么是 WordPress 插件?
插件是扩展 WordPress 核心功能的 PHP 代码包。与主题不同,插件可以在主题切换时保持功能独立。
插件 vs 主题功能
php
<?php
// functions.php - 主题功能(主题激活时有效)
function theme_feature() {
// 只能跟随主题
}
// 插件中 - 插件功能(插件启用时有效)
function plugin_feature() {
// 独立于主题
}创建第一个插件
目录结构
wp-content/plugins/my-first-plugin/
├── my-first-plugin.php # 主文件
├── uninstall.php # 卸载清理
├── readme.txt # WordPress.org 说明
├── languages/ # 翻译文件
├── includes/ # 核心功能
│ └── class-plugin.php
├── admin/ # 后台文件
│ ├── css/
│ ├── js/
│ └── partials/
├── public/ # 前台文件
│ ├── css/
│ └── js/
└── assets/ # 静态资源
└── images/主文件结构
php
<?php
/**
* Plugin Name: 我的第一个插件
* Plugin URI: https://example.com/my-first-plugin
* Description: 插件的简短描述
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: 开发者名称
* Author URI: https://example.com
* License: GPL v2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: my-first-plugin
* Domain Path: /languages
*/
// 防止直接访问
if (!defined('ABSPATH')) {
exit;
}
// 定义常量
define('MFP_PLUGIN_VERSION', '1.0.0');
define('MFP_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('MFP_PLUGIN_URL', plugin_dir_url(__FILE__));
define('MFP_PLUGIN_BASENAME', plugin_basename(__FILE__));
// 加载类
require_once MFP_PLUGIN_DIR . 'includes/class-plugin.php';
// 初始化插件
function mfp_init() {
$plugin = new MyFirstPlugin();
$plugin->init();
}
add_action('plugins_loaded', 'mfp_init');
// 激活钩子
register_activation_hook(__FILE__, 'mfp_activate');
/**
* 插件激活时执行
*/
function mfp_activate() {
// 创建选项
add_option('mfp_version', MFP_PLUGIN_VERSION);
// 创建数据库表(如果需要)
mfp_create_tables();
// 刷新永久链接
flush_rewrite_rules();
}
/**
* 停用钩子
*/
function mfp_deactivate() {
// 清理工作
flush_rewrite_rules();
}
register_deactivation_hook(__FILE__, 'mfp_deactivate');插件类结构
php
<?php
/**
* 插件主类
*/
class MyFirstPlugin {
/**
* 初始化插件
*/
public function init() {
// 加载文本域
$this->load_textdomain();
// 注册钩子
$this->init_hooks();
// 加载脚本和样式
$this->enqueue_assets();
}
/**
* 加载翻译文件
*/
public function load_textdomain() {
load_plugin_textdomain(
'my-first-plugin',
false,
dirname(plugin_basename(__FILE__)) . '/languages/'
);
}
/**
* 初始化钩子
*/
public function init_hooks() {
// 添加动作钩子
add_action('init', array($this, 'register_post_type'));
add_action('add_meta_boxes', array($this, 'add_meta_box'));
add_action('save_post', array($this, 'save_meta_box'));
// 添加过滤器钩子
add_filter('the_content', array($this, 'filter_content'));
add_filter('excerpt_length', array($this, 'custom_excerpt_length'));
// AJAX 钩子
add_action('wp_ajax_mfp_save_data', array($this, 'ajax_save_data'));
add_action('wp_ajax_nopriv_mfp_save_data', array($this, 'ajax_save_data'));
}
/**
* 加载资源
*/
public function enqueue_assets() {
// 后台样式和脚本
if (is_admin()) {
wp_enqueue_style(
'mfp-admin',
MFP_PLUGIN_URL . 'admin/css/admin.css',
array(),
MFP_PLUGIN_VERSION
);
wp_enqueue_script(
'mfp-admin',
MFP_PLUGIN_URL . 'admin/js/admin.js',
array('jquery'),
MFP_PLUGIN_VERSION,
true
);
}
// 前台样式和脚本
wp_enqueue_style(
'mfp-frontend',
MFP_PLUGIN_URL . 'public/css/frontend.css',
array(),
MFP_PLUGIN_VERSION
);
}
// ... 更多方法
}安全的插件开发
输入验证与输出转义
php
<?php
/**
* 输入验证
*/
// 验证用户权限
if (!current_user_can('manage_options')) {
wp_die('权限不足');
}
// 验证 Nonce
if (!isset($_POST['mfp_nonce']) ||
!wp_verify_nonce($_POST['mfp_nonce'], 'mfp_save_action')) {
wp_die('安全验证失败');
}
// 验证数据类型
$title = isset($_POST['title']) ? sanitize_text_field($_POST['title']) : '';
$email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
$url = isset($_POST['url']) ? esc_url_raw($_POST['url']) : '';
$content = isset($_POST['content']) ? wp_kses_post($_POST['content']) : '';
$number = isset($_POST['number']) ? absint($_POST['number']) : 0;
$float = isset($_POST['float']) ? (float) $_POST['float'] : 0.0;
/**
* 输出转义
*/
// 文本转义
echo esc_html($text);
echo esc_html_e('Text', 'plugin-text-domain'); // 带翻译
echo esc_html__($text, 'plugin-text-domain');
// 属性转义
echo esc_attr($class);
echo esc_url($url);
echo esc_url($link, array('http', 'https'));
// HTML 内容转义
echo wp_kses_post($html);
echo wp_kses($html, $allowed_tags);
// JavaScript 转义
echo esc_js($string);
echo esc_html($string); // 用于 HTML 中的 JS
// 文本域转义
echo esc_textarea($text);完整的安全处理示例
php
<?php
/**
* 保存表单数据
*/
public function save_meta_box($post_id) {
// 1. 检查自动保存
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
return $post_id;
}
// 2. 检查用户权限
if (!current_user_can('edit_post', $post_id)) {
return $post_id;
}
// 3. 验证 Nonce
if (!isset($_POST['mfp_meta_nonce']) ||
!wp_verify_nonce($_POST['mfp_meta_nonce'], 'mfp_save_meta')) {
return $post_id;
}
// 4. 检查文章类型
if ($_POST['post_type'] !== 'portfolio') {
return $post_id;
}
// 5. 保存数据
if (isset($_POST['mfp_client'])) {
update_post_meta(
$post_id,
'mfp_client',
sanitize_text_field($_POST['mfp_client'])
);
}
if (isset($_POST['mfp_url'])) {
update_post_meta(
$post_id,
'mfp_url',
esc_url_raw($_POST['mfp_url'])
);
}
}Meta Box 开发
添加 Meta Box
php
<?php
/**
* 添加 Meta Box
*/
public function add_meta_boxes() {
add_meta_box(
'mfp_portfolio_details', // ID
'项目详情', // 标题
array($this, 'render_meta_box'), // 回调
'portfolio', // 文章类型
'normal', // 位置 (normal, side, advanced)
'high' // 优先级
);
}
/**
* 渲染 Meta Box
*/
public function render_meta_box($post) {
// 获取现有值
$client = get_post_meta($post->ID, 'mfp_client', true);
$url = get_post_meta($post->ID, 'mfp_url', true);
$featured = get_post_meta($post->ID, 'mfp_featured', true);
// Nonce 字段
wp_nonce_field('mfp_save_meta', 'mfp_meta_nonce');
?>
<div class="mfp-meta-box">
<p>
<label for="mfp_client">客户名称:</label>
<input type="text"
id="mfp_client"
name="mfp_client"
value="<?php echo esc_attr($client); ?>"
class="widefat">
</p>
<p>
<label for="mfp_url">项目链接:</label>
<input type="url"
id="mfp_url"
name="mfp_url"
value="<?php echo esc_url($url); ?>"
class="widefat">
</p>
<p>
<label for="mfp_featured">
<input type="checkbox"
id="mfp_featured"
name="mfp_featured"
value="1"
<?php checked($featured, '1'); ?>>
精选项目
</label>
</p>
</div>
<style>
.mfp-meta-box p { margin: 1em 0; }
.mfp-meta-box label { display: block; font-weight: bold; margin-bottom: 5px; }
.mfp-meta-box input[type="text"],
.mfp-meta-box input[type="url"] { width: 100%; }
</style>
<?php
}短代码开发
创建短代码
php
<?php
/**
* 注册短代码
*/
public function register_shortcodes() {
add_shortcode('mfp_button', array($this, 'render_button_shortcode'));
add_shortcode('mfp_portfolio_grid', array($this, 'render_portfolio_grid'));
}
/**
* 按钮短代码
* 使用方法: [mfp_button url="https://..." text="点击我" style="primary"]
*/
public function render_button_shortcode($atts, $content = null) {
$atts = shortcode_atts(array(
'url' => '#',
'text' => '按钮',
'style' => 'primary',
'target' => '_self',
), $atts, 'mfp_button');
$classes = 'mfp-button mfp-button-' . esc_attr($atts['style']);
$target = $atts['target'] === '_blank' ? ' target="_blank" rel="noopener"' : '';
return sprintf(
'<a href="%s" class="%s"%s>%s</a>',
esc_url($atts['url']),
esc_attr($classes),
$target,
esc_html($atts['text'])
);
}
/**
* 作品集网格短代码
* 使用方法: [mfp_portfolio_grid count="6" category="web-design"]
*/
public function render_portfolio_grid($atts) {
$atts = shortcode_atts(array(
'count' => 6,
'category' => '',
'columns' => 3,
), $atts, 'mfp_portfolio_grid');
$args = array(
'post_type' => 'portfolio',
'posts_per_page' => absint($atts['count']),
);
if (!empty($atts['category'])) {
$args['tax_query'] = array(
array(
'taxonomy' => 'portfolio_category',
'field' => 'slug',
'terms' => $atts['category'],
),
);
}
$query = new WP_Query($args);
if (!$query->have_posts()) {
return '<p>暂无作品</p>';
}
ob_start();
?>
<div class="mfp-portfolio-grid mfp-cols-<?php echo esc_attr($atts['columns']); ?>">
<?php while ($query->have_posts()): $query->the_post(); ?>
<article class="mfp-portfolio-item">
<?php if (has_post_thumbnail()): ?>
<div class="item-image">
<a href="<?php the_permalink(); ?>">
<?php the_post_thumbnail('medium'); ?>
</a>
</div>
<?php endif; ?>
<h3><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
</article>
<?php endwhile; ?>
</div>
<?php
wp_reset_postdata();
return ob_get_clean();
}Widget 开发
php
<?php
/**
* 创建 Widget
*/
class MFP_Recent_Posts_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'mfp_recent_posts',
'最新作品',
array('description' => '显示最新的作品集文章')
);
}
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'];
}
$query = new WP_Query(array(
'post_type' => 'portfolio',
'posts_per_page' => !empty($instance['count']) ? $instance['count'] : 5,
));
if ($query->have_posts()): ?>
<ul class="mfp-recent-posts">
<?php while ($query->have_posts()): $query->the_post(); ?>
<li>
<a href="<?php the_permalink(); ?>">
<?php if (has_post_thumbnail()): ?>
<?php the_post_thumbnail('thumbnail'); ?>
<?php endif; ?>
<span><?php the_title(); ?></span>
</a>
</li>
<?php endwhile; ?>
</ul>
<?php endif;
echo $args['after_widget'];
}
public function form($instance) {
$title = !empty($instance['title']) ? $instance['title'] : '最新作品';
$count = !empty($instance['count']) ? $instance['count'] : 5;
?>
<p>
<label for="<?php echo $this->get_field_id('title'); ?>">标题:</label>
<input type="text"
id="<?php echo $this->get_field_id('title'); ?>"
name="<?php echo $this->get_field_name('title'); ?>"
value="<?php echo esc_attr($title); ?>"
class="widefat">
</p>
<p>
<label for="<?php echo $this->get_field_id('count'); ?>">显示数量:</label>
<input type="number"
id="<?php echo $this->get_field_id('count'); ?>"
name="<?php echo $this->get_field_name('count'); ?>"
value="<?php echo esc_attr($count); ?>"
class="widefat">
</p>
<?php
}
public function update($new_instance, $old_instance) {
$instance = array();
$instance['title'] = !empty($new_instance['title']) ? sanitize_text_field($new_instance['title']) : '';
$instance['count'] = !empty($new_instance['count']) ? absint($new_instance['count']) : 5;
return $instance;
}
}
// 注册 Widget
function mfp_register_widgets() {
register_widget('MFP_Recent_Posts_Widget');
}
add_action('widgets_init', 'mfp_register_widgets');卸载处理
uninstall.php
php
<?php
/**
* 卸载清理
*/
// 如果不是从 WordPress 调用,则退出
if (!defined('WP_UNINSTALL_PLUGIN')) {
exit;
}
// 删除插件选项
delete_option('mfp_version');
delete_option('mfp_settings');
// 删除所有文章 meta
$posts = get_posts(array(
'post_type' => 'portfolio',
'posts_per_page' => -1,
'fields' => 'ids',
));
foreach ($posts as $post_id) {
delete_post_meta($post_id, 'mfp_client');
delete_post_meta($post_id, 'mfp_url');
delete_post_meta($post_id, 'mfp_featured');
}
// 删除自定义数据库表(如果有)
global $wpdb;
$wpdb->query("DROP TABLE IF EXISTS {$wpdb->prefix}mfp_logs");提交到 WordPress.org
readme.txt 格式
txt
=== 插件名称 ===
Contributors: username
Tags: tag1, tag2
Requires at least: 6.0
Tested up to: 6.4
Stable tag: 1.0.0
Requires PHP: 7.4
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
简短描述(最多 150 字符)
== Description ==
详细描述...
== Installation ==
1. 上传插件到 `/wp-content/plugins/` 目录
2. 通过 WordPress 的"插件"菜单激活
3. 在设置页面配置
== Frequently Asked Questions ==
= 问题 1? =
答案 1。
== Screenshots ==
1. 描述1
2. 描述2
== Changelog ==
= 1.0.0 =
* 首次发布
== Upgrade Notice ==
= 1.0.0 =
首次发布