WordPress 后台菜单可以按角色工作流精简,但“看不见菜单”不等于“没有权限”。正确顺序是先定义 capability 和服务端授权,再按同一 capability 调整菜单展示。
添加顶级菜单
<?php
add_action(
'admin_menu',
function (): void {
add_menu_page(
'站点运营设置',
'运营设置',
'manage_site_content',
'site-operations',
'site_render_operations_page',
'dashicons-admin-tools',
58
);
}
);
function site_render_operations_page(): void {
if (
! current_user_can( 'manage_site_content' )
) {
wp_die(
esc_html__( '你无权访问此页面。', 'site' )
);
}
echo '<div class="wrap">';
echo '<h1>' .
esc_html__( '站点运营设置', 'site' ) .
'</h1>';
echo '</div>';
}
administrator 是角色名,不是适合传给 add_menu_page() 的权限能力。自定义 capability 应在插件激活、角色迁移或明确的权限管理流程中授予,不能在每次请求随意改角色。
处理表单时还要检查 capability、nonce、字段清洗,并在输出时转义。菜单回调中的检查是纵深防御,不应省略。
添加子菜单
<?php
add_action(
'admin_menu',
function (): void {
add_submenu_page(
'site-operations',
'内容同步',
'内容同步',
'manage_site_content',
'site-content-sync',
'site_render_sync_page'
);
}
);
父 slug 必须与顶级菜单一致。若要把自定义入口放到已有菜单下,应使用其真实 slug,而不是后台显示文案。
移除菜单
<?php
add_action(
'admin_menu',
function (): void {
if (
current_user_can( 'manage_options' )
) {
return;
}
remove_menu_page( 'tools.php' );
remove_menu_page(
'edit.php?post_type=acf-field-group'
);
remove_submenu_page(
'themes.php',
'theme-editor.php'
);
},
999
);
较晚的优先级让第三方插件先注册菜单,再执行移除。使用菜单 slug,不要按翻译后的中文/英文标题遍历全局 $menu 数组;显示文案会随语言和插件版本变化。
如果只想提供“菜单”的快捷入口
把 WordPress 自带的导航管理页包装成新的顶级菜单并不会改变它的权限,也可能造成重复导航。更清楚的做法是保留“外观 → 菜单”,或在自定义运营页中提供一个经过 capability 检查的链接:
<a href="<?php echo esc_url(
admin_url( 'nav-menus.php' )
); ?>">
<?php esc_html_e( '管理导航菜单', 'site' ); ?>
</a>
安全检查
用户即使不知道菜单 URL,仍可能直接访问 wp-admin/tools.php 或调用对应接口。隐藏菜单只改善信息架构。要限制操作,必须检查:
- 页面 callback 的
current_user_can()。 - REST 路由的
permission_callback。 - AJAX/表单的 capability 和 nonce。
- 自定义文章类型的 capability 映射。
- 对象级权限,例如是否能编辑某一篇文章。