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 '

', 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( '

 

%s

%s

', 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多版本可选,是本地开发完成后快速部署验证的理想平台。

THE END