剖析 WordPress `wp_upload_dir()` 函数的源码:如何根据配置和日期生成媒体上传目录。

咳咳,大家好!今天咱们来聊聊 WordPress 里面一个非常重要,但又经常被大家忽略的小可爱—— wp_upload_dir() 函数。别看它名字平平无奇,它可是 WordPress 管理媒体上传的核心基石! 毫不夸张地说,理解了它,你就掌握了 WordPress 媒体库的"命运密码"!

一、wp_upload_dir():你的媒体文件“新家”设计师

简单来说,wp_upload_dir() 的作用就是帮你生成一个包含了媒体上传目录信息的数组。这个数组告诉你,WordPress 应该把用户上传的图片、视频、文档等等文件,统统放到哪个文件夹里。

<?php
$upload_dir = wp_upload_dir();

echo '<pre>';
print_r($upload_dir);
echo '</pre>';

/*
可能输出的结果类似这样:

Array
(
    [path] => /var/www/html/wp-content/uploads/2023/10
    [url] => http://example.com/wp-content/uploads/2023/10
    [subdir] => /2023/10
    [basedir] => /var/www/html/wp-content/uploads
    [baseurl] => http://example.com/wp-content/uploads
    [error] =>
)
*/
?>

可以看到,这个函数返回一个数组,包含了以下关键信息:

键名 说明 举例
path 上传目录的完整服务器路径(绝对路径) /var/www/html/wp-content/uploads/2023/10
url 上传目录的 URL 地址 http://example.com/wp-content/uploads/2023/10
subdir 上传目录相对于 basedir 的子目录 /2023/10
basedir 上传目录的根目录的完整服务器路径(绝对路径) /var/www/html/wp-content/uploads
baseurl 上传目录的根目录的 URL 地址 http://example.com/wp-content/uploads
error 如果有错误,这里会包含错误信息 空字符串(没有错误)

二、源码剖析:wp_upload_dir() 的"内心世界"

现在,让我们深入 wp-includes/functions.php 文件,扒一扒 wp_upload_dir() 函数的源码,看看它是如何工作的。

function wp_upload_dir( $time = null ) {
    static $cache = array();

    $key = md5( serialize( array( $time, get_current_blog_id() ) ) );

    if ( isset( $cache[ $key ] ) ) {
        return $cache[ $key ];
    }

    $siteurl = get_option( 'siteurl' );
    $upload_path = trim( get_option( 'upload_path' ) );

    if ( empty( $upload_path ) ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } else {
        $dir = $upload_path;
        if ( 'wp-content/uploads' == $upload_path ) {
            $dir = WP_CONTENT_DIR . '/uploads';
        } elseif ( 0 !== strpos( $dir, ABSPATH ) ) {
            // If upload_path is not absolute, then add ABSPATH.
            $dir = ABSPATH . $upload_path;
        }
    }

    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
        $url = WP_CONTENT_URL . '/uploads';
    } else {
        if ( 0 !== strpos( $url, 'http' ) ) {
            $url = $siteurl . '/' . $url;
        }
    }

    if ( is_multisite() ) {
        if ( ! get_site_option( 'ms_files_rewriting' ) ) {
            $ms_dir = '/sites/' . get_current_blog_id();
            $dir .= $ms_dir;
            $url .= $ms_dir;
        }
    }

    $today = gmdate( 'Y/m', $time );
    $basedir = $dir;
    $baseurl = $url;

    if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
        $dir .= '/' . $today;
        $url .= '/' . $today;
    }

    /**
     * Filters the uploads directory data array.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'baseurl'.
     */
    $uploads = apply_filters(
        'upload_dir',
        array(
            'path'   => $dir,
            'url'    => $url,
            'subdir' => '/' . $today,
            'basedir' => $basedir,
            'baseurl' => $baseurl,
            'error'  => false,
        )
    );

    $cache[ $key ] = $uploads;

    return $uploads;
}

别慌!让我们一步一步拆解这段代码:

  1. 缓存机制:static $cache

    static $cache = array();
    $key = md5( serialize( array( $time, get_current_blog_id() ) ) );
    
    if ( isset( $cache[ $key ] ) ) {
    return $cache[ $key ];
    }

    这段代码使用了静态变量 $cache 来缓存函数的结果。这意味着,对于相同的参数($time 和当前博客 ID),函数只会计算一次,后续调用会直接从缓存中读取结果,提高性能。 md5( serialize( array( $time, get_current_blog_id() ) ) ) 这行代码生成一个唯一的键值,用于在缓存数组中存储和检索结果。使用了 serialize 函数将数组转换为字符串,然后再使用 md5 函数生成哈希值。

  2. 读取配置:upload_pathupload_url_path

    $siteurl = get_option( 'siteurl' );
    $upload_path = trim( get_option( 'upload_path' ) );
    
    if ( empty( $upload_path ) ) {
    $dir = WP_CONTENT_DIR . '/uploads';
    } else {
    $dir = $upload_path;
    if ( 'wp-content/uploads' == $upload_path ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif ( 0 !== strpos( $dir, ABSPATH ) ) {
        // If upload_path is not absolute, then add ABSPATH.
        $dir = ABSPATH . $upload_path;
    }
    }
    
    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
    $url = WP_CONTENT_URL . '/uploads';
    } else {
    if ( 0 !== strpos( $url, 'http' ) ) {
        $url = $siteurl . '/' . $url;
    }
    }
    • 首先,它会尝试从数据库中读取两个重要的配置项:upload_pathupload_url_path。这两个配置项允许你自定义上传目录的路径和 URL。
    • 如果 upload_path 为空,则使用默认的 WP_CONTENT_DIR . '/uploads' 作为上传目录。
    • 如果 upload_path 的值是 wp-content/uploads,也会使用默认的 WP_CONTENT_DIR . '/uploads'
    • 如果 upload_path 不是绝对路径,则会在前面加上 ABSPATH,使其成为绝对路径。
    • 对于 upload_url_path,如果为空,则使用默认的 WP_CONTENT_URL . '/uploads'
    • 如果 upload_url_path 不是以 http 开头,则会在前面加上 siteurl

    简单来说,这段代码的作用就是确定上传目录的服务器路径和 URL。WordPress 会优先使用你在后台配置的自定义路径,如果没有配置,则使用默认路径。

  3. 多站点支持:is_multisite()

    if ( is_multisite() ) {
    if ( ! get_site_option( 'ms_files_rewriting' ) ) {
        $ms_dir = '/sites/' . get_current_blog_id();
        $dir .= $ms_dir;
        $url .= $ms_dir;
    }
    }

    这段代码专门处理 WordPress 多站点的情况。

    • is_multisite() 函数判断当前是否是多站点环境。
    • get_site_option( 'ms_files_rewriting' ) 用于检查是否启用了 "文件重写" 功能。如果未启用,WordPress 会为每个站点创建一个独立的上传目录,目录名为 /sites/{站点ID}

    这样做的目的是为了隔离不同站点的媒体文件,避免混淆。

  4. 日期目录:uploads_use_yearmonth_folders

    $today = gmdate( 'Y/m', $time );
    $basedir = $dir;
    $baseurl = $url;
    
    if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
    $dir .= '/' . $today;
    $url .= '/' . $today;
    }
    • gmdate( 'Y/m', $time ) 获取当前的年份和月份,格式为 YYYY/MM$time 参数允许指定一个时间戳,如果不指定,则使用当前时间。
    • get_option( 'uploads_use_yearmonth_folders' ) 检查是否启用了 "按年月组织上传文件" 功能。如果启用,WordPress 会在上传目录中创建以年份和月份命名的子目录,例如 /2023/10

    这个功能可以帮助你更好地组织媒体文件,方便查找和管理。

  5. 过滤器:upload_dir

    $uploads = apply_filters(
    'upload_dir',
    array(
        'path'   => $dir,
        'url'    => $url,
        'subdir' => '/' . $today,
        'basedir' => $basedir,
        'baseurl' => $baseurl,
        'error'  => false,
    )
    );

    这是 WordPress 插件和主题开发者大显身手的地方! apply_filters( 'upload_dir', ... ) 允许你通过 upload_dir 过滤器,修改 wp_upload_dir() 函数返回的数组。你可以自定义上传目录的路径、URL 等信息,实现各种高级功能。

  6. 返回结果

    $cache[ $key ] = $uploads;
    
    return $uploads;

    最后,将结果存入缓存并返回包含上传目录信息的数组。

三、实战演练:自定义上传目录

现在,让我们通过一个实际的例子,来演示如何使用 upload_dir 过滤器自定义上传目录。

假设你希望将所有媒体文件上传到 wp-content/my-uploads 目录,并且不使用年月组织。你可以创建一个简单的插件,添加以下代码:

<?php
/**
 * Plugin Name: Custom Upload Directory
 * Description: 自定义上传目录
 */

add_filter( 'upload_dir', 'custom_upload_dir' );

function custom_upload_dir( $dirs ) {
    $dirs['basedir'] = WP_CONTENT_DIR . '/my-uploads';
    $dirs['baseurl'] = WP_CONTENT_URL . '/my-uploads';
    $dirs['path'] = $dirs['basedir']; // 去掉年月文件夹
    $dirs['url'] = $dirs['baseurl'];  // 去掉年月文件夹
    $dirs['subdir']  = ''; // 清空 subdir

    return $dirs;
}

add_filter( 'pre_option_uploads_use_yearmonth_folders', '__return_false' );

这段代码做了以下几件事:

  • add_filter( 'upload_dir', 'custom_upload_dir' ):将 custom_upload_dir 函数挂载到 upload_dir 过滤器上。
  • custom_upload_dir( $dirs ):这个函数接收 wp_upload_dir() 函数返回的数组 $dirs,并修改其中的 basedirbaseurlpathurl 键的值,将其指向 wp-content/my-uploads 目录。
  • add_filter( 'pre_option_uploads_use_yearmonth_folders', '__return_false' ):禁用按年月组织上传文件功能。

激活这个插件后,所有上传的媒体文件都会被保存到 wp-content/my-uploads 目录,并且不会创建年月子目录。

四、注意事项:一些你必须知道的"潜规则"

  • 权限问题: 确保 WordPress 具有对上传目录的读写权限。否则,用户可能无法上传文件。
  • 安全性: 如果你自定义了上传目录,请务必确保目录的安全性,防止恶意文件上传。
  • 多站点: 在多站点环境下,自定义上传目录需要特别小心,确保不同站点的文件不会互相干扰。
  • *`preoption过滤器:**pre_option_uploads_use_yearmonth_folders是一个很有用的技巧。WordPress 使用get_option()函数从数据库中获取配置选项。preoption{$option}过滤器允许你在get_option()函数真正从数据库读取选项之前,拦截并修改选项的值。__return_false是一个 WordPress 内置函数,它总是返回false。所以,这段代码的目的是,无论数据库中uploads_use_yearmonth_folders选项的值是什么,都强制返回false`,从而禁用按年月组织上传文件功能。

五、总结:wp_upload_dir() 的"江湖地位"

wp_upload_dir() 函数是 WordPress 媒体上传的核心,理解它的工作原理对于 WordPress 开发者来说至关重要。通过 upload_dir 过滤器,你可以灵活地自定义上传目录,实现各种高级功能。

记住,掌握了 wp_upload_dir(),你就掌握了 WordPress 媒体库的"命运密码"!

希望今天的讲解对你有所帮助!下次再见!

发表回复

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