EP130. “第二个插件:顶级菜单与子菜单”
🔒 登录后可标记已读开始做本章最后一个 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 速查
| 名称 | 类型 | 用途 |
|---|---|---|
ABSPATH | WP 内建常量 | 只有请求正常经过 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