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

各位观众老爷,大家好!今天咱们来聊聊 WordPress 里的一个“幕后英雄”—— wp_upload_dir() 函数。别看它名字平平无奇,它可是掌管着你网站上所有媒体文件的命运,负责规划它们“安家落户”的地点。

咱们今天就来扒一扒它的源码,看看它是如何一步步计算出你的媒体文件该放在哪个文件夹,以及如何生成相应的 URL 的。准备好了吗?发车!

一、wp_upload_dir():你的媒体文件“导航员”

简单来说,wp_upload_dir() 函数的作用就是返回一个包含了媒体上传目录信息的数组。这个数组里面有什么呢?大概是这些:

键 (Key) 值 (Value)
path 上传目录的完整服务器路径,例如 /var/www/wordpress/wp-content/uploads/2023/10。注意,这是服务器上的真实路径,不是 URL。
url 上传目录的完整 URL,例如 https://yourwebsite.com/wp-content/uploads/2023/10。这就是你在浏览器里访问媒体文件的地址。
subdir 上传目录相对于 wp-content/uploads 目录的相对路径,例如 /2023/10。 如果 wp-content/uploads 是你的上传根目录,那么这就是月份文件夹。
basedir wp-content/uploads 目录的完整服务器路径。
baseurl wp-content/uploads 目录的完整 URL。
error 如果发生错误,这里会包含错误信息。如果没有错误,则为空字符串。

有了这些信息,WordPress 就能轻松地把你的图片、视频和其他文件存放到正确的位置,并且在你的网站上正确显示它们。

二、源码剖析:一步一步揭秘

好了,废话不多说,直接上源码!我们来看一下 wp-includes/functions.php 文件中 wp_upload_dir() 函数的关键部分(为了方便讲解,我对源码进行了一些简化和注释):

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

    if ( null === $time ) {
        $time = current_time( 'mysql' );
    }

    $key = md5( $time . '|' . $create_dir . '|' . $refresh_cache );

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

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

    // 如果用户自定义了 upload_path,则使用自定义路径,否则默认为 'wp-content/uploads'
    if ( empty( $upload_path ) ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif ( 0 !== strpos( $upload_path, ABSPATH ) ) {
        // 如果 upload_path 不是绝对路径,则相对于 ABSPATH
        $dir = ABSPATH . $upload_path;
    } else {
        $dir = $upload_path;
    }

    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
        $url = content_url( 'uploads' );
    }

    // 获取年月文件夹的格式,默认为 'Y/m'
    $uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' );
    if ( false !== $uploads_use_yearmonth_folders &&
        ( ! ( is_multisite() && ! get_site_option( 'ms_upload_use_yearmonth_folders' ) && is_main_site() ) )
    ) {
        // 获取当前的年份和月份
        $subdir = '/' . date( 'Y/m', strtotime( $time ) );
    } else {
        $subdir = '';
    }

    $dir .= $subdir;
    $url .= $subdir;

    // 处理多站点的情况(这里简化了部分代码)
    if ( is_multisite() ) {
        // ... (多站点相关逻辑) ...
    }

    $basedir = $dir;
    $baseurl = $url;

    // 创建目录(如果需要并且目录不存在)
    if ( $create_dir ) {
        $error = false;
        if ( ! wp_mkdir_p( $dir ) ) {
            if ( is_dir( $dir ) ) {
                if ( ! is_writable( $dir ) ) {
                    $error = sprintf(
                        /* translators: %s: Directory path. */
                        __( 'Upload directory %s is not writable.' ),
                        $dir
                    );
                }
            } else {
                $error = sprintf(
                    /* translators: %s: Directory path. */
                    __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
                    $dir
                );
            }
        }

        if ( $error ) {
            return array(
                'path'    => false,
                'url'     => false,
                'subdir'  => false,
                'basedir' => false,
                'baseurl' => false,
                'error'   => $error,
            );
        }
    }

    // 返回结果数组
    $ret = array(
        'path'    => $dir,
        'url'     => $url,
        'subdir'  => $subdir,
        'basedir' => $basedir,
        'baseurl' => $baseurl,
        'error'   => false,
    );

    $cache[ $key ] = $ret;

    return apply_filters( 'upload_dir', $ret );
}

现在,让我们逐行拆解一下:

  1. 缓存机制:

    static $cache = array();
    if ( null === $time ) {
        $time = current_time( 'mysql' );
    }
    
    $key = md5( $time . '|' . $create_dir . '|' . $refresh_cache );
    
    if ( isset( $cache[ $key ] ) && ! $refresh_cache ) {
        return $cache[ $key ];
    }

    这段代码使用了静态变量 $cache 来缓存结果。 wp_upload_dir() 函数会根据传入的参数生成一个唯一的 key,然后检查缓存中是否已经存在对应的结果。如果存在并且 $refresh_cachefalse,则直接返回缓存结果,避免重复计算,提高效率。

    为什么要缓存呢?因为计算上传目录可能涉及到读取数据库配置、判断文件系统状态等操作,比较耗时。缓存可以显著提升性能,尤其是当多次调用 wp_upload_dir() 函数时。

  2. 获取上传路径和 URL:

    $upload_path = trim( get_option( 'upload_path' ) );
    
    // 如果用户自定义了 upload_path,则使用自定义路径,否则默认为 'wp-content/uploads'
    if ( empty( $upload_path ) ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif ( 0 !== strpos( $upload_path, ABSPATH ) ) {
        // 如果 upload_path 不是绝对路径,则相对于 ABSPATH
        $dir = ABSPATH . $upload_path;
    } else {
        $dir = $upload_path;
    }
    
    $url = get_option( 'upload_url_path' );
    if ( empty( $url ) ) {
        $url = content_url( 'uploads' );
    }

    这里首先从数据库中读取 upload_pathupload_url_path 选项。这两个选项允许用户自定义上传目录的路径和 URL。

    • 如果 upload_path 为空,则使用默认路径 WP_CONTENT_DIR . '/uploads',也就是 wp-content/uploads 目录。
    • 如果 upload_path 不是绝对路径,则将其视为相对于 WordPress 根目录的路径,并使用 ABSPATH 进行拼接。
    • upload_url_path 的处理方式类似,如果为空,则使用 content_url( 'uploads' ) 函数生成默认的 URL。

    WP_CONTENT_DIRwp-config.php 中定义的 WP_CONTENT_DIR 常量,表示 wp-content 目录的绝对路径。ABSPATH 也是在 wp-config.php 中定义的,表示 WordPress 根目录的绝对路径。 content_url() 函数会根据你的 WordPress 设置和网站地址生成正确的 wp-content 目录的 URL。

  3. 处理年月文件夹:

    $uploads_use_yearmonth_folders = get_option( 'uploads_use_yearmonth_folders' );
    if ( false !== $uploads_use_yearmonth_folders &&
        ( ! ( is_multisite() && ! get_site_option( 'ms_upload_use_yearmonth_folders' ) && is_main_site() ) )
    ) {
        // 获取当前的年份和月份
        $subdir = '/' . date( 'Y/m', strtotime( $time ) );
    } else {
        $subdir = '';
    }
    
    $dir .= $subdir;
    $url .= $subdir;

    这段代码检查是否启用了按年月组织上传文件的选项。如果启用了(uploads_use_yearmonth_folders 选项为 true),则根据当前时间生成形如 /2023/10 的子目录,并将其拼接到 $dir$url 后面。

    date( 'Y/m', strtotime( $time ) ) 函数用于格式化时间戳。strtotime( $time )$time 转换为时间戳,date( 'Y/m', ...) 则将其格式化为 YYYY/MM 的形式。

  4. 处理多站点:

    if ( is_multisite() ) {
        // ... (多站点相关逻辑) ...
    }

    这部分代码处理多站点的情况。在多站点环境中,每个站点可以有自己的上传目录。这部分代码会根据当前站点的 ID 和设置来调整上传路径和 URL。(为了简化,这里省略了具体的代码)。

  5. 创建目录:

    if ( $create_dir ) {
        $error = false;
        if ( ! wp_mkdir_p( $dir ) ) {
            if ( is_dir( $dir ) ) {
                if ( ! is_writable( $dir ) ) {
                    $error = sprintf(
                        /* translators: %s: Directory path. */
                        __( 'Upload directory %s is not writable.' ),
                        $dir
                    );
                }
            } else {
                $error = sprintf(
                    /* translators: %s: Directory path. */
                    __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
                    $dir
                );
            }
        }
    
        if ( $error ) {
            return array(
                'path'    => false,
                'url'     => false,
                'subdir'  => false,
                'basedir' => false,
                'baseurl' => false,
                'error'   => $error,
            );
        }
    }

    如果 $create_dirtrue(默认情况),则尝试创建上传目录。这里使用了 wp_mkdir_p() 函数,它可以递归地创建目录,也就是说,如果父目录不存在,它也会自动创建。

    如果创建目录失败,则会检查目录是否已经存在,以及是否可写。如果存在问题,则返回一个包含错误信息的数组。

  6. 返回结果:

    $ret = array(
        'path'    => $dir,
        'url'     => $url,
        'subdir'  => $subdir,
        'basedir' => $basedir,
        'baseurl' => $baseurl,
        'error'   => false,
    );
    
    $cache[ $key ] = $ret;
    
    return apply_filters( 'upload_dir', $ret );

    最后,将计算出的路径、URL 和其他信息封装到一个数组中,并将其存储到缓存中。然后,使用 apply_filters( 'upload_dir', $ret ) 应用 upload_dir 过滤器。这个过滤器允许其他插件或主题修改上传目录的信息。

三、upload_dir 过滤器:给你的媒体文件“改名换姓”

upload_dir 过滤器是一个非常强大的工具,它允许你自定义上传目录的行为。你可以使用它来:

  • 修改上传路径和 URL。
  • 更改年月文件夹的格式。
  • 将文件上传到不同的存储位置,例如云存储服务。
  • 等等。

下面是一个使用 upload_dir 过滤器的例子,它可以将上传目录更改为 wp-content/my-uploads

add_filter( 'upload_dir', 'my_custom_upload_dir' );

function my_custom_upload_dir( $dirs ) {
    $dirs['basedir'] = WP_CONTENT_DIR . '/my-uploads';
    $dirs['baseurl'] = content_url( 'my-uploads' );
    $dirs['path'] = $dirs['basedir'] . $dirs['subdir'];
    $dirs['url'] = $dirs['baseurl'] . $dirs['subdir'];
    return $dirs;
}

在这个例子中,我们定义了一个名为 my_custom_upload_dir() 的函数,它接收一个 $dirs 数组作为参数。这个数组就是 wp_upload_dir() 函数返回的数组。

我们在 my_custom_upload_dir() 函数中修改了 $dirs 数组的 basedirbaseurlpathurl 键,将上传目录更改为 wp-content/my-uploads

最后,我们使用 add_filter( 'upload_dir', 'my_custom_upload_dir' )my_custom_upload_dir() 函数注册为 upload_dir 过滤器的回调函数。

四、实际应用:一些场景示例

  • 自定义上传目录: 你可以使用 upload_dir 过滤器将上传目录更改为云存储服务,例如 Amazon S3 或 Google Cloud Storage。这样可以减轻服务器的负担,并提高网站的性能。
  • 按用户组织上传文件: 在多用户博客或社区网站中,你可以使用 upload_dir 过滤器按用户 ID 或用户名组织上传文件。
  • 创建独特的年月文件夹格式: 如果默认的 YYYY/MM 格式不符合你的需求,你可以使用 upload_dir 过滤器自定义年月文件夹的格式。例如,你可以使用 YYYY-MMMM-YYYY 格式。
  • 限制上传文件类型: 虽然这不是 wp_upload_dir() 直接控制的,但你可以结合其他钩子(如 upload_mimes)和 upload_dir 过滤器,根据上传目录的不同,允许上传不同的文件类型。

五、总结:wp_upload_dir() 的重要性

wp_upload_dir() 函数是 WordPress 媒体上传的核心。它负责计算上传目录的路径和 URL,并提供了一个灵活的 upload_dir 过滤器,允许你自定义上传目录的行为。

理解 wp_upload_dir() 函数的工作原理,可以帮助你更好地管理你的媒体文件,并构建更强大的 WordPress 插件和主题。

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

发表回复

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