Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,29 @@ public function create_item( $request ) {
);
}

/*
* A request names the file to attach either as an upload or as a URL to
* sideload, never as both: the two are alternative sources for the same
* attachment, so honoring one would silently discard the other. An
* upload arrives as a multipart file parameter or as the raw request
* body, which upload_from_data() identifies by its Content-Disposition
* filename.
*/
if ( ! empty( $request['url'] ) ) {
$headers = $request->get_headers();
$disposition = empty( $headers['content_disposition'] )
? null
: self::get_filename_from_disposition( $headers['content_disposition'] );

if ( ! empty( $request->get_file_params() ) || ! empty( $disposition ) ) {
return new WP_Error(
'rest_invalid_param',
__( 'The url parameter cannot be combined with an uploaded file.' ),
array( 'status' => 400 )
);
}
}

// Handle generate_sub_sizes parameter.
if ( false === $request['generate_sub_sizes'] ) {
add_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 );
Expand All @@ -480,18 +503,6 @@ public function create_item( $request ) {
add_filter( 'image_editor_output_format', '__return_empty_array', 100 );
}

/*
* When a URL is supplied instead of an uploaded file, sideload the
* remote image on the server. This avoids a cross-origin browser fetch,
* which fails under cross-origin isolation. The sub-size and scaling
* filters applied above still govern whether derivatives are generated.
*/
if ( ! empty( $request['url'] ) ) {
$response = $this->create_item_from_url( $request );
$this->remove_client_side_media_processing_filters();
return $response;
}

$insert = $this->insert_attachment( $request );

if ( is_wp_error( $insert ) ) {
Expand Down Expand Up @@ -586,33 +597,23 @@ public function create_item( $request ) {
}

/**
* Sideloads an external image from a URL into the media library.
* Handles an image sideloaded from a URL.
*
* 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().
* fetch that fails under cross-origin isolation. The downloaded file is
* handed to wp_handle_sideload(), so this returns the same data as the
* uploaded-file handlers and the attachment is inserted by the shared code
* in insert_attachment().
*
* @since 7.1.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
* @param string $url URL of the image to sideload.
* @param string|null $time Optional. Time formatted in 'yyyy/mm'. Default null.
* @return array|WP_Error Data from wp_handle_sideload().
*/
protected function create_item_from_url( $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() )
);
}

protected function upload_from_url( $url, $time = null ) {
// Include filesystem functions to get access to download_url() and wp_handle_sideload().
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 );
Expand Down Expand Up @@ -705,36 +706,25 @@ protected function create_item_from_url( $request ) {
);
}

$attachment_id = media_handle_sideload( $file_array, $post_id );
$sideloaded = wp_handle_sideload( $file_array, array( 'test_form' => false ), $time );

if ( is_wp_error( $attachment_id ) ) {
if ( isset( $sideloaded['error'] ) ) {
/*
* media_handle_sideload() deletes the temp file on success; remove
* it explicitly when the sideload fails.
* wp_handle_sideload() moves the temp file on success; remove it
* explicitly when it 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 new WP_Error(
'rest_upload_sideload_error',
$sideloaded['error'],
array( 'status' => 500 )
);
}

return $response;
return $sideloaded;
}

/**
Expand Down Expand Up @@ -774,7 +764,16 @@ protected function insert_attachment( $request ) {
}
}

if ( ! empty( $files ) ) {
/*
* A URL is the third way to name the file, alongside a multipart upload
* and a raw request body. All three produce the same data, so the rest
* of this method is shared: the URL path gets the same title and caption
* defaults, the same hooks, and the same terms and meta handling as an
* uploaded file.
*/
if ( ! empty( $request['url'] ) ) {
$file = $this->upload_from_url( $request['url'], $time );
} elseif ( ! empty( $files ) ) {
$file = $this->upload_from_file( $files, $headers, $time );
} else {
$file = $this->upload_from_data( $request->get_body(), $headers, $time );
Expand Down Expand Up @@ -815,6 +814,11 @@ protected function insert_attachment( $request ) {

$attachment = $this->prepare_item_for_database( $request );

// The rest_pre_insert_attachment filter can return an error.
if ( is_wp_error( $attachment ) ) {
return $attachment;
}

$attachment->post_mime_type = $type;
$attachment->guid = $url;

Expand Down
Loading
Loading