各位朋友们,晚上好!今天咱们来聊聊WordPress插件的“寻宝之旅”,看看get_plugins()
这个函数,是如何像一位经验老道的考古学家一样,把散落在插件目录里的宝藏(插件头信息)给挖掘出来的。
开场白:插件,WordPress的灵魂伴侣
WordPress之所以如此强大,很大程度上要归功于其丰富的插件生态。插件就像乐高积木,可以让你自由组合,搭建出各种功能的网站。而get_plugins()
函数,就是负责把这些积木都给你展示出来,让你知道有哪些积木可以用。
一、get_plugins()
:插件管理的指挥官
首先,咱们要明确一点,get_plugins()
函数位于wp-admin/includes/plugin.php
文件中。它的主要职责就是扫描插件目录,解析每个插件的头部信息,然后把这些信息整理成一个数组,方便你在后台管理插件。
咱们先看看get_plugins()
函数的大致框架(简化版):
function get_plugins( $plugin_folder = '' ) {
static $all_plugins;
if ( ! is_array( $all_plugins ) ) {
$all_plugins = array();
}
if ( isset( $all_plugins[ $plugin_folder ] ) ) {
return $all_plugins[ $plugin_folder ];
}
$wp_plugins = array();
$plugin_root = WP_PLUGIN_DIR;
if ( ! empty( $plugin_folder ) ) {
$plugin_root .= '/' . $plugin_folder;
}
// 1. 扫描插件目录
$plugins_dir = @opendir( $plugin_root );
$plugin_files = array();
if ( $plugins_dir ) {
while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
if ( substr( $file, 0, 1 ) == '.' ) {
continue;
}
if ( is_dir( $plugin_root . '/' . $file ) ) {
$plugins_subdir = @opendir( $plugin_root . '/' . $file );
if ( $plugins_subdir ) {
while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
if ( substr( $subfile, 0, 1 ) == '.' ) {
continue;
}
if ( substr( $subfile, -4 ) == '.php' ) {
$plugin_files[] = "$file/$subfile";
}
}
closedir( $plugins_subdir );
}
} else {
if ( substr( $file, -4 ) == '.php' ) {
$plugin_files[] = $file;
}
}
}
closedir( $plugins_dir );
}
if ( empty( $plugin_files ) ) {
return array();
}
// 2. 加载插件头部信息
foreach ( $plugin_files as $plugin_file ) {
if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
continue;
}
$plugin_data = get_plugin_data( "$plugin_root/$plugin_file" );
if ( empty( $plugin_data['Name'] ) ) {
continue;
}
$wp_plugins[ $plugin_file ] = $plugin_data;
}
$all_plugins[ $plugin_folder ] = $wp_plugins;
return $wp_plugins;
}
这个简化版已经包含了get_plugins()
的核心逻辑:
- 扫描插件目录: 找到所有
.php
文件(可能是直接在插件目录下,也可能在子目录里)。 - 加载插件头部信息: 读取每个
.php
文件的头部注释,提取插件的名称、描述、版本等信息。
二、深入虎穴:扫描插件目录
这部分的代码主要负责遍历插件目录,找到所有的插件文件。
$plugins_dir = @opendir( $plugin_root );
$plugin_files = array();
if ( $plugins_dir ) {
while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
if ( substr( $file, 0, 1 ) == '.' ) {
continue;
}
if ( is_dir( $plugin_root . '/' . $file ) ) {
$plugins_subdir = @opendir( $plugin_root . '/' . $file );
if ( $plugins_subdir ) {
while ( ( $subfile = readdir( $plugins_subdir ) ) !== false ) {
if ( substr( $subfile, 0, 1 ) == '.' ) {
continue;
}
if ( substr( $subfile, -4 ) == '.php' ) {
$plugin_files[] = "$file/$subfile";
}
}
closedir( $plugins_subdir );
}
} else {
if ( substr( $file, -4 ) == '.php' ) {
$plugin_files[] = $file;
}
}
}
closedir( $plugins_dir );
}
这段代码逻辑清晰,但我们还是来拆解一下:
opendir()
:打开插件目录。readdir()
:读取目录中的文件和子目录。is_dir()
:判断是否是目录。substr()
:截取字符串,用来判断文件名是否以.php
结尾,或者是否以.
开头(隐藏文件)。
这段代码会递归地遍历插件目录,找到所有以.php
结尾的文件,并将它们的文件名存储在$plugin_files
数组中。 注意,这里的路径是相对于插件目录的。
三、拨云见日:解析插件头部信息
找到了插件文件,下一步就是读取它们的头部信息。 这个任务由get_plugin_data()
函数来完成。
function get_plugin_data( $plugin_file, $markup = true, $translate = true ) {
$default_headers = array(
'Name' => 'Plugin Name',
'PluginURI' => 'Plugin URI',
'Version' => 'Version',
'Description' => 'Description',
'Author' => 'Author',
'AuthorURI' => 'Author URI',
'TextDomain' => 'Text Domain',
'DomainPath' => 'Domain Path',
'Network' => 'Network',
'RequiresWP' => 'Requires at least',
'RequiresPHP' => 'Requires PHP',
'UpdateURI' => 'Update URI',
);
$plugin_data = get_file_data( $plugin_file, $default_headers, 'plugin' );
if ( $markup ) {
$plugin_data['Name'] = wptexturize( $plugin_data['Name'] );
$plugin_data['PluginURI'] = esc_url( $plugin_data['PluginURI'] );
$plugin_data['Description'] = wptexturize( $plugin_data['Description'] );
$plugin_data['Author'] = wptexturize( $plugin_data['Author'] );
$plugin_data['AuthorURI'] = esc_url( $plugin_data['AuthorURI'] );
$plugin_data['Version'] = wptexturize( $plugin_data['Version'] );
} else {
$plugin_data['Name'] = wp_strip_all_tags( $plugin_data['Name'] );
$plugin_data['Description'] = wp_strip_all_tags( $plugin_data['Description'] );
$plugin_data['Author'] = wp_strip_all_tags( $plugin_data['Author'] );
}
if ( $translate && ! empty( $plugin_data['TextDomain'] ) ) {
/**
* Filters the translated plugin header data array.
*
* @since 2.7.0
*
* @param string[] $plugin_data Plugin header data.
* @param string $plugin_file Path to the plugin file.
* @param bool $markup Whether to apply markup to the plugin data. Defaults to true.
* @param bool $translate Whether to translate the plugin data. Defaults to true.
*/
$plugin_data = apply_filters( 'plugin_data_i18n', $plugin_data, $plugin_file, $markup, $translate );
}
$plugin_data = apply_filters( 'extra_plugin_headers', $plugin_data, $plugin_file );
return $plugin_data;
}
get_plugin_data()
函数的核心在于get_file_data()
函数,它负责读取文件内容并解析头部信息。我们先来看看get_plugin_data()
做了哪些事情:
- 定义默认头部信息:
$default_headers
数组定义了插件头部信息的字段名和对应的标签。 - 调用
get_file_data()
: 将插件文件路径和默认头部信息传递给get_file_data()
函数。 - 处理数据: 对获取到的数据进行一些处理,比如URL转义、文本格式化等。
- 应用过滤器: 允许其他插件或主题通过过滤器修改插件数据。
接下来,我们深入get_file_data()
函数,看看它是如何读取和解析文件头部信息的。
function get_file_data( $file, $default_headers, $context = '' ) {
// We don't need to write to the file, so just use r.
$fp = fopen( $file, 'r' );
// Pull out the first 8KiB of the file.
$file_data = fread( $fp, 8192 );
// PHP will close file handle, but we are good citizens.
fclose( $fp );
// Make sure we catch CR-only line endings.
$file_data = str_replace( "r", "n", $file_data );
/**
* Filters the extra file headers used to retrieve metadata.
*
* The dynamic portion of the hook name, `$context`, refers
* to the context where the hook is used, such as a plugin or theme.
*
* @since 2.9.0
*
* @param string[] $default_headers Array of default file headers.
* @param string $file Full path to the file.
*/
$default_headers = apply_filters( "extra_{$context}_headers", $default_headers, $file );
foreach ( $default_headers as $field => $regex ) {
if ( preg_match( '/^[ t/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_data, $match ) && $match[1] ) {
$all_headers[ $field ] = trim( $match[1] );
} else {
$all_headers[ $field ] = '';
}
}
return $all_headers;
}
get_file_data()
函数的主要步骤如下:
- 打开文件: 使用
fopen()
函数以只读模式打开文件。 - 读取文件内容: 使用
fread()
函数读取文件的前8KB内容。 为什么只读取前8KB? 因为插件的头部信息通常都在文件的开头部分,所以没有必要读取整个文件。 - 关闭文件: 使用
fclose()
函数关闭文件。 - 处理换行符: 将
r
替换为n
,确保换行符的一致性。 - 应用过滤器: 允许通过过滤器修改默认的头部信息。
- 正则匹配: 遍历默认头部信息,使用正则表达式从文件内容中提取对应的值。
重点来了:正则表达式
get_file_data()
函数使用正则表达式来匹配插件头部信息。让我们来看一下这个正则表达式:
/^[ t/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi
^[ t/*#@]*
: 匹配行首的空白字符(空格、制表符)、/*
、#
、@
等注释符号,允许头部信息出现在注释中。preg_quote( $regex, '/')
: 转义$regex
中的特殊字符,确保正则表达式的准确性。$regex
就是我们在$default_headers
中定义的标签,比如Plugin Name
。:(.*)$
: 匹配标签后面的冒号和所有内容,直到行尾。$match[1]
就是我们想要提取的插件信息。m
: 多行模式,允许正则表达式匹配多行文本。i
: 忽略大小写模式,允许正则表达式忽略大小写。
举个例子,如果$regex
是Plugin Name
,那么正则表达式就会匹配类似下面这样的字符串:
/*
Plugin Name: My Awesome Plugin
*/
或者
# Plugin Name: My Awesome Plugin
四、整合与返回:插件信息的归宿
回到get_plugins()
函数,在解析完所有插件的头部信息后,它会将这些信息存储在一个数组中,并返回这个数组。
foreach ( $plugin_files as $plugin_file ) {
if ( ! is_readable( "$plugin_root/$plugin_file" ) ) {
continue;
}
$plugin_data = get_plugin_data( "$plugin_root/$plugin_file" );
if ( empty( $plugin_data['Name'] ) ) {
continue;
}
$wp_plugins[ $plugin_file ] = $plugin_data;
}
$all_plugins[ $plugin_folder ] = $wp_plugins;
return $wp_plugins;
$wp_plugins
数组的键是插件的文件名,值是插件的头部信息。 $all_plugins
是一个静态变量,用来缓存已经扫描过的插件目录,避免重复扫描。
五、实战演练:自己动手丰衣足食
为了更好地理解get_plugins()
函数的工作原理,我们可以自己动手写一个简单的函数来模拟它的功能。
function my_get_plugins( $plugin_dir ) {
$plugins = array();
$files = scandir( $plugin_dir );
foreach ( $files as $file ) {
if ( $file === '.' || $file === '..' ) {
continue;
}
$plugin_file = $plugin_dir . '/' . $file;
if ( is_dir( $plugin_file ) ) {
// 递归扫描子目录,简化处理,只取第一个php文件
$sub_files = scandir( $plugin_file );
foreach($sub_files as $sub_file) {
if(substr($sub_file, -4) === '.php') {
$plugin_file = $plugin_dir . '/' . $file . '/' . $sub_file;
break;
}
}
}
if ( substr( $file, -4 ) === '.php' || (is_dir($plugin_file) && substr($plugin_file, -4) === '.php') ) {
$plugin_data = my_get_plugin_data( $plugin_file );
if ( ! empty( $plugin_data['Name'] ) ) {
$plugins[ $file ] = $plugin_data;
}
}
}
return $plugins;
}
function my_get_plugin_data( $plugin_file ) {
$default_headers = array(
'Name' => 'Plugin Name',
'Version' => 'Version',
'Description' => 'Description',
'Author' => 'Author',
);
$file_contents = file_get_contents( $plugin_file );
$plugin_data = array();
foreach ( $default_headers as $field => $regex ) {
if ( preg_match( '/^[ t/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi', $file_contents, $match ) && $match[1] ) {
$plugin_data[ $field ] = trim( $match[1] );
} else {
$plugin_data[ $field ] = '';
}
}
return $plugin_data;
}
// 使用示例
$plugins_dir = WP_PLUGIN_DIR;
$my_plugins = my_get_plugins( $plugins_dir );
echo "<pre>";
print_r( $my_plugins );
echo "</pre>";
这个简单的例子可以让你更直观地了解get_plugins()
函数的工作流程。 当然,这个例子只是一个简化版,没有处理所有的情况,比如插件的目录结构、错误处理等。
六、总结:插件管理的艺术
get_plugins()
函数是WordPress插件管理的核心函数之一。它通过扫描插件目录,解析插件头部信息,将所有插件的信息整理成一个数组,方便我们在后台管理插件。
函数/代码段 | 功能描述 |
---|---|
get_plugins() |
扫描插件目录,解析插件头部信息,返回插件列表。 |
opendir() |
打开目录。 |
readdir() |
读取目录中的文件和子目录。 |
is_dir() |
判断是否是目录。 |
get_plugin_data() |
读取插件文件内容,解析插件头部信息。 |
get_file_data() |
从文件中读取数据,使用正则表达式匹配头部信息。 |
正则表达式 | '/^[ t/*#@]*' . preg_quote( $regex, '/' ) . ':(.*)$/mi' ,用于匹配插件头部信息。 |
$default_headers |
数组,定义了插件头部信息的字段名和对应的标签。 |
WP_PLUGIN_DIR |
常量,定义了插件目录的路径。 |
通过对get_plugins()
函数的源码剖析,我们不仅了解了它的工作原理,也学习了一些PHP编程技巧,比如目录遍历、文件读取、正则表达式等。 希望今天的分享对大家有所帮助!
各位,下课!