WP DEVELOP

EP130. “第二个插件:顶级菜单与子菜单”

首页 WordPress 开发课程 插件开发 CH1:PHP 基础 · EP130
约 10 分钟· #EP130#插件开发 CH1:PHP 基础
🔒 登录后可标记已读

开始做本章最后一个 PHP 插件——「敏感词过滤器」。跟第一个插件(挂在 Settings 子菜单下)不同,这次要建一个独立的顶级菜单(带自己的图标、排在侧边栏指定位置),并且下面挂两个子菜单页(Words List、Options)。这一讲先把菜单骨架搭出来(页面内容先用「Hello world」占位),下一讲才开始做自定义图标和真正的表单内容。顺带介绍了一个安全惯例:在插件主文件最前面加一行判断,阻止有人直接用 URL 访问这个 PHP 文件。


涉及文件

  • wp-content/plugins/our-word-filter-plugin/index.php (新建)

代码实现

index.php(完整文件)

<?php

/*
  Plugin Name: Our Word Filter Plugin
  Description: Replaces a list of words.
  Version 1.0
  Author: Brad
  Author URI: https://www.udemy.com/user/bradschiff/
*/

if( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly

class OurWordFilterPlugin {
  function __construct() {
    add_action('admin_menu', array($this, 'ourMenu'));
  }

  function ourMenu() {
    add_menu_page('Words To Filter', 'Word Filter', 'manage_options', 'ourwordfilter', array($this, 'wordFilterPage'), 'dashicons-smiley', 100);
    add_submenu_page('ourwordfilter', 'Words To Filter', 'Words List', 'manage_options', 'ourwordfilter', array($this, 'wordFilterPage'));
    add_submenu_page('ourwordfilter', 'Word Filter Options', 'Options', 'manage_options', 'word-filter-options', array($this, 'optionsSubPage'));
  }

  function wordFilterPage() { ?>
    Hello world.
  <?php }

  function optionsSubPage() { ?>
    Hello world from the options page.
  <?php }

}

$ourWordFilterPlugin = new OurWordFilterPlugin();

关键改动点:

  • if( ! defined( 'ABSPATH' ) ) exit;ABSPATH 是 WordPress 加载时才会定义的一个全局常量,只有通过正常的 WordPress 请求流程执行到这个文件,这个常量才存在。如果有人试图直接用浏览器访问插件文件夹里这个 PHP 文件的 URL(绕开 WordPress 本身),ABSPATH 就不存在,脚本会立刻 exit 终止——这是社区里很常见的一种防御性写法,防止插件文件被直接访问触发意料之外的行为
  • 这个新插件用 index.php 当主文件名(跟第一个插件用「跟文件夹同名」的命名习惯不同),文件名本身其实无所谓,WordPress 只认插件头信息注释,不认文件名
  • add_menu_page($page_title, $menu_title, $capability, $menu_slug, $callback, $icon_url, $position):7 个参数依次是浏览器标签标题、侧边栏显示文字、所需权限、页面 slug、内容输出函数、图标(这一讲先用内建的 dashicons-smiley 占位,下一讲换成自定义 SVG)、菜单在侧边栏纵向排列的位置数值(数字越小越靠上,这里给 100 让它靠后显示)
  • add_submenu_page($parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback):第一个参数指向父级菜单的 slug,把这个子页面「挂」在指定的顶级菜单下面
  • 一个隐藏的默认行为:WordPress 会自动把「点击顶级菜单本身」也当成子菜单列表里的第一项,如果不手动干预,这一项会直接沿用顶级菜单注册时的文字(这里是 Word Filter),跟后面手动加的子菜单项文字重复,看起来很怪
  • 解决办法:手动再注册一次跟父级页面 slug 相同的子菜单项(必须紧跟在 add_menu_page() 后面),只是把显示文字改成想要的样子(Words List)——这样点击顶级菜单文字或点击子菜单里的「Words List」都会跳到同一个页面(因为 slug 和回调函数都相同),只是侧边栏显示文字不再重复。这个技巧在 WordPress 核心自己的「Posts → All Posts」菜单结构里也是同样的实现方式

[截图:wp-admin 侧边栏新出现的 Word Filter 顶级菜单,展开后显示 Words List / Options 两个子菜单项]


Hook / Function 速查

名称类型用途
ABSPATHWP 内建常量只有请求正常经过 WordPress 加载流程时才会被定义,常用于阻止文件被直接访问
add_menu_page(...)WP 内建 function在后台侧边栏新增一个顶级菜单
add_submenu_page(...)WP 内建 function在指定顶级菜单下新增一个子菜单页

常见坑

  • 只调用一次 add_menu_page(),不额外处理第一个子菜单项——会导致侧边栏子菜单的第一项文字跟顶级菜单文字重复,看起来像是同一个链接出现了两次(虽然点击效果一致,但视觉上显得突兀)
  • 手动补的「重复子菜单项」注册顺序放错位置——必须紧跟在 add_menu_page() 之后,其余真正的子菜单页才依次排在后面

延伸 / 后续讲座会用到

下一讲会换成自定义 SVG 图标,并开始给 Words List 页面写真正的表单(不使用 Settings API 自动生成,完全手写 HTML + 自己处理提交逻辑)。


Sources

Udemy:

  • Become a WordPress Developer: Unlocking Power With Code — Section 23, EP130