WordPress插件开发入门:从零构建自定义功能插件的完整教程
当现有插件无法满足需求时,自己开发WordPress插件是最灵活的解决方案。插件让你在不修改主题和WordPress核心文件的前提下,安全地添加自定义功能。本文带你从零构建一个完整的功能插件。
一、插件基础结构
wp-content/plugins/my-custom-plugin/
├── my-custom-plugin.php # 插件主文件(必须)
├── includes/
│ ├── class-post-views.php # 功能类文件
│ ├── class-shortcodes.php
│ └── class-rest-api.php
├── admin/
│ ├── class-admin.php # 后台管理页面
│ └── css/admin.css
├── public/
│ ├── css/public.css # 前台样式
│ └── js/public.js
├── languages/ # 翻译文件
│ └── my-plugin-zh_CN.po
└── uninstall.php # 卸载时的清理逻辑二、插件主文件头部声明
<?php /** * Plugin Name: My Custom Plugin * Plugin URI: https://yourdomain.com/my-plugin * Description: 自定义功能插件示例,包含文章浏览量统计、自定义短代码和REST API扩展 * Version: 1.0.0 * Requires at least: 6.0 * Requires PHP: 8.0 * Author: Your Name * Author URI: https://yourdomain.com * License: GPL v2 or later * License URI: https://www.gnu.org/licenses/gpl-2.0.html * Text Domain: my-custom-plugin * Domain Path: /languages */ // 直接访问保护(防止文件被直接请求) if ( ! defined( 'ABSPATH' ) ) { exit; // Exit if accessed directly } // 插件版本常量 define( 'MCP_VERSION', '1.0.0' ); define( 'MCP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); define( 'MCP_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); // 自动加载类文件 spl_autoload_register( function( $class ) { $prefix = 'MCP_'; if ( strpos( $class, $prefix ) !== 0 ) return; $file = MCP_PLUGIN_DIR . 'includes/class-' . strtolower( str_replace( ['MCP_', '_'], ['', '-'], $class ) ) . '.php'; if ( file_exists( $file ) ) require $file; }); // 初始化插件 add_action( 'plugins_loaded', function() { // 加载翻译文件 load_plugin_textdomain( 'my-custom-plugin', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); // 初始化各功能模块 new MCP_Post_Views(); new MCP_Shortcodes(); new MCP_Rest_API(); if ( is_admin() ) { new MCP_Admin(); } }); // 激活钩子:插件激活时创建数据库表 register_activation_hook( __FILE__, 'mcp_activate' ); function mcp_activate() { global $wpdb; $table_name = $wpdb->prefix . 'post_views';
$charset_collate = $wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) NOT NULL AUTO_INCREMENT,
post_id bigint(20) NOT NULL,
view_count bigint(20) NOT NULL DEFAULT 0,
last_viewed datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY post_id (post_id)
) $charset_collate;";
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );
// 保存插件版本
add_option( 'mcp_version', MCP_VERSION );
}
// 停用钩子
register_deactivation_hook( __FILE__, 'mcp_deactivate' );
function mcp_deactivate() {
// 清除计划任务
wp_clear_scheduled_hook( 'mcp_daily_cleanup' );
}
三、功能类1:文章浏览量统计
<?php // includes/class-post-views.php class MCP_Post_Views { public function __construct() { // 记录浏览量 add_action( 'wp_head', [ $this, 'track_view' ] ); // 在文章标题后显示浏览量 add_filter( 'the_title', [ $this, 'append_view_count' ], 10, 2 ); // 注册排序参数(让文章可以按浏览量排序) add_filter( 'posts_clauses', [ $this, 'sort_by_views' ], 10, 2 ); } public function track_view() { if ( ! is_singular( 'post' ) ) return; global $wpdb, $post; $table = $wpdb->prefix . 'post_views';
// 使用INSERT ... ON DUPLICATE KEY UPDATE原子操作
$wpdb->query( $wpdb->prepare(
"INSERT INTO $table (post_id, view_count)
VALUES (%d, 1)
ON DUPLICATE KEY UPDATE view_count = view_count + 1",
$post->ID
) );
}
public function get_views( int $post_id ): int {
global $wpdb;
$table = $wpdb->prefix . 'post_views';
$count = $wpdb->get_var( $wpdb->prepare(
"SELECT view_count FROM $table WHERE post_id = %d",
$post_id
) );
return (int) $count;
}
public function append_view_count( string $title, int $post_id ): string {
// 只在文章列表页的文章标题后追加(避免影响SEO标题)
if ( ! in_the_loop() || ! is_archive() ) return $title;
$views = $this->get_views( $post_id );
return $title . sprintf(
' %s 次浏览',
number_format( $views )
);
}
public function sort_by_views( array $clauses, \WP_Query $query ): array {
if ( ! $query->get( 'orderby_views' ) ) return $clauses;
global $wpdb;
$table = $wpdb->prefix . 'post_views';
$clauses['join'] .= " LEFT JOIN $table pv ON pv.post_id = {$wpdb->posts}.ID";
$clauses['orderby'] = "pv.view_count DESC";
return $clauses;
}
}
四、功能类2:自定义短代码
<?php // includes/class-shortcodes.php class MCP_Shortcodes { public function __construct() { add_shortcode( 'popular_posts', [ $this, 'popular_posts_shortcode' ] ); add_shortcode( 'contact_box', [ $this, 'contact_box_shortcode' ] ); add_shortcode( 'server_specs', [ $this, 'server_specs_shortcode' ] ); } // 热门文章列表:[popular_posts count="5" cat="服务器"] public function popular_posts_shortcode( array $atts ): string { $atts = shortcode_atts( [ 'count' => 5,
'cat' => '',
'show_img' => 'yes',
], $atts, 'popular_posts' );
$args = [
'posts_per_page' => absint( $atts['count'] ),
'orderby_views' => true,
'post_status' => 'publish',
];
if ( ! empty( $atts['cat'] ) ) {
$args['category_name'] = sanitize_text_field( $atts['cat'] );
}
$query = new \WP_Query( $args );
if ( ! $query->have_posts() ) {
return '暂无内容
'; } ob_start(); echo '
- '; while ( $query->have_posts() ) { $query->the_post(); $views = ( new self() ); // 注意:实际应注入依赖而非新建实例,这里简化演示 printf( '
- %s
', esc_url( get_permalink() ), esc_html( get_the_title() ) ); } echo '
'; wp_reset_postdata(); return ob_get_clean(); } // 联系框:[contact_box email="contact@yourdomain.com" title="联系我们"] public function contact_box_shortcode( array $atts ): string { $atts = shortcode_atts( [ 'email' => get_option( 'admin_email' ), 'title' => '联系我们', 'color' => '#0066cc', ], $atts, 'contact_box' ); return sprintf( '
', esc_attr( $atts['color'] ), esc_html( $atts['title'] ), esc_attr( $atts['email'] ), esc_html( $atts['email'] ) ); } // 服务器规格表格:[server_specs cpu="4核" ram="8G" disk="100G SSD"] public function server_specs_shortcode( array $atts ): string { $atts = shortcode_atts( [ 'cpu' => '', 'ram' => '', 'disk' => '', 'bw' => '', ], $atts, 'server_specs' ); $rows = ''; if ( $atts['cpu'] ) $rows .= "
CPU{$atts['cpu']}
"; if ( $atts['ram'] ) $rows .= "
内存{$atts['ram']}
"; if ( $atts['disk'] ) $rows .= "
硬盘{$atts['disk']}
"; if ( $atts['bw'] ) $rows .= "
带宽{$atts['bw']}
"; return "
{$rows}
"; } }
五、功能类3:REST API扩展
<?php
// includes/class-rest-api.php
class MCP_Rest_API {
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_routes' ] );
}
public function register_routes() {
// 获取文章浏览量:GET /wp-json/mcp/v1/views/{post_id}
register_rest_route( 'mcp/v1', '/views/(?P\d+)', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_post_views' ],
'permission_callback' => '__return_true', // 公开接口
'args' => [
'id' => [
'required' => true,
'validate_callback' => fn($v) => is_numeric($v),
'sanitize_callback' => 'absint',
],
],
]);
// 批量获取热门文章:GET /wp-json/mcp/v1/popular?count=10
register_rest_route( 'mcp/v1', '/popular', [
'methods' => \WP_REST_Server::READABLE,
'callback' => [ $this, 'get_popular_posts' ],
'permission_callback' => '__return_true',
]);
}
public function get_post_views( \WP_REST_Request $request ): \WP_REST_Response {
$post_id = $request->get_param( 'id' );
$post = get_post( $post_id );
if ( ! $post ) {
return new \WP_REST_Response( [ 'error' => '文章不存在' ], 404 );
}
global $wpdb;
$table = $wpdb->prefix . 'post_views';
$views = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT view_count FROM $table WHERE post_id = %d", $post_id
) );
return new \WP_REST_Response( [
'post_id' => $post_id,
'post_title' => $post->post_title,
'view_count' => $views,
], 200 );
}
public function get_popular_posts( \WP_REST_Request $request ): \WP_REST_Response {
$count = absint( $request->get_param( 'count' ) ) ?: 10;
$count = min( $count, 50 ); // 最多返回50条
$posts = new \WP_Query([
'posts_per_page' => $count,
'orderby_views' => true,
'post_status' => 'publish',
'fields' => 'ids',
]);
$data = array_map( function( $id ) {
global $wpdb;
$views = (int) $wpdb->get_var( $wpdb->prepare(
"SELECT view_count FROM {$wpdb->prefix}post_views WHERE post_id = %d", $id
) );
return [
'id' => $id,
'title' => get_the_title( $id ),
'url' => get_permalink( $id ),
'view_count' => $views,
];
}, $posts->posts );
return new \WP_REST_Response( $data, 200 );
}
}
六、插件安全开发规范
<code"><?php
// 安全开发四原则示例
// 1. 数据验证(Validate):检查数据类型和格式
$post_id = absint( $_GET['post_id'] ?? 0 );
if ( $post_id <= 0 ) { wp_die( '无效的文章ID' ); } // 2. 数据清理(Sanitize):清除危险字符 $title = sanitize_text_field( $_POST['title'] ?? '' ); $content = wp_kses_post( $_POST['content'] ?? '' ); // 允许安全的HTML $email = sanitize_email( $_POST['email'] ?? '' ); $url = esc_url_raw( $_POST['url'] ?? '' ); // 3. 权限验证(Capability Check):确认用户有权执行操作 if ( ! current_user_can( 'edit_post', $post_id ) ) { wp_die( '您没有权限执行此操作' ); } // 4. Nonce验证(CSRF保护) // 输出nonce wp_nonce_field( 'mcp_save_settings', 'mcp_nonce' ); // 验证nonce if ( ! wp_verify_nonce( $_POST['mcp_nonce'] ?? '', 'mcp_save_settings' ) ) { wp_die( '安全验证失败,请刷新页面重试' ); } // 5. 数据库操作使用prepare()防SQL注入 global $wpdb; $results = $wpdb->get_results( $wpdb->prepare(
"SELECT * FROM {$wpdb->posts} WHERE ID = %d AND post_status = %s",
$post_id,
'publish'
) );
// 6. 输出转义(Escape):防XSS
echo esc_html( $title ); // 纯文本输出
echo esc_attr( $css_class ); // HTML属性值
echo esc_url( $link ); // URL
echo wp_kses_post( $content ); // 包含安全HTML的内容七、总结
WordPress插件开发遵循"不修改核心文件、通过钩子扩展功能、严格遵守安全规范"三条原则。本文实现的浏览量统计、短代码和REST API是最常见的三类插件功能需求。开发完成后,将插件上传到服务器的 wp-content/plugins/ 目录并启用即可。IDC.Net的香港VPS完整支持WordPress开发环境,PHP 8.1~8.3多版本可选,是本地开发完成后快速部署验证的理想平台。
