好嘞,各位观众老爷们,欢迎来到“扒光 WordPress 后台小秘密”系列讲座。今天咱们要聊的是一个非常关键的函数:get_current_screen()
,看看它到底是怎么摸清我们现在身处 WordPress 后台的哪个角落。
一、开场白:get_current_screen()
的重要性
在 WordPress 后台开发中,我们经常需要知道当前页面是文章编辑页、用户管理页、还是小工具设置页等等。get_current_screen()
函数就像一个老侦探,负责收集线索、分析证据,最终确定“案发现场”。
为什么要知道当前页面?原因很多:
- 加载特定页面的 CSS/JavaScript: 只有在特定页面才需要加载的脚本和样式,避免全局加载造成性能浪费。
- 修改特定页面的行为: 根据当前页面,调整表单验证规则、添加自定义按钮等等。
- 权限控制: 判断用户是否有权限访问当前页面,进行相应的权限检查。
- 追踪用户行为: 记录用户在特定页面的操作,用于分析和优化。
总之,get_current_screen()
是后台开发的基石,理解它的工作原理至关重要。
二、源码剖析:步步为营,抽丝剥茧
让我们深入 wp-includes/screen.php
文件,看看 get_current_screen()
的真面目。 为了方便讲解,我简化了一些代码,保留了核心逻辑。
<?php
/**
* Retrieve the current screen object.
*
* @global WP_Screen $current_screen
*
* @return WP_Screen|null WP_Screen object. Null if not set.
*/
function get_current_screen() {
global $current_screen;
if ( ! is_a( $current_screen, 'WP_Screen' ) ) {
set_current_screen();
}
return $current_screen;
}
/**
* Sets the current screen object.
*
* @global WP_Screen $current_screen
* @global string $pagenow
* @global WP_Admin_Bar $wp_admin_bar
*
* @param WP_Screen|string|null $hook_name Optional. A hook name.
*/
function set_current_screen( $hook_name = '' ) {
global $current_screen, $pagenow, $wp_admin_bar;
if ( ! is_a( $current_screen, 'WP_Screen' ) ) {
$current_screen = new WP_Screen();
}
// Populate admin menu variables.
require_once ABSPATH . 'wp-admin/includes/admin.php';
// Get the hook name.
if ( empty( $hook_name ) ) {
$hook_name = get_current_screen_hook();
}
$current_screen->set_hookname( $hook_name );
$current_screen->populate_network_path();
$current_screen->populate_base();
$current_screen->populate_id();
$current_screen->populate_key();
$current_screen->populate_taxonomy();
$current_screen->populate_post_type();
// Add screen help tabs and help sidebar.
do_action( 'load-' . $current_screen->id );
do_action( 'current_screen', $current_screen );
if ( is_admin_bar_showing() ) {
require_once ABSPATH . 'wp-admin/includes/admin-bar.php';
wp_admin_bar_init();
}
}
/**
* Get the current screen hook.
*
* @global string $pagenow
* @global string $typenow
* @global string $taxnow
*
* @return string|null The hook name of the current screen, or null if not set.
*/
function get_current_screen_hook() {
global $pagenow, $typenow, $taxnow;
$hook_name = false;
if ( isset( $_GET['import'] ) ) {
$hook_name = 'import';
} elseif ( 'plugins.php' == $pagenow ) {
$plugin = isset( $_REQUEST['plugin'] ) ? trim( sanitize_text_field( wp_unslash( $_REQUEST['plugin'] ) ) ) : '';
if ( $plugin ) {
$hook_name = 'plugin-install';
} else {
$hook_name = 'plugins';
}
} elseif ( 'themes.php' == $pagenow ) {
$hook_name = 'themes';
} elseif ( $typenow ) {
$hook_name = get_plugin_page_hookname( 'edit.php?post_type=' . $typenow, '' );
} elseif ( $taxnow ) {
$hook_name = get_plugin_page_hookname( 'edit-tags.php?taxonomy=' . $taxnow, '' );
} elseif ( isset( $_GET['page'] ) ) {
$hook_name = get_plugin_page_hookname( $_GET['page'], '' );
} elseif ( in_array( $pagenow, array( 'post.php', 'post-new.php' ) ) ) {
$hook_name = 'post';
} elseif ( in_array( $pagenow, array( 'media-upload.php', 'async-upload.php' ) ) ) {
$hook_name = 'media-upload';
} elseif ( in_array( $pagenow, array( 'index.php', 'wp-activate.php' ) ) ) {
$hook_name = 'dashboard';
} else {
$hook_name = $pagenow;
}
return $hook_name;
}
class WP_Screen {
public $id = '';
public $taxonomy = '';
public $post_type = '';
public $base = '';
public $action = '';
public $parent_base = '';
public $parent_file = '';
public $screen_icon = '';
public $help_tabs = array();
public $help_sidebar = '';
public $is_network = false;
public $is_user = false;
public $in_admin = true;
private $key = null;
private $hook_suffix;
public function __construct( $hook_name = '', $parent_file = '' ) {
if ( ! empty( $hook_name ) ) {
$this->set_hookname( $hook_name );
}
if ( ! empty( $parent_file ) ) {
$this->parent_file = $parent_file;
}
}
public function set_hookname( $hook_name ) {
$this->hook_suffix = preg_replace( '#W#', '', strtolower( $hook_name ) );
}
public function populate_network_path() {
if ( ! function_exists( 'is_network_admin' ) ) {
return;
}
$this->is_network = is_network_admin();
$this->is_user = is_user_admin();
}
public function populate_base() {
$base = 'admin';
if ( 'appearance_page_custom-header' === $this->id ) {
$base = 'appearance_page_header';
}
if ( 'appearance_page_custom-background' === $this->id ) {
$base = 'appearance_page_background';
}
$this->base = $base;
}
public function populate_id() {
global $pagenow, $typenow, $taxnow;
if ( $typenow ) {
$this->id = "edit-{$typenow}";
} elseif ( $taxnow ) {
$this->id = "edit-{$taxnow}";
} else {
$this->id = $pagenow;
}
}
public function populate_key() {
$this->key = md5( serialize( array(
'id' => $this->id,
'base' => $this->base,
'action' => $this->action,
'post_type' => $this->post_type,
'taxonomy' => $this->taxonomy,
'is_network' => $this->is_network,
'is_user' => $this->is_user,
) ) );
}
public function populate_taxonomy() {
global $taxnow;
if ( $taxnow ) {
$this->taxonomy = $taxnow;
}
}
public function populate_post_type() {
global $typenow;
if ( $typenow ) {
$this->post_type = $typenow;
}
}
}
2.1 核心流程
-
get_current_screen()
: 这是我们调用的函数。它首先检查全局变量$current_screen
是否存在并且是一个WP_Screen
对象。如果不是,它会调用set_current_screen()
来初始化$current_screen
。最后,返回$current_screen
对象。 -
set_current_screen()
: 这个函数负责创建和初始化WP_Screen
对象。- 它首先创建一个新的
WP_Screen
对象(如果$current_screen
还没有被创建)。 - 然后,它调用
get_current_screen_hook()
来获取当前页面的 "hook name"。这才是确定当前页面的关键步骤。 - 接下来,它使用
WP_Screen
对象的各种populate_*()
方法,根据 hook name 以及全局变量(如$pagenow
,$typenow
,$taxnow
)来设置WP_Screen
对象的各种属性,例如id
,base
,post_type
,taxonomy
等。 - 最后,它触发两个 action hook:
load-$current_screen->id
和current_screen
,允许其他插件或主题在当前页面加载时执行自定义代码。
- 它首先创建一个新的
-
get_current_screen_hook()
: 这个函数是整个流程中最复杂的部分,也是决定当前页面的核心逻辑所在。它通过检查各种全局变量和$_GET
参数,来确定当前页面的 "hook name"。这个 hook name 最终会被用作WP_Screen
对象的id
属性的一部分。
2.2 get_current_screen_hook()
的逻辑
get_current_screen_hook()
函数使用一系列 if...elseif...else
语句,逐步缩小范围,确定当前页面。
条件 | 页面类型 | Hook Name |
---|---|---|
isset( $_GET['import'] ) |
导入页面 | 'import' |
$pagenow == 'plugins.php' |
插件页面 | 如果有 $_REQUEST['plugin'] 参数,则是 'plugin-install' ,否则是 'plugins' |
$pagenow == 'themes.php' |
主题页面 | 'themes' |
$typenow (当前文章类型) |
文章类型列表页面 | 'edit.php?post_type=' . $typenow 的钩子名称 (通过 get_plugin_page_hookname ) |
$taxnow (当前分类法) |
分类法列表页面 | 'edit-tags.php?taxonomy=' . $taxnow 的钩子名称 (通过 get_plugin_page_hookname ) |
isset( $_GET['page'] ) |
插件创建的页面 (通过 add_menu_page ) |
$_GET['page'] 的钩子名称 (通过 get_plugin_page_hookname ) |
$pagenow 是 'post.php' 或 'post-new.php' |
编辑或新建文章页面 | 'post' |
$pagenow 是 'media-upload.php' 或 'async-upload.php' |
媒体上传页面 | 'media-upload' |
$pagenow 是 'index.php' 或 'wp-activate.php' |
仪表盘或激活页面 | 'dashboard' |
其他情况 | 其他页面 | $pagenow |
2.3 WP_Screen
对象的属性
WP_Screen
类定义了许多属性,用于描述当前页面的各种信息。
属性 | 描述 |
---|---|
id |
页面的唯一标识符。通常是 $pagenow 、edit-$post_type 或 edit-$taxonomy 。 |
taxonomy |
当前页面的分类法名称,如果适用。 |
post_type |
当前页面的文章类型名称,如果适用。 |
base |
页面的基本类型。通常是 'admin' 。 |
action |
页面的动作,例如 'edit' 或 'add' 。 |
parent_base |
父页面的基本类型。 |
parent_file |
父页面的文件名。 |
is_network |
是否是网络管理页面(在 Multisite 环境中)。 |
is_user |
是否是用户管理页面(在 Multisite 环境中)。 |
三、实战演练:代码示例,手把手教学
现在,让我们通过一些代码示例,看看如何使用 get_current_screen()
来实现一些常见的任务。
3.1 只在文章编辑页面加载 JavaScript
add_action( 'admin_enqueue_scripts', 'my_admin_enqueue_scripts' );
function my_admin_enqueue_scripts() {
$screen = get_current_screen();
if ( $screen && $screen->id == 'post' ) {
wp_enqueue_script( 'my-custom-script', plugin_dir_url( __FILE__ ) . 'js/my-custom-script.js', array( 'jquery' ), '1.0', true );
}
}
这段代码会在文章编辑页面加载 my-custom-script.js
文件。 admin_enqueue_scripts
是一个在后台加载脚本和样式时触发的 action hook。 我们首先获取当前屏幕对象,然后检查它的 id
属性是否等于 'post'
。如果是,就加载我们的自定义脚本。
3.2 在特定插件页面添加自定义 Meta Box
假设我们有一个名为 "my-awesome-plugin" 的插件,并且它创建了一个名为 "my-awesome-page" 的后台页面。 我们想在这个页面上添加一个自定义 Meta Box。
add_action( 'add_meta_boxes', 'my_add_meta_boxes' );
function my_add_meta_boxes() {
$screen = get_current_screen();
if ( $screen && $screen->id == 'my-awesome-page' ) {
add_meta_box(
'my_meta_box',
'My Awesome Meta Box',
'my_meta_box_callback',
$screen->id,
'normal',
'default'
);
}
}
function my_meta_box_callback( $post ) {
// Meta Box 的内容
echo '<p>This is my awesome meta box!</p>';
}
这段代码会在 "my-awesome-page" 页面上添加一个名为 "My Awesome Meta Box" 的 Meta Box。 add_meta_boxes
是一个在添加 Meta Box 时触发的 action hook。 我们首先获取当前屏幕对象,然后检查它的 id
属性是否等于 'my-awesome-page'
。如果是,就添加我们的自定义 Meta Box。 my_meta_box_callback
函数负责渲染 Meta Box 的内容。
3.3 根据文章类型显示不同的帮助文本
add_action( 'current_screen', 'my_add_help_tab' );
function my_add_help_tab( $screen ) {
if ( 'edit-book' == $screen->id ) {
$screen->add_help_tab( array(
'id' => 'book_help_tab',
'title' => 'Book Help',
'content' => '<p>This is help content for the Book post type.</p>',
) );
} elseif ( 'edit-movie' == $screen->id ) {
$screen->add_help_tab( array(
'id' => 'movie_help_tab',
'title' => 'Movie Help',
'content' => '<p>This is help content for the Movie post type.</p>',
) );
}
}
这段代码会根据当前文章类型,在帮助选项卡中显示不同的帮助文本。 current_screen
是一个在当前屏幕对象被设置后触发的 action hook。 我们首先检查屏幕对象的 id
属性是否等于 'edit-book'
或 'edit-movie'
。如果是,就添加相应的帮助选项卡。
四、注意事项:避坑指南,安全第一
-
在正确的 action hook 中使用
get_current_screen()
:get_current_screen()
的返回值依赖于 WordPress 的加载顺序。 通常,在admin_enqueue_scripts
、add_meta_boxes
或current_screen
等 action hook 中使用它是安全的。 在更早的 action hook 中使用它可能会导致$current_screen
对象尚未被初始化。 -
检查返回值是否为
WP_Screen
对象: 在访问$screen
对象的属性之前,务必检查它是否是WP_Screen
对象,以避免潜在的错误。 可以使用is_a( $screen, 'WP_Screen' )
来进行检查。 -
谨慎使用
$_GET
和$_POST
参数: 虽然get_current_screen_hook()
函数会检查$_GET
参数来确定当前页面,但在你的代码中直接使用$_GET
和$_POST
参数时要小心,确保进行适当的验证和清理,以防止安全漏洞(例如 XSS 攻击)。 -
了解不同页面的
id
属性: 不同的页面有不同的id
属性。 可以使用var_dump( $screen )
来查看当前页面的完整WP_Screen
对象,从而了解它的id
属性和其他属性。 -
插件冲突: 某些插件可能会修改
$current_screen
对象或者get_current_screen_hook()
的行为,导致你的代码无法正常工作。 在出现问题时,尝试禁用其他插件来排查冲突。
五、总结:掌握核心,融会贯通
get_current_screen()
函数是 WordPress 后台开发中一个非常重要的工具。 通过深入了解它的工作原理,我们可以更好地控制后台页面的行为,为用户提供更好的体验。 记住,理解源码是提升技能的关键。 希望今天的讲座能帮助你更好地理解 get_current_screen()
,并在实际开发中灵活运用。
好了,今天的讲座就到这里。 感谢各位的观看,我们下期再见! 如果有任何问题,欢迎留言讨论。 祝大家编程愉快!