深入理解 WordPress `wp_upload_dir()` 函数的源码:如何根据配置和日期动态生成上传目录路径。

各位,今天咱们来聊聊WordPress里一个看似简单,实则暗藏玄机的函数:wp_upload_dir()。 它负责告诉我们,文件应该往哪儿上传,上传后的网址是啥。 别看它名字平平无奇,背后可是藏着一套逻辑严谨的目录生成和路径拼接的机制。 准备好了吗? 咱们这就深入源码,把它的底裤都扒下来,看看它到底是怎么运作的!

开场白:与wp_upload_dir()的第一次亲密接触

想象一下,你正在开发一个WordPress插件,需要上传一些用户头像。 你肯定不想把这些头像直接扔到WordPress的根目录,对吧? 这时候,wp_upload_dir()就派上用场了。 它能帮你找到合适的上传目录,并且返回一个包含各种路径信息的数组。

简单来说,wp_upload_dir()就是WordPress的文件上传“导航员”。

源码解读:一层一层地揭开wp_upload_dir()的神秘面纱

好了,废话不多说,直接上代码! (以下代码基于WordPress 6.4.3,不同版本可能会有细微差异,但核心逻辑不变。)

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

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

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

    if ( is_multisite() ) {
        if ( ! isset( $switched ) ) {
            $ms_files_rewriting = (bool) get_site_option( 'ms_files_rewriting' );
        } else {
            $ms_files_rewriting = $switched ? false : (bool) get_site_option( 'ms_files_rewriting' );
        }
    }

    if ( empty( $upload_path ) || 'wp-content/uploads' == $upload_path || ( isset( $ms_files_rewriting ) && ! $ms_files_rewriting ) ) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } else {
        $dir = ABSPATH . $upload_path;
    }

    $url = str_replace( WP_CONTENT_DIR, content_url(), $dir );

    if ( defined( 'UPLOADS' ) && false === strpos( $dir, WP_CONTENT_DIR ) ) {
        $dir = ABSPATH . UPLOADS;
        $url = $siteurl . '/' . UPLOADS;
    }

    if ( is_ssl() && ! is_admin() && strpos( $url, 'https' ) === false ) {
        $url = str_replace( 'http://', 'https://', $url );
    }

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

    $upload_dir = $upload['path'];
    $upload_url = $upload['url'];

    if ( ! empty( $time ) ) {
        $time = preg_replace( '/[^0-9]/', '', $time );
        if ( strlen( $time ) == 4 ) {
            $time = $time . '/00';
        }
        $time = gmdate( 'Y/m', strtotime( $time ) );
    }

    if ( ! empty( $time ) ) {
        $upload_dir .= '/' . $time;
        $upload_url .= '/' . $time;
        $upload['subdir'] = '/' . $time;
    }

    $upload['path'] = $upload_dir;
    $upload['url'] = $upload_url;

    if ( ( ( ! is_dir( $upload_dir ) ) && $create_dir ) ) {
        $old_umask = umask( 0 );

        if ( wp_mkdir_p( $upload_dir ) ) {
            $error = false;
            if ( get_site_option( 'upload_filetypes' ) ) {
                // Set correct file permissions on upload folder, if possible.
                $perms = fileperms( dirname( $upload_dir ) );
                if ( $perms ) {
                    chmod( $upload_dir, $perms & 0777 | 0775 );
                }
            }
        } else {
            $upload['error'] = sprintf(
                /* translators: %s: Directory path. */
                __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
                esc_html( $upload_dir )
            );
            $error           = true;
        }

        umask( $old_umask );
    }

    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $upload Array of upload directory data with keys of 'path',
     *                      'url', 'subdir', 'basedir', and 'baseurl'.
     * @param string $context The context for the filter. Possible values are
     *                        'upload_dir', 'wp_handle_upload', and 'wp_handle_sideload'.
     */
    return apply_filters( 'wp_upload_dir', $upload, 'upload_dir' );
}

是不是感觉有点头大? 别怕,咱们一步一步来分析。

1. 获取基本配置信息

首先,函数会获取一些重要的配置信息,包括:

  • siteurl: WordPress的站点URL,通常是你的域名。
  • upload_path: 上传目录的相对路径。 这个选项可以在WordPress后台的“设置 -> 媒体”中配置。 如果没有配置,或者配置为默认值 wp-content/uploads,那么上传目录就会位于 wp-content/uploads 下。
$siteurl = get_option( 'siteurl' );
$upload_path = trim( get_option( 'upload_path' ) );

2. 多站点判断( Multisite )

如果是多站点环境,还会判断是否启用了 ms_files_rewriting 。 这个选项决定了文件是如何存储和访问的。 简单来说,如果启用了,每个站点的文件都会存储在不同的子目录中;如果没有启用,所有站点的文件都会存储在同一个目录中。

if ( is_multisite() ) {
    if ( ! isset( $switched ) ) {
        $ms_files_rewriting = (bool) get_site_option( 'ms_files_rewriting' );
    } else {
        $ms_files_rewriting = $switched ? false : (bool) get_site_option( 'ms_files_rewriting' );
    }
}

3. 确定上传目录的基本路径

接下来,函数会根据 upload_pathms_files_rewriting 的值,确定上传目录的基本路径。

  • 如果 upload_path 为空,或者为默认值 wp-content/uploads,或者在多站点环境下 ms_files_rewritingfalse,那么上传目录的基本路径就是 WP_CONTENT_DIR . '/uploads'
  • 否则,上传目录的基本路径就是 ABSPATH . $upload_path
if ( empty( $upload_path ) || 'wp-content/uploads' == $upload_path || ( isset( $ms_files_rewriting ) && ! $ms_files_rewriting ) ) {
    $dir = WP_CONTENT_DIR . '/uploads';
} else {
    $dir = ABSPATH . $upload_path;
}

$url = str_replace( WP_CONTENT_DIR, content_url(), $dir );

这里需要注意的是:

  • WP_CONTENT_DIRwp-content 目录的绝对路径。
  • ABSPATH 是 WordPress 根目录的绝对路径。
  • content_url() 函数返回 wp-content 目录的 URL。

4. 使用 UPLOADS 常量覆盖默认路径

如果定义了 UPLOADS 常量,并且上传目录不在 wp-content 目录下,那么就使用 UPLOADS 常量来覆盖之前的设置。

if ( defined( 'UPLOADS' ) && false === strpos( $dir, WP_CONTENT_DIR ) ) {
    $dir = ABSPATH . UPLOADS;
    $url = $siteurl . '/' . UPLOADS;
}

这个常量允许开发者将上传目录放在 WordPress 根目录之外,提供更大的灵活性。

5. 处理 SSL 连接

如果当前连接是 SSL 连接( HTTPS ),并且不在后台管理界面,并且 URL 中没有 https, 那么就将 URL 中的 http 替换为 https

if ( is_ssl() && ! is_admin() && strpos( $url, 'https' ) === false ) {
    $url = str_replace( 'http://', 'https://', $url );
}

6. 应用 upload_dir 过滤器

到目前为止,我们已经确定了上传目录的基本路径和 URL。 但是,WordPress允许我们通过 upload_dir 过滤器来修改这些值。

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

这个过滤器接收一个数组作为参数,该数组包含了以下键:

  • path: 上传目录的绝对路径。
  • url: 上传目录的 URL。
  • subdir: 子目录的相对路径(相对于 basedir)。
  • basedir: 上传目录的基本路径。
  • baseurl: 上传目录的基本 URL。
  • error: 错误信息。

通过使用这个过滤器,你可以完全控制上传目录的位置和 URL。

7. 处理时间参数($time

wp_upload_dir() 函数允许我们传递一个时间参数 $time,用来指定上传目录的子目录。 如果传递了 $time 参数,函数会根据这个时间生成一个形如 YYYY/MM 的子目录。

if ( ! empty( $time ) ) {
    $time = preg_replace( '/[^0-9]/', '', $time );
    if ( strlen( $time ) == 4 ) {
        $time = $time . '/00';
    }
    $time = gmdate( 'Y/m', strtotime( $time ) );
}

if ( ! empty( $time ) ) {
    $upload_dir .= '/' . $time;
    $upload_url .= '/' . $time;
    $upload['subdir'] = '/' . $time;
}

这里有几个需要注意的地方:

  • preg_replace( '/[^0-9]/', '', $time ) 会移除 $time 参数中所有非数字字符。
  • 如果 $time 参数的长度为 4,那么就假设它代表年份,并在后面添加 /00,表示 1 月份。
  • gmdate( 'Y/m', strtotime( $time ) ) 会将 $time 参数转换为 YYYY/MM 格式的字符串。

8. 创建目录

如果上传目录不存在,并且 $create_dir 参数为 true(默认值),那么函数会尝试创建该目录。

if ( ( ( ! is_dir( $upload_dir ) ) && $create_dir ) ) {
    $old_umask = umask( 0 );

    if ( wp_mkdir_p( $upload_dir ) ) {
        $error = false;
        if ( get_site_option( 'upload_filetypes' ) ) {
            // Set correct file permissions on upload folder, if possible.
            $perms = fileperms( dirname( $upload_dir ) );
            if ( $perms ) {
                chmod( $upload_dir, $perms & 0777 | 0775 );
            }
        }
    } else {
        $upload['error'] = sprintf(
            /* translators: %s: Directory path. */
            __( 'Unable to create directory %s. Is its parent directory writable by the server?' ),
            esc_html( $upload_dir )
        );
        $error           = true;
    }

    umask( $old_umask );
}

这里使用了 wp_mkdir_p() 函数来递归创建目录。 这个函数可以确保所有父目录都存在,即使它们不存在,也会被创建。

同时,还会尝试设置上传目录的权限,以确保 Web 服务器可以写入该目录。

9. 应用 wp_upload_dir 过滤器

最后,函数会再次应用一个过滤器:wp_upload_dir

/**
 * Filters the uploads directory data.
 *
 * @since 2.0.0
 *
 * @param array $upload Array of upload directory data with keys of 'path',
 *                      'url', 'subdir', 'basedir', and 'baseurl'.
 * @param string $context The context for the filter. Possible values are
 *                        'upload_dir', 'wp_handle_upload', and 'wp_handle_sideload'.
 */
return apply_filters( 'wp_upload_dir', $upload, 'upload_dir' );

这个过滤器与之前的 upload_dir 过滤器类似,但是它是在创建目录之后应用的。 这意味着你可以在这里修改最终的上传目录信息,例如,你可以根据目录创建的结果来设置错误信息。

10. 返回结果

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

  • path: 上传目录的绝对路径。
  • url: 上传目录的 URL。
  • subdir: 子目录的相对路径(相对于 basedir)。
  • basedir: 上传目录的基本路径。
  • baseurl: 上传目录的基本 URL。
  • error: 错误信息。

案例分析:wp_upload_dir() 的实际应用

为了更好地理解 wp_upload_dir() 的用法,我们来看几个实际的案例。

案例 1:使用默认配置

如果你的 WordPress 站点使用默认的上传配置,那么 wp_upload_dir() 函数会返回以下信息:

path /path/to/wp-content/uploads
url http://yourdomain.com/wp-content/uploads
subdir
basedir /path/to/wp-content/uploads
baseurl http://yourdomain.com/wp-content/uploads
error false

案例 2:自定义上传目录

如果你在 WordPress 后台的“设置 -> 媒体”中,将上传目录设置为 files,那么 wp_upload_dir() 函数会返回以下信息:

path /path/to/wordpress/files
url http://yourdomain.com/files
subdir
basedir /path/to/wordpress/files
baseurl http://yourdomain.com/files
error false

案例 3:使用时间参数

如果你调用 wp_upload_dir( '2023-10-27' ),那么 wp_upload_dir() 函数会返回以下信息:

path /path/to/wp-content/uploads/2023/10
url http://yourdomain.com/wp-content/uploads/2023/10
subdir /2023/10
basedir /path/to/wp-content/uploads
baseurl http://yourdomain.com/wp-content/uploads
error false

案例 4:使用过滤器修改上传目录

假设你想将所有上传文件都存储在 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;
}

这段代码会将 upload_dir 过滤器的 basedirbaseurl 属性修改为 wp-content/my-uploads 目录,从而实现自定义上传目录的目的。

总结:wp_upload_dir() 的核心要点

  • wp_upload_dir() 函数负责确定 WordPress 的上传目录。
  • 它会考虑多种因素,包括站点配置、多站点环境、UPLOADS 常量和时间参数。
  • 你可以使用 upload_dirwp_upload_dir 过滤器来修改上传目录的信息。
  • 该函数会尝试创建上传目录,并设置正确的权限。
  • 最终,它会返回一个包含上传目录信息的数组。

最后的话:掌握wp_upload_dir(),成为文件上传大师!

好了,今天的讲座就到这里。 希望通过这次深入的源码解读和案例分析,你已经对 wp_upload_dir() 函数有了更深刻的理解。 掌握了这个函数,你就可以灵活地控制 WordPress 的文件上传行为,更好地满足你的开发需求。 记住,编程就像剥洋葱,一层一层地剥开,才能看到最核心的东西。 下次再见!

发表回复

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