阐述 WordPress `add_menu_page()` 函数源码:如何通过 `$wp_menu_pages` 全局数组注册后台顶级菜单。

各位观众老爷,晚上好!我是今天的讲师,咱们今晚就来聊聊WordPress后台菜单这块的“地基” —— add_menu_page() 函数,看看它如何利用 $wp_menu_pages 这个全局数组,在后台撑起一片天。

开场白:菜单的江湖地位

在WordPress后台,菜单就相当于导航,是用户找到各种功能模块的入口。一个清晰合理的菜单结构,能极大提升用户体验,让管理后台变得高效便捷。而add_menu_page()函数,就是我们创造顶级菜单的利器。

核心概念:$wp_menu_pages 全局数组

$wp_menu_pages 是 WordPress 中一个非常重要的全局数组,它负责存储所有顶级菜单的信息。 每一个顶级菜单,都会以数组元素的形式存在于 $wp_menu_pages 中。 WordPress正是通过读取和解析这个数组,来生成最终的后台菜单。

add_menu_page() 函数的庐山真面目

首先,我们来看看 add_menu_page() 函数的定义:

function add_menu_page( string $page_title, string $menu_title, string $capability, string $menu_slug, callable|string $callback = '', string $icon_url = '', int|string|null $position = null ): bool {
    global $menu, $submenu;
    global $wp_menu_pages, $wp_submenu_pages;

    $menu_slug = sanitize_key( $menu_slug );

    $title = wp_strip_all_tags( $menu_title );

    $position = (int) $position;

    // If the position is already taken, find the next free one.
    while ( isset( $wp_menu_pages[ $position ] ) ) {
        $position++;
        if ( $position > 999 ) {
            break;
        }
    }

    if ( empty( $icon_url ) ) {
        $icon_url = 'dashicons-admin-generic';
    }

    $menu_name = $menu_title;

    $menu[ $position ] = array(
        $menu_name,
        $capability,
        $menu_slug,
        $page_title,
        'menu-top ' . sanitize_html_class( $menu_slug ),
        'menu-top',
        $icon_url,
    );

    $wp_menu_pages[ $position ] = true;

    add_submenu_page( $menu_slug, $page_title, $menu_title, $capability, $menu_slug, $callback );

    return true;
}

参数说明:

参数 类型 描述
$page_title string 页面标题,显示在浏览器的标题栏和H1标签里。
$menu_title string 菜单标题,显示在左侧菜单栏中。
$capability string 用户权限,只有具备该权限的用户才能看到这个菜单。常用的权限包括 manage_options(管理员),edit_posts(编辑)等。
$menu_slug string 菜单别名,用于生成URL和在代码中引用。必须是唯一的。
$callback callable|string 回调函数,当用户点击菜单时,执行该函数来渲染页面内容。 可以是一个函数名,也可以是一个匿名函数。如果留空, WordPress 会尝试加载一个与 $menu_slug 同名的 PHP 文件。
$icon_url string 菜单图标,可以是Dashicons的类名(例如 dashicons-admin-generic),也可以是图片URL。
$position int|string|null 菜单位置,数字越小,菜单越靠前。如果留空,WordPress会自动分配一个位置。 为了避免冲突,建议使用大于10的数字。 已经存在的位置,会被自动调整。

代码解读:深入 add_menu_page() 的内部

我们来逐步剖析add_menu_page()函数的源码,看看它是如何操纵$wp_menu_pages的。

  1. 声明全局变量:

    global $menu, $submenu;
    global $wp_menu_pages, $wp_submenu_pages;

    这几行代码声明了函数内部使用的全局变量。 $menu$submenu 最终会用于生成菜单的HTML结构。 $wp_menu_pages$wp_submenu_pages 用于存储菜单和子菜单的数据。

  2. 安全处理:

    $menu_slug = sanitize_key( $menu_slug );
    $title = wp_strip_all_tags( $menu_title );

    这两行代码对$menu_slug$menu_title进行了安全处理,防止XSS攻击。 sanitize_key() 用于清理 $menu_slug,确保它是一个有效的key。 wp_strip_all_tags() 用于移除 $menu_title 中的所有HTML标签。

  3. 位置处理:

    $position = (int) $position;
    
    // If the position is already taken, find the next free one.
    while ( isset( $wp_menu_pages[ $position ] ) ) {
        $position++;
        if ( $position > 999 ) {
            break;
        }
    }

    这部分代码负责处理菜单的位置。 首先,将 $position 转换为整数。 然后,检查 $wp_menu_pages 数组中是否已经存在该位置。 如果存在,则自动递增 $position,直到找到一个空闲的位置。 这样可以避免菜单位置冲突。 如果 $position 大于999,则停止查找。

  4. 设置默认图标:

    if ( empty( $icon_url ) ) {
        $icon_url = 'dashicons-admin-generic';
    }

    如果 $icon_url 为空,则设置默认图标为 dashicons-admin-generic

  5. 构建菜单数组:

    $menu_name = $menu_title;
    
    $menu[ $position ] = array(
        $menu_name,
        $capability,
        $menu_slug,
        $page_title,
        'menu-top ' . sanitize_html_class( $menu_slug ),
        'menu-top',
        $icon_url,
    );

    这部分代码构建了菜单数组,并将其添加到 $menu 全局变量中。 $menu 数组的每一个元素都代表一个顶级菜单,包含了菜单的标题、权限、别名、页面标题、CSS类名、类型和图标等信息。

  6. 更新 $wp_menu_pages

    $wp_menu_pages[ $position ] = true;

    这行代码是关键! 它将 $wp_menu_pages 数组中 $position 对应的元素设置为 true,表示该位置已经被占用。 这也是add_menu_page() 函数与 $wp_menu_pages 数组交互的核心所在。

  7. 添加子菜单:

    add_submenu_page( $menu_slug, $page_title, $menu_title, $capability, $menu_slug, $callback );

    最后,调用 add_submenu_page() 函数添加一个默认的子菜单。 这个子菜单与顶级菜单具有相同的别名、页面标题、菜单标题、权限和回调函数。

实战演练:创建一个自定义菜单

说了这么多理论,不如来点实际的。 我们来创建一个名为 "我的插件" 的顶级菜单,并为其添加一个子菜单。

<?php
/**
 * Plugin Name: 我的插件
 * Description: 一个简单的插件,用于演示如何添加后台菜单
 */

add_action( 'admin_menu', 'my_plugin_menu' );

function my_plugin_menu() {
    add_menu_page(
        '我的插件设置', // 页面标题
        '我的插件',   // 菜单标题
        'manage_options', // 权限
        'my-plugin',    // 菜单别名
        'my_plugin_page', // 回调函数
        'dashicons-admin-plugins', // 图标
        25 // 位置
    );
}

function my_plugin_page() {
    ?>
    <div class="wrap">
        <h1>我的插件设置</h1>
        <p>欢迎使用我的插件!</p>
    </div>
    <?php
}

这段代码做了什么?

  1. 注册 admin_menu 钩子: add_action( 'admin_menu', 'my_plugin_menu' ); 这行代码将 my_plugin_menu 函数绑定到 admin_menu 钩子上。 admin_menu 钩子在 WordPress 初始化后台菜单时触发。
  2. 创建菜单: add_menu_page() 函数用于创建顶级菜单。 我们设置了页面标题、菜单标题、权限、菜单别名、回调函数、图标和位置。
  3. 定义回调函数: my_plugin_page() 函数是回调函数,用于渲染页面内容。 当用户点击菜单时,该函数会被执行。

将这段代码保存为一个PHP文件,例如 my-plugin.php,然后将其上传到 wp-content/plugins/ 目录下。 在 WordPress 后台激活该插件,你就能看到名为 "我的插件" 的顶级菜单了。

深入思考:位置的重要性

$position 参数控制菜单在后台菜单中的排序。 如果你设置了一个已经被占用的位置,WordPress 会自动调整你的菜单位置,以避免冲突。

例如,如果你将 $position 设置为 2,而 2 这个位置已经被 "仪表盘" 菜单占用,那么 WordPress 会将你的菜单放在 "仪表盘" 菜单的后面,即下一个空闲的位置。

关于 $wp_menu_pages 的一些补充说明

  • $wp_menu_pages 是一个稀疏数组,这意味着数组的键不一定是连续的。 例如,你可能看到 $wp_menu_pages 数组中存在键 225,但不存在 324 之间的键。
  • $wp_menu_pages 数组的值通常为 true,表示该位置已经被占用。 但你也可以修改这些值,例如将其设置为菜单别名,以便在代码中更方便地查找菜单。
  • 虽然你可以直接修改 $wp_menu_pages 数组,但不建议这样做。 最好使用 add_menu_page()remove_menu_page() 函数来管理后台菜单。

add_menu_page() 的变体:添加动态菜单

有时候,我们可能需要根据用户的角色或插件的设置,动态地添加或删除菜单。 这时,我们可以在 admin_menu 钩子上注册一个函数,根据条件判断是否调用 add_menu_page() 函数。

add_action( 'admin_menu', 'my_dynamic_menu' );

function my_dynamic_menu() {
    if ( current_user_can( 'manage_options' ) ) {
        add_menu_page(
            '动态菜单',
            '动态菜单',
            'manage_options',
            'dynamic-menu',
            'dynamic_menu_page',
            'dashicons-admin-tools',
            30
        );
    } else {
        // 用户没有权限,不添加菜单
    }
}

function dynamic_menu_page() {
    echo '<div class="wrap"><h1>动态菜单页面</h1></div>';
}

在这个例子中,只有具有 manage_options 权限的用户才能看到 "动态菜单"。

总结:add_menu_page() 的精髓

add_menu_page() 函数通过以下步骤,在 WordPress 后台创建顶级菜单:

  1. 接收菜单参数,包括页面标题、菜单标题、权限、菜单别名、回调函数、图标和位置。
  2. 对菜单参数进行安全处理。
  3. 检查菜单位置是否已经被占用,并自动调整位置。
  4. 构建菜单数组,并将其添加到 $menu 全局变量中。
  5. 更新 $wp_menu_pages 数组,标记该位置已经被占用。
  6. 添加一个默认的子菜单。

理解了 add_menu_page() 函数的工作原理,你就能更灵活地定制 WordPress 后台菜单,为用户提供更好的管理体验。

彩蛋:如何移除菜单?

既然讲了添加菜单,就不得不提一下如何移除菜单。 WordPress 提供了 remove_menu_page() 函数来移除顶级菜单。

remove_action( 'admin_menu', 'my_plugin_menu' ); // 先移除注册的函数,避免冲突

add_action( 'admin_menu', 'remove_my_menu' );

function remove_my_menu() {
    remove_menu_page( 'my-plugin' ); // 传入菜单别名
}

这段代码会移除别名为 my-plugin 的顶级菜单。 注意,你需要传入菜单的别名($menu_slug),而不是菜单标题。remove_action是为了避免再次添加my_plugin_menu这个菜单,造成冲突。

最后的忠告:

定制后台菜单时,一定要考虑用户体验。 避免添加过多或不必要的菜单,保持菜单结构的清晰简洁。 良好的菜单设计,能让你的 WordPress 项目更加专业易用。

好了,今天的讲座就到这里。 希望大家有所收获,谢谢!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注