解释 `wp_upload_dir()` 函数的源码,它是如何生成媒体上传目录的路径和 URL 的?

咳咳,各位同学,欢迎来到今天的“Wordpress 媒体上传目录探秘”讲座。我是你们今天的讲师,咱们废话不多说,直接进入正题!

今天我们要解剖的,是 WordPress 中一个非常重要且常用的函数:wp_upload_dir()。它就像 WordPress 的“文件管理员”,负责告诉你,你的媒体文件都应该放在哪里,以及如何通过 URL 访问它们。

一、wp_upload_dir() 的身世背景

首先,我们要知道 wp_upload_dir() 函数位于 wp-includes/functions.php 文件中。它返回一个数组,包含了媒体上传目录的各种信息,比如路径、URL 等等。

二、wp_upload_dir() 的庐山真面目(源码解析)

让我们直接来看源码(简化版,方便理解):

function wp_upload_dir( $time = null, $create_dir = true, $deprecated = false ) {
    global $switched;
    static $cache = array();

    if ( $deprecated ) {
        _deprecated_argument( __FUNCTION__, '2.0' );
    }

    $key = md5( serialize( array( $time, $create_dir, $switched ) ) );

    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 ( 0 !== strpos( $dir, ABSPATH ) ) {
            $dir = trailingslashit( ABSPATH ) . $dir;
        }
    }

    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
        $url = trailingslashit( $siteurl ) . 'wp-content/uploads';
    }

    // If multisite (and not switched), confirm that the upload base exists.
    if ( is_multisite() && ! is_switched() ) {
        $ms_dir = '/sites/' . get_current_blog_id();
        $dir   .= $ms_dir;
        $url   .= $ms_dir;
    }

    if ( false !== strpos( $time, '/' ) ) {
        $time_bits = explode( '/', $time );
        $time = array(
            'year'  => $time_bits[0],
            'month' => $time_bits[1],
        );
        unset( $time_bits );
    }

    if ( empty( $time ) ) {
        $time = gmdate( 'Y/m' );
    }

    if ( is_array( $time ) ) {
        $year  = ( ! empty( $time['year'] ) ) ? absint( $time['year'] ) : gmdate( 'Y' );
        $month = ( ! empty( $time['month'] ) ) ? zeroise( absint( $time['month'] ), 2 ) : gmdate( 'm' );
    } else {
        $year  = substr( $time, 0, 4 );
        $month = substr( $time, 5, 2 );
    }

    $subdir = "/{$year}/{$month}";
    $dir   .= $subdir;
    $url   .= $subdir;

    $basedir = $dir;
    $baseurl = $url;
    $error = false;

    if ( $create_dir ) {
        $stat = wp_mkdir_p( untrailingslashit( $dir ) );
        if ( ! $stat ) {
            $error = true;
        }
    }

    $ret = array(
        'path'    => $dir,
        'url'     => $url,
        'subdir'  => $subdir,
        'basedir' => $basedir,
        'baseurl' => $baseurl,
        'error'   => $error,
    );

    $cache[ $key ] = $ret;

    return $ret;
}

是不是感觉有点长?别怕,我们一步一步来拆解它。

2.1 缓存机制

    static $cache = array();
    $key = md5( serialize( array( $time, $create_dir, $switched ) ) );

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

首先,函数使用了静态变量 $cache 来缓存结果。这意味着,如果相同的参数被多次调用,函数会直接从缓存中返回结果,而不会重新计算。这提高了性能。md5(serialize()) 主要用来生成一个基于传入参数的唯一键值。

2.2 获取配置信息

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

这里,函数从 WordPress 选项中获取了三个关键配置:

  • siteurl: WordPress 站点的 URL。
  • upload_path: 上传目录的路径。这是一个可选配置,允许你自定义上传目录的位置。如果没有设置,默认为 wp-content/uploads
  • upload_url_path: 上传目录的 URL。同样是可选配置,用于自定义上传目录的 URL。如果没有设置,默认为 siteurl . '/wp-content/uploads'

2.3 确定上传目录的基本路径

    if ( empty( $upload_path ) ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } else {
        $dir = $upload_path;
        if ( 0 !== strpos( $dir, ABSPATH ) ) {
            $dir = trailingslashit( ABSPATH ) . $dir;
        }
    }

这段代码确定了上传目录的基本路径。如果 upload_path 没有设置,就使用默认的 WP_CONTENT_DIR . '/uploads'WP_CONTENT_DIRwp-content 目录的绝对路径。

如果 upload_path 设置了,函数会检查它是否是绝对路径。如果不是,就把它拼接到 ABSPATH (WordPress 根目录的绝对路径) 后面。

2.4 确定上传目录的基本 URL

    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
        $url = trailingslashit( $siteurl ) . 'wp-content/uploads';
    }

这段代码确定了上传目录的基本 URL。如果 upload_url_path 没有设置,就使用默认的 siteurl . '/wp-content/uploads'

2.5 多站点支持

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

如果 WordPress 是多站点模式,并且当前不是“切换”到其他站点(is_switched() 函数用于判断是否已经切换到其他站点),那么这段代码会在路径和 URL 中添加 /sites/{blog_id} 子目录,用于区分不同站点的上传文件。

2.6 时间参数处理 (年/月)

    if ( false !== strpos( $time, '/' ) ) {
        $time_bits = explode( '/', $time );
        $time = array(
            'year'  => $time_bits[0],
            'month' => $time_bits[1],
        );
        unset( $time_bits );
    }

    if ( empty( $time ) ) {
        $time = gmdate( 'Y/m' );
    }

    if ( is_array( $time ) ) {
        $year  = ( ! empty( $time['year'] ) ) ? absint( $time['year'] ) : gmdate( 'Y' );
        $month = ( ! empty( $time['month'] ) ) ? zeroise( absint( $time['month'] ), 2 ) : gmdate( 'm' );
    } else {
        $year  = substr( $time, 0, 4 );
        $month = substr( $time, 5, 2 );
    }

这段代码处理了时间参数 $time$time 可以是 null、字符串 (YYYY/MM) 或数组 (array(‘year’ => YYYY, ‘month’ => MM))。

  • 如果 $time 是 null,就使用当前的年份和月份。
  • 如果 $time 是字符串,就把它分割成年份和月份。
  • 如果 $time 是数组,就从中提取年份和月份。

zeroise() 函数用于在月份前面补 0,例如把 1 变成 01。

2.7 生成子目录

    $subdir = "/{$year}/{$month}";
    $dir   .= $subdir;
    $url   .= $subdir;

根据年份和月份,生成子目录 $subdir,并把它拼接到路径和 URL 后面。

2.8 创建目录

    if ( $create_dir ) {
        $stat = wp_mkdir_p( untrailingslashit( $dir ) );
        if ( ! $stat ) {
            $error = true;
        }
    }

如果 $create_dir 参数为 true (默认值),就使用 wp_mkdir_p() 函数创建目录。wp_mkdir_p() 函数会递归创建目录,类似于 Linux 的 mkdir -p 命令。

如果目录创建失败,就把 $error 设置为 true。

2.9 返回结果

    $ret = array(
        'path'    => $dir,
        'url'     => $url,
        'subdir'  => $subdir,
        'basedir' => $basedir,
        'baseurl' => $baseurl,
        'error'   => $error,
    );

    $cache[ $key ] = $ret;

    return $ret;

最后,函数返回一个数组,包含了以下信息:

  • path: 完整的上传目录路径。
  • url: 完整的上传目录 URL。
  • subdir: 年/月 子目录。
  • basedir: 上传目录的基本路径 (不包含年/月)。
  • baseurl: 上传目录的基本 URL (不包含年/月)。
  • error: 是否发生错误。

并将结果存入缓存。

三、wp_upload_dir() 的参数

wp_upload_dir() 函数接受三个参数:

参数 类型 默认值 描述
$time string/array/null null 指定上传目录的年份和月份。可以是以下几种形式:

  • null: 使用当前的年份和月份。
  • ‘YYYY/MM’: 指定年份和月份的字符串。
  • array(‘year’ => YYYY, ‘month’ => MM): 指定年份和月份的数组。
$create_dir bool true 是否创建目录。如果为 true,函数会尝试创建上传目录。如果为 false,函数不会创建目录。
$deprecated bool false 这是一个废弃的参数,不要使用。

四、wp_upload_dir() 的使用示例

<?php
$upload_dir = wp_upload_dir();

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

// 获取当前月份的上传目录路径
$upload_path = $upload_dir['path'];
echo "当前月份的上传目录路径: " . $upload_path . "<br>";

// 获取当前月份的上传目录 URL
$upload_url = $upload_dir['url'];
echo "当前月份的上传目录 URL: " . $upload_url . "<br>";

// 获取基本目录
$basedir = $upload_dir['basedir'];
echo "基本目录: " . $basedir . "<br>";

// 获取基本URL
$baseurl = $upload_dir['baseurl'];
echo "基本URL: " . $baseurl . "<br>";

// 指定年份和月份
$upload_dir_2023_10 = wp_upload_dir( '2023/10' );
echo "2023年10月的上传目录路径: " . $upload_dir_2023_10['path'] . "<br>";

// 指定年份和月份, 不创建目录
$upload_dir_2024_01_no_create = wp_upload_dir( '2024/01', false );
echo "2024年1月的上传目录路径 (不创建目录): " . $upload_dir_2024_01_no_create['path'] . "<br>";
?>

这段代码演示了如何使用 wp_upload_dir() 函数来获取上传目录的信息。

五、自定义上传目录

WordPress 允许你自定义上传目录的位置和 URL。你可以在 wp-config.php 文件中定义 WP_CONTENT_DIRWP_CONTENT_URL 常量来修改 wp-content 目录的位置。

或者,你可以在 WordPress 后台的“设置 -> 媒体”页面中设置“上传文件”选项,来修改上传目录的路径和 URL。 但是强烈建议使用常量定义,以保证路径的绝对性和可靠性。

六、注意事项

  • 确保上传目录具有正确的权限,以便 WordPress 可以写入文件。
  • 避免在 wp-content/uploads 目录中存放除了媒体文件以外的其他文件。
  • 如果你的网站使用了 CDN,你需要配置 CDN 来加速上传目录的访问。

七、总结

wp_upload_dir() 函数是 WordPress 中一个非常重要的函数,它负责告诉你媒体文件应该放在哪里,以及如何通过 URL 访问它们。理解它的工作原理,可以帮助你更好地管理你的媒体文件,并自定义上传目录的位置和 URL。

八、思考题

  1. wp_upload_dir() 函数中的缓存机制有什么作用?
  2. 如果 upload_path 选项没有设置,wp_upload_dir() 函数会使用哪个默认路径?
  3. wp_mkdir_p() 函数的作用是什么?它和 mkdir() 函数有什么区别?
  4. 如何自定义 WordPress 的上传目录?有哪些方法?
  5. 在多站点模式下,wp_upload_dir() 函数如何区分不同站点的上传文件?

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

发表回复

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