Sideloads an external image from a URL into the media library.
Description
Downloads the remote file on the server, avoiding a cross-origin browser fetch that fails under cross-origin isolation. Whether sub-sizes are generated is governed by the filters applied in create_item().
Parameters
$requestWP_REST_Requestrequired- Full details about the request.
Source
protected function create_item_from_url( WP_REST_Request $request ) {
// Sideloading downloads and stores a file, so require the upload capability.
if ( ! current_user_can( 'upload_files' ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to upload media on this site.' ),
array( 'status' => rest_authorization_required_code() )
);
}
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/image.php';
$url = $request['url'];
$post_id = ! empty( $request['post'] ) ? (int) $request['post'] : 0;
// Derive the filename from the URL path before downloading anything.
$url_path = wp_parse_url( $url, PHP_URL_PATH );
$filename = $url_path ? wp_basename( $url_path ) : '';
if ( '' === $filename ) {
return new WP_Error(
'rest_invalid_url',
__( 'Could not determine a filename from the provided URL.' ),
array( 'status' => 400 )
);
}
/*
* Only download URLs whose extension maps to an allowed image MIME type.
* The sideload handler would reject other types anyway (via
* wp_check_filetype_and_ext()), but checking first avoids downloading
* files that can never be accepted, such as PHP scripts.
*/
$filetype = wp_check_filetype( $filename );
if ( ! $filetype['type'] || ! str_starts_with( $filetype['type'], 'image/' ) ) {
return new WP_Error(
'rest_invalid_url',
__( 'The provided URL does not point to a supported image file.' ),
array( 'status' => 400 )
);
}
/*
* Cap the download at the same size the site would accept as a direct
* upload. check_upload_size() only applies on multisite, so without a
* ceiling here a single site has no limit at all on this path: the
* `upload_max_filesize` and `post_max_size` directives bound a request
* body, not a fetch the server makes itself.
*
* When `wp_max_upload_size` returns 0, no ceiling is applied.
*/
$max_size = (int) wp_max_upload_size();
/*
* Download the remote file with WordPress's HTTP API, which validates
* the host and blocks requests to private or local addresses. This is
* the same primitive core's media_sideload_image() relies on.
*
* `limit_response_size` stops the transfer once the limit is passed,
* so an oversized remote file is never written to disk in full. One
* byte over the ceiling is enough to fail the size check below.
*/
$limit_response_size = static function ( $args ) use ( $max_size ) {
$args['limit_response_size'] = $max_size + 1;
return $args;
};
if ( $max_size > 0 ) {
add_filter( 'http_request_args', $limit_response_size );
}
$tmp_file = download_url( $url );
if ( $max_size > 0 ) {
remove_filter( 'http_request_args', $limit_response_size );
}
if ( is_wp_error( $tmp_file ) ) {
return $tmp_file;
}
$file_array = array(
'name' => $filename,
'tmp_name' => $tmp_file,
);
$size_check = self::check_upload_size( $file_array );
if ( is_wp_error( $size_check ) ) {
if ( file_exists( $tmp_file ) ) {
wp_delete_file( $tmp_file );
}
return $size_check;
}
if ( $max_size > 0 && wp_filesize( $tmp_file ) > $max_size ) {
if ( file_exists( $tmp_file ) ) {
wp_delete_file( $tmp_file );
}
return new WP_Error(
'rest_upload_file_too_big',
/* translators: %s: Maximum allowed file size in kilobytes. */
sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), number_format( $max_size / KB_IN_BYTES ) ),
array( 'status' => 400 )
);
}
$attachment_id = media_handle_sideload( $file_array, $post_id );
if ( is_wp_error( $attachment_id ) ) {
/*
* media_handle_sideload() deletes the temp file on success; remove
* it explicitly when the sideload fails.
*/
if ( file_exists( $tmp_file ) ) {
wp_delete_file( $tmp_file );
}
return $attachment_id;
}
$attachment = get_post( $attachment_id );
$request->set_param( 'context', 'edit' );
/*
* media_handle_sideload() fires the standard insert hooks (including
* wp_after_insert_post), but not the REST-specific action, so fire it
* here for parity with the uploaded-file path in create_item().
*/
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
do_action( 'rest_after_insert_attachment', $attachment, $request, true );
$response = $this->prepare_item_for_response( $attachment, $request );
$response->set_status( 201 );
$response->header( 'Location', rest_url( rest_get_route_for_post( $attachment_id ) ) );
return $response;
}
Hooks
- do_action( ‘rest_after_insert_attachment’,
WP_Post $attachment ,WP_REST_Request $request ,bool $creating ) Fires after a single attachment is completely created or updated via the REST API.
Changelog
| Version | Description |
|---|---|
| 7.1.0 | Introduced. |
User Contributed Notes
You must log in before being able to contribute a note or feedback.