wp_upload_dir

The timeline below displays how wordpress function wp_upload_dir has changed across different WordPress versions. If a version is not listed, refer to the next available version below.

WordPress Version: 6.3

/**
 * Returns an array containing the current upload directory's path and URL.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array {
 *     Array of information about the upload directory.
 *
 *     @type string       $path    Base directory and subdirectory or full path to upload directory.
 *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
 *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
 *     @type string       $basedir Path without subdir.
 *     @type string       $baseurl URL path without subdir.
 *     @type string|false $error   False or error message.
 * }
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads {
     *     Array of information about the upload directory.
     *
     *     @type string       $path    Base directory and subdirectory or full path to upload directory.
     *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
     *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
     *     @type string       $basedir Path without subdir.
     *     @type string       $baseurl URL path without subdir.
     *     @type string|false $error   False or error message.
     * }
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (str_starts_with($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = wp_basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: Directory path. */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 5.5

/**
 * Returns an array containing the current upload directory's path and URL.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array {
 *     Array of information about the upload directory.
 *
 *     @type string       $path    Base directory and subdirectory or full path to upload directory.
 *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
 *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
 *     @type string       $basedir Path without subdir.
 *     @type string       $baseurl URL path without subdir.
 *     @type string|false $error   False or error message.
 * }
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads {
     *     Array of information about the upload directory.
     *
     *     @type string       $path    Base directory and subdirectory or full path to upload directory.
     *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
     *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
     *     @type string       $basedir Path without subdir.
     *     @type string       $baseurl URL path without subdir.
     *     @type string|false $error   False or error message.
     * }
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = wp_basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: Directory path. */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 5.3

/**
 * Returns an array containing the current upload directory's path and URL.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @staticvar array $cache
 * @staticvar array $tested_paths
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array {
 *     Array of information about the upload directory.
 *
 *     @type string       $path    Base directory and subdirectory or full path to upload directory.
 *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
 *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
 *     @type string       $basedir Path without subdir.
 *     @type string       $baseurl URL path without subdir.
 *     @type string|false $error   False or error message.
 * }
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads {
     *     Array of information about the upload directory.
     *
     *     @type string       $path    Base directory and subdirectory or full path to upload directory.
     *     @type string       $url     Base URL and subdirectory or absolute URL to upload directory.
     *     @type string       $subdir  Subdirectory if uploads use year/month folders option is on.
     *     @type string       $basedir Path without subdir.
     *     @type string       $baseurl URL path without subdir.
     *     @type string|false $error   False or error message.
     * }
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = wp_basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: Directory path. */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 5.2

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @staticvar array $cache
 * @staticvar array $tested_paths
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = wp_basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: directory path */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 4.9

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @staticvar array $cache
 * @staticvar array $tested_paths
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: directory path */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 4.7

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(
                    /* translators: %s: directory path */
                    __('Unable to create directory %s. Is its parent directory writable by the server?'),
                    esc_html($error_path)
                );
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 4.6

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory.
 *                           Default true for backward compatibility.
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filters the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), esc_html($error_path));
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 5.1

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory. Default true (backwards compatible).
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array(), $tested_paths = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filter the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        if (array_key_exists($path, $tested_paths)) {
            $uploads['error'] = $tested_paths[$path];
        } else {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), esc_html($error_path));
            }
            $tested_paths[$path] = $uploads['error'];
        }
    }
    return $uploads;
}

WordPress Version: 4.5

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - false or error message.
 *
 * @since 2.0.0
 * @uses _wp_upload_dir()
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @param bool   $create_dir Optional. Whether to check and create the uploads directory. Default true (backwards compatible).
 * @param bool   $refresh_cache Optional. Whether to refresh the cache. Default false.
 * @return array See above for description.
 */
function wp_upload_dir($time = null, $create_dir = true, $refresh_cache = false)
{
    static $cache = array();
    $key = sprintf('%d-%s', get_current_blog_id(), (string) $time);
    if ($refresh_cache || empty($cache[$key])) {
        $cache[$key] = _wp_upload_dir($time);
    }
    /**
     * Filter the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', $cache[$key]);
    if ($create_dir) {
        $path = $uploads['path'];
        $tested_paths = wp_cache_get('upload_dir_tested_paths');
        if (!is_array($tested_paths)) {
            $tested_paths = array();
        }
        if (!in_array($path, $tested_paths, true)) {
            if (!wp_mkdir_p($path)) {
                if (0 === strpos($uploads['basedir'], ABSPATH)) {
                    $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
                } else {
                    $error_path = basename($uploads['basedir']) . $uploads['subdir'];
                }
                $uploads['error'] = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), esc_html($error_path));
            } else {
                $tested_paths[] = $path;
                wp_cache_set('upload_dir_tested_paths', $tested_paths);
            }
        }
    }
    return $uploads;
}

WordPress Version: 4.0

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - set to false.
 *
 * @since 2.0.0
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'. Default null.
 * @return array See above for description.
 */
function wp_upload_dir($time = null)
{
    $siteurl = get_option('siteurl');
    $upload_path = trim(get_option('upload_path'));
    if (empty($upload_path) || 'wp-content/uploads' == $upload_path) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif (0 !== strpos($upload_path, ABSPATH)) {
        // $dir is absolute, $upload_path is (maybe) relative to ABSPATH
        $dir = path_join(ABSPATH, $upload_path);
    } else {
        $dir = $upload_path;
    }
    if (!$url = get_option('upload_url_path')) {
        if (empty($upload_path) || 'wp-content/uploads' == $upload_path || $upload_path == $dir) {
            $url = WP_CONTENT_URL . '/uploads';
        } else {
            $url = trailingslashit($siteurl) . $upload_path;
        }
    }
    /*
     * Honor the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
     * We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
     */
    if (defined('UPLOADS') && !(is_multisite() && get_site_option('ms_files_rewriting'))) {
        $dir = ABSPATH . UPLOADS;
        $url = trailingslashit($siteurl) . UPLOADS;
    }
    // If multisite (and if not the main site in a post-MU network)
    if (is_multisite() && !(is_main_network() && is_main_site() && defined('MULTISITE'))) {
        if (!get_site_option('ms_files_rewriting')) {
            /*
             * If ms-files rewriting is disabled (networks created post-3.5), it is fairly
             * straightforward: Append sites/%d if we're not on the main site (for post-MU
             * networks). (The extra directory prevents a four-digit ID from conflicting with
             * a year-based directory for the main site. But if a MU-era network has disabled
             * ms-files rewriting manually, they don't need the extra directory, as they never
             * had wp-content/uploads for the main site.)
             */
            if (defined('MULTISITE')) {
                $ms_dir = '/sites/' . get_current_blog_id();
            } else {
                $ms_dir = '/' . get_current_blog_id();
            }
            $dir .= $ms_dir;
            $url .= $ms_dir;
        } elseif (defined('UPLOADS') && !ms_is_switched()) {
            /*
             * Handle the old-form ms-files.php rewriting if the network still has that enabled.
             * When ms-files rewriting is enabled, then we only listen to UPLOADS when:
             * 1) We are not on the main site in a post-MU network, as wp-content/uploads is used
             *    there, and
             * 2) We are not switched, as ms_upload_constants() hardcodes these constants to reflect
             *    the original blog ID.
             *
             * Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
             * (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
             * as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
             * rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
             */
            if (defined('BLOGUPLOADDIR')) {
                $dir = untrailingslashit(BLOGUPLOADDIR);
            } else {
                $dir = ABSPATH . UPLOADS;
            }
            $url = trailingslashit($siteurl) . 'files';
        }
    }
    $basedir = $dir;
    $baseurl = $url;
    $subdir = '';
    if (get_option('uploads_use_yearmonth_folders')) {
        // Generate the yearly and monthly dirs
        if (!$time) {
            $time = current_time('mysql');
        }
        $y = substr($time, 0, 4);
        $m = substr($time, 5, 2);
        $subdir = "/{$y}/{$m}";
    }
    $dir .= $subdir;
    $url .= $subdir;
    /**
     * Filter the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', array('path' => $dir, 'url' => $url, 'subdir' => $subdir, 'basedir' => $basedir, 'baseurl' => $baseurl, 'error' => false));
    // Make sure we have an uploads directory.
    if (!wp_mkdir_p($uploads['path'])) {
        if (0 === strpos($uploads['basedir'], ABSPATH)) {
            $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
        } else {
            $error_path = basename($uploads['basedir']) . $uploads['subdir'];
        }
        $message = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), $error_path);
        $uploads['error'] = $message;
    }
    return $uploads;
}

WordPress Version: 3.9

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - set to false.
 *
 * @since 2.0.0
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'.
 * @return array See above for description.
 */
function wp_upload_dir($time = null)
{
    $siteurl = get_option('siteurl');
    $upload_path = trim(get_option('upload_path'));
    if (empty($upload_path) || 'wp-content/uploads' == $upload_path) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif (0 !== strpos($upload_path, ABSPATH)) {
        // $dir is absolute, $upload_path is (maybe) relative to ABSPATH
        $dir = path_join(ABSPATH, $upload_path);
    } else {
        $dir = $upload_path;
    }
    if (!$url = get_option('upload_url_path')) {
        if (empty($upload_path) || 'wp-content/uploads' == $upload_path || $upload_path == $dir) {
            $url = WP_CONTENT_URL . '/uploads';
        } else {
            $url = trailingslashit($siteurl) . $upload_path;
        }
    }
    // Obey the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
    // We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
    if (defined('UPLOADS') && !(is_multisite() && get_site_option('ms_files_rewriting'))) {
        $dir = ABSPATH . UPLOADS;
        $url = trailingslashit($siteurl) . UPLOADS;
    }
    // If multisite (and if not the main site in a post-MU network)
    if (is_multisite() && !(is_main_network() && is_main_site() && defined('MULTISITE'))) {
        if (!get_site_option('ms_files_rewriting')) {
            // If ms-files rewriting is disabled (networks created post-3.5), it is fairly straightforward:
            // Append sites/%d if we're not on the main site (for post-MU networks). (The extra directory
            // prevents a four-digit ID from conflicting with a year-based directory for the main site.
            // But if a MU-era network has disabled ms-files rewriting manually, they don't need the extra
            // directory, as they never had wp-content/uploads for the main site.)
            if (defined('MULTISITE')) {
                $ms_dir = '/sites/' . get_current_blog_id();
            } else {
                $ms_dir = '/' . get_current_blog_id();
            }
            $dir .= $ms_dir;
            $url .= $ms_dir;
        } elseif (defined('UPLOADS') && !ms_is_switched()) {
            // Handle the old-form ms-files.php rewriting if the network still has that enabled.
            // When ms-files rewriting is enabled, then we only listen to UPLOADS when:
            //   1) we are not on the main site in a post-MU network,
            //      as wp-content/uploads is used there, and
            //   2) we are not switched, as ms_upload_constants() hardcodes
            //      these constants to reflect the original blog ID.
            //
            // Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
            // (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
            // as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
            // rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
            if (defined('BLOGUPLOADDIR')) {
                $dir = untrailingslashit(BLOGUPLOADDIR);
            } else {
                $dir = ABSPATH . UPLOADS;
            }
            $url = trailingslashit($siteurl) . 'files';
        }
    }
    $basedir = $dir;
    $baseurl = $url;
    $subdir = '';
    if (get_option('uploads_use_yearmonth_folders')) {
        // Generate the yearly and monthly dirs
        if (!$time) {
            $time = current_time('mysql');
        }
        $y = substr($time, 0, 4);
        $m = substr($time, 5, 2);
        $subdir = "/{$y}/{$m}";
    }
    $dir .= $subdir;
    $url .= $subdir;
    /**
     * Filter the uploads directory data.
     *
     * @since 2.0.0
     *
     * @param array $uploads Array of upload directory data with keys of 'path',
     *                       'url', 'subdir, 'basedir', and 'error'.
     */
    $uploads = apply_filters('upload_dir', array('path' => $dir, 'url' => $url, 'subdir' => $subdir, 'basedir' => $basedir, 'baseurl' => $baseurl, 'error' => false));
    // Make sure we have an uploads dir
    if (!wp_mkdir_p($uploads['path'])) {
        if (0 === strpos($uploads['basedir'], ABSPATH)) {
            $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
        } else {
            $error_path = basename($uploads['basedir']) . $uploads['subdir'];
        }
        $message = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), $error_path);
        $uploads['error'] = $message;
    }
    return $uploads;
}

WordPress Version: 3.7

/**
 * Get an array containing the current upload directory's path and url.
 *
 * Checks the 'upload_path' option, which should be from the web root folder,
 * and if it isn't empty it will be used. If it is empty, then the path will be
 * 'WP_CONTENT_DIR/uploads'. If the 'UPLOADS' constant is defined, then it will
 * override the 'upload_path' option and 'WP_CONTENT_DIR/uploads' path.
 *
 * The upload URL path is set either by the 'upload_url_path' option or by using
 * the 'WP_CONTENT_URL' constant and appending '/uploads' to the path.
 *
 * If the 'uploads_use_yearmonth_folders' is set to true (checkbox if checked in
 * the administration settings panel), then the time will be used. The format
 * will be year first and then month.
 *
 * If the path couldn't be created, then an error will be returned with the key
 * 'error' containing the error message. The error suggests that the parent
 * directory is not writable by the server.
 *
 * On success, the returned array will have many indices:
 * 'path' - base directory and sub directory or full path to upload directory.
 * 'url' - base url and sub directory or absolute URL to upload directory.
 * 'subdir' - sub directory if uploads use year/month folders option is on.
 * 'basedir' - path without subdir.
 * 'baseurl' - URL path without subdir.
 * 'error' - set to false.
 *
 * @since 2.0.0
 * @uses apply_filters() Calls 'upload_dir' on returned array.
 *
 * @param string $time Optional. Time formatted in 'yyyy/mm'.
 * @return array See above for description.
 */
function wp_upload_dir($time = null)
{
    $siteurl = get_option('siteurl');
    $upload_path = trim(get_option('upload_path'));
    if (empty($upload_path) || 'wp-content/uploads' == $upload_path) {
        $dir = WP_CONTENT_DIR . '/uploads';
    } elseif (0 !== strpos($upload_path, ABSPATH)) {
        // $dir is absolute, $upload_path is (maybe) relative to ABSPATH
        $dir = path_join(ABSPATH, $upload_path);
    } else {
        $dir = $upload_path;
    }
    if (!$url = get_option('upload_url_path')) {
        if (empty($upload_path) || 'wp-content/uploads' == $upload_path || $upload_path == $dir) {
            $url = WP_CONTENT_URL . '/uploads';
        } else {
            $url = trailingslashit($siteurl) . $upload_path;
        }
    }
    // Obey the value of UPLOADS. This happens as long as ms-files rewriting is disabled.
    // We also sometimes obey UPLOADS when rewriting is enabled -- see the next block.
    if (defined('UPLOADS') && !(is_multisite() && get_site_option('ms_files_rewriting'))) {
        $dir = ABSPATH . UPLOADS;
        $url = trailingslashit($siteurl) . UPLOADS;
    }
    // If multisite (and if not the main site in a post-MU network)
    if (is_multisite() && !(is_main_network() && is_main_site() && defined('MULTISITE'))) {
        if (!get_site_option('ms_files_rewriting')) {
            // If ms-files rewriting is disabled (networks created post-3.5), it is fairly straightforward:
            // Append sites/%d if we're not on the main site (for post-MU networks). (The extra directory
            // prevents a four-digit ID from conflicting with a year-based directory for the main site.
            // But if a MU-era network has disabled ms-files rewriting manually, they don't need the extra
            // directory, as they never had wp-content/uploads for the main site.)
            if (defined('MULTISITE')) {
                $ms_dir = '/sites/' . get_current_blog_id();
            } else {
                $ms_dir = '/' . get_current_blog_id();
            }
            $dir .= $ms_dir;
            $url .= $ms_dir;
        } elseif (defined('UPLOADS') && !ms_is_switched()) {
            // Handle the old-form ms-files.php rewriting if the network still has that enabled.
            // When ms-files rewriting is enabled, then we only listen to UPLOADS when:
            //   1) we are not on the main site in a post-MU network,
            //      as wp-content/uploads is used there, and
            //   2) we are not switched, as ms_upload_constants() hardcodes
            //      these constants to reflect the original blog ID.
            //
            // Rather than UPLOADS, we actually use BLOGUPLOADDIR if it is set, as it is absolute.
            // (And it will be set, see ms_upload_constants().) Otherwise, UPLOADS can be used, as
            // as it is relative to ABSPATH. For the final piece: when UPLOADS is used with ms-files
            // rewriting in multisite, the resulting URL is /files. (#WP22702 for background.)
            if (defined('BLOGUPLOADDIR')) {
                $dir = untrailingslashit(BLOGUPLOADDIR);
            } else {
                $dir = ABSPATH . UPLOADS;
            }
            $url = trailingslashit($siteurl) . 'files';
        }
    }
    $basedir = $dir;
    $baseurl = $url;
    $subdir = '';
    if (get_option('uploads_use_yearmonth_folders')) {
        // Generate the yearly and monthly dirs
        if (!$time) {
            $time = current_time('mysql');
        }
        $y = substr($time, 0, 4);
        $m = substr($time, 5, 2);
        $subdir = "/{$y}/{$m}";
    }
    $dir .= $subdir;
    $url .= $subdir;
    $uploads = apply_filters('upload_dir', array('path' => $dir, 'url' => $url, 'subdir' => $subdir, 'basedir' => $basedir, 'baseurl' => $baseurl, 'error' => false));
    // Make sure we have an uploads dir
    if (!wp_mkdir_p($uploads['path'])) {
        if (0 === strpos($uploads['basedir'], ABSPATH)) {
            $error_path = str_replace(ABSPATH, '', $uploads['basedir']) . $uploads['subdir'];
        } else {
            $error_path = basename($uploads['basedir']) . $uploads['subdir'];
        }
        $message = sprintf(__('Unable to create directory %s. Is its parent directory writable by the server?'), $error_path);
        $uploads['error'] = $message;
    }
    return $uploads;
}