Handles PHP uploads in WordPress.
Description
Sanitizes file names, checks extensions for mime type, and moves the file to the appropriate directory within the uploads directory.
See also
Parameters
$file
arrayrequired- Reference to a single element from
$_FILES
. Call the function once for each uploaded file.
name
stringThe original name of the file on the client machine.type
stringThe mime type of the file, if the browser provided this information.tmp_name
stringThe temporary filename of the file in which the uploaded file was stored on the server.size
intThe size, in bytes, of the uploaded file.error
intThe error code associated with this file upload.
$overrides
array|falserequired- An array of override parameters for this file, or boolean false if none are provided.
upload_error_handler
callableFunction to call when there is an error during the upload process.
See wp_handle_upload_error().unique_filename_callback
callableFunction to call when determining a unique file name for the file.
See wp_unique_filename().upload_error_strings
string[]The strings that describe the error indicated in$_FILES[{form field}]['error']
.test_form
boolWhether to test that the$_POST['action']
parameter is as expected.test_size
boolWhether to test that the file size is greater than zero bytes.test_type
boolWhether to test that the mime type of the file is as expected.mimes
string[]Array of allowed mime types keyed by their file extension regex.
$time
stringrequired- Time formatted in
'yyyy/mm'
. $action
stringrequired- Expected value for
$_POST['action']
.
Source
function _wp_handle_upload( &$file, $overrides, $time, $action ) {
// The default error handler.
if ( ! function_exists( 'wp_handle_upload_error' ) ) {
function wp_handle_upload_error( &$file, $message ) {
return array( 'error' => $message );
}
}
/**
* Filters the data for a file before it is uploaded to WordPress.
*
* The dynamic portion of the hook name, `$action`, refers to the post action.
*
* Possible hook names include:
*
* - `wp_handle_sideload_prefilter`
* - `wp_handle_upload_prefilter`
*
* @since 2.9.0 as 'wp_handle_upload_prefilter'.
* @since 4.0.0 Converted to a dynamic hook with `$action`.
*
* @param array $file {
* Reference to a single element from `$_FILES`.
*
* @type string $name The original name of the file on the client machine.
* @type string $type The mime type of the file, if the browser provided this information.
* @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
* @type int $size The size, in bytes, of the uploaded file.
* @type int $error The error code associated with this file upload.
* }
*/
$file = apply_filters( "{$action}_prefilter", $file );
/**
* Filters the override parameters for a file before it is uploaded to WordPress.
*
* The dynamic portion of the hook name, `$action`, refers to the post action.
*
* Possible hook names include:
*
* - `wp_handle_sideload_overrides`
* - `wp_handle_upload_overrides`
*
* @since 5.7.0
*
* @param array|false $overrides An array of override parameters for this file. Boolean false if none are
* provided. See _wp_handle_upload().
* @param array $file {
* Reference to a single element from `$_FILES`.
*
* @type string $name The original name of the file on the client machine.
* @type string $type The mime type of the file, if the browser provided this information.
* @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
* @type int $size The size, in bytes, of the uploaded file.
* @type int $error The error code associated with this file upload.
* }
*/
$overrides = apply_filters( "{$action}_overrides", $overrides, $file );
// You may define your own function and pass the name in $overrides['upload_error_handler'].
$upload_error_handler = 'wp_handle_upload_error';
if ( isset( $overrides['upload_error_handler'] ) ) {
$upload_error_handler = $overrides['upload_error_handler'];
}
// You may have had one or more 'wp_handle_upload_prefilter' functions error out the file. Handle that gracefully.
if ( isset( $file['error'] ) && ! is_numeric( $file['error'] ) && $file['error'] ) {
return call_user_func_array( $upload_error_handler, array( &$file, $file['error'] ) );
}
// Install user overrides. Did we mention that this voids your warranty?
// You may define your own function and pass the name in $overrides['unique_filename_callback'].
$unique_filename_callback = null;
if ( isset( $overrides['unique_filename_callback'] ) ) {
$unique_filename_callback = $overrides['unique_filename_callback'];
}
/*
* This may not have originally been intended to be overridable,
* but historically has been.
*/
if ( isset( $overrides['upload_error_strings'] ) ) {
$upload_error_strings = $overrides['upload_error_strings'];
} else {
// Courtesy of php.net, the strings that describe the error indicated in $_FILES[{form field}]['error'].
$upload_error_strings = array(
false,
sprintf(
/* translators: 1: upload_max_filesize, 2: php.ini */
__( 'The uploaded file exceeds the %1$s directive in %2$s.' ),
'upload_max_filesize',
'php.ini'
),
sprintf(
/* translators: %s: MAX_FILE_SIZE */
__( 'The uploaded file exceeds the %s directive that was specified in the HTML form.' ),
'MAX_FILE_SIZE'
),
__( 'The uploaded file was only partially uploaded.' ),
__( 'No file was uploaded.' ),
'',
__( 'Missing a temporary folder.' ),
__( 'Failed to write file to disk.' ),
__( 'File upload stopped by extension.' ),
);
}
// All tests are on by default. Most can be turned off by $overrides[{test_name}] = false;
$test_form = isset( $overrides['test_form'] ) ? $overrides['test_form'] : true;
$test_size = isset( $overrides['test_size'] ) ? $overrides['test_size'] : true;
// If you override this, you must provide $ext and $type!!
$test_type = isset( $overrides['test_type'] ) ? $overrides['test_type'] : true;
$mimes = isset( $overrides['mimes'] ) ? $overrides['mimes'] : null;
// A correct form post will pass this test.
if ( $test_form && ( ! isset( $_POST['action'] ) || $_POST['action'] !== $action ) ) {
return call_user_func_array( $upload_error_handler, array( &$file, __( 'Invalid form submission.' ) ) );
}
// A successful upload will pass this test. It makes no sense to override this one.
if ( isset( $file['error'] ) && $file['error'] > 0 ) {
return call_user_func_array( $upload_error_handler, array( &$file, $upload_error_strings[ $file['error'] ] ) );
}
// A properly uploaded file will pass this test. There should be no reason to override this one.
$test_uploaded_file = 'wp_handle_upload' === $action ? is_uploaded_file( $file['tmp_name'] ) : @is_readable( $file['tmp_name'] );
if ( ! $test_uploaded_file ) {
return call_user_func_array( $upload_error_handler, array( &$file, __( 'Specified file failed upload test.' ) ) );
}
$test_file_size = 'wp_handle_upload' === $action ? $file['size'] : filesize( $file['tmp_name'] );
// A non-empty file will pass this test.
if ( $test_size && ! ( $test_file_size > 0 ) ) {
if ( is_multisite() ) {
$error_msg = __( 'File is empty. Please upload something more substantial.' );
} else {
$error_msg = sprintf(
/* translators: 1: php.ini, 2: post_max_size, 3: upload_max_filesize */
__( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your %1$s file or by %2$s being defined as smaller than %3$s in %1$s.' ),
'php.ini',
'post_max_size',
'upload_max_filesize'
);
}
return call_user_func_array( $upload_error_handler, array( &$file, $error_msg ) );
}
// A correct MIME type will pass this test. Override $mimes or use the upload_mimes filter.
if ( $test_type ) {
$wp_filetype = wp_check_filetype_and_ext( $file['tmp_name'], $file['name'], $mimes );
$ext = empty( $wp_filetype['ext'] ) ? '' : $wp_filetype['ext'];
$type = empty( $wp_filetype['type'] ) ? '' : $wp_filetype['type'];
$proper_filename = empty( $wp_filetype['proper_filename'] ) ? '' : $wp_filetype['proper_filename'];
// Check to see if wp_check_filetype_and_ext() determined the filename was incorrect.
if ( $proper_filename ) {
$file['name'] = $proper_filename;
}
if ( ( ! $type || ! $ext ) && ! current_user_can( 'unfiltered_upload' ) ) {
return call_user_func_array( $upload_error_handler, array( &$file, __( 'Sorry, you are not allowed to upload this file type.' ) ) );
}
if ( ! $type ) {
$type = $file['type'];
}
} else {
$type = '';
}
/*
* A writable uploads dir will pass this test. Again, there's no point
* overriding this one.
*/
$uploads = wp_upload_dir( $time );
if ( ! ( $uploads && false === $uploads['error'] ) ) {
return call_user_func_array( $upload_error_handler, array( &$file, $uploads['error'] ) );
}
$filename = wp_unique_filename( $uploads['path'], $file['name'], $unique_filename_callback );
// Move the file to the uploads dir.
$new_file = $uploads['path'] . "/$filename";
/**
* Filters whether to short-circuit moving the uploaded file after passing all checks.
*
* If a non-null value is returned from the filter, moving the file and any related
* error reporting will be completely skipped.
*
* @since 4.9.0
*
* @param mixed $move_new_file If null (default) move the file after the upload.
* @param array $file {
* Reference to a single element from `$_FILES`.
*
* @type string $name The original name of the file on the client machine.
* @type string $type The mime type of the file, if the browser provided this information.
* @type string $tmp_name The temporary filename of the file in which the uploaded file was stored on the server.
* @type int $size The size, in bytes, of the uploaded file.
* @type int $error The error code associated with this file upload.
* }
* @param string $new_file Filename of the newly-uploaded file.
* @param string $type Mime type of the newly-uploaded file.
*/
$move_new_file = apply_filters( 'pre_move_uploaded_file', null, $file, $new_file, $type );
if ( null === $move_new_file ) {
if ( 'wp_handle_upload' === $action ) {
$move_new_file = @move_uploaded_file( $file['tmp_name'], $new_file );
} else {
// Use copy and unlink because rename breaks streams.
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$move_new_file = @copy( $file['tmp_name'], $new_file );
unlink( $file['tmp_name'] );
}
if ( false === $move_new_file ) {
if ( str_starts_with( $uploads['basedir'], ABSPATH ) ) {
$error_path = str_replace( ABSPATH, '', $uploads['basedir'] ) . $uploads['subdir'];
} else {
$error_path = basename( $uploads['basedir'] ) . $uploads['subdir'];
}
return $upload_error_handler(
$file,
sprintf(
/* translators: %s: Destination file path. */
__( 'The uploaded file could not be moved to %s.' ),
$error_path
)
);
}
}
// Set correct file permissions.
$stat = stat( dirname( $new_file ) );
$perms = $stat['mode'] & 0000666;
chmod( $new_file, $perms );
// Compute the URL.
$url = $uploads['url'] . "/$filename";
if ( is_multisite() ) {
clean_dirsize_cache( $new_file );
}
/**
* Filters the data array for the uploaded file.
*
* @since 2.1.0
*
* @param array $upload {
* Array of upload data.
*
* @type string $file Filename of the newly-uploaded file.
* @type string $url URL of the newly-uploaded file.
* @type string $type Mime type of the newly-uploaded file.
* }
* @param string $context The type of upload action. Values include 'upload' or 'sideload'.
*/
return apply_filters(
'wp_handle_upload',
array(
'file' => $new_file,
'url' => $url,
'type' => $type,
),
'wp_handle_sideload' === $action ? 'sideload' : 'upload'
);
}
Hooks
- apply_filters( ‘pre_move_uploaded_file’,
mixed $move_new_file ,array $file ,string $new_file ,string $type ) Filters whether to short-circuit moving the uploaded file after passing all checks.
- apply_filters( ‘wp_handle_upload’,
array $upload ,string $context ) Filters the data array for the uploaded file.
- apply_filters( “{$action}_overrides”,
array|false $overrides ,array $file ) Filters the override parameters for a file before it is uploaded to WordPress.
- apply_filters( “{$action}_prefilter”,
array $file ) Filters the data for a file before it is uploaded to WordPress.
Changelog
Version | Description |
---|---|
4.0.0 | Introduced. |
User Contributed Notes
You must log in before being able to contribute a note or feedback.