Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,46 @@ public function register_routes() {
'callback' => array( $this, 'finalize_item' ),
'permission_callback' => array( $this, 'edit_media_item_permissions_check' ),
'args' => array(
'id' => array(
'id' => array(
'description' => __( 'Unique identifier for the attachment.' ),
'type' => 'integer',
),
'sub_sizes' => array(
'description' => __( 'Array of sub-size metadata collected from sideload responses.' ),
'type' => 'array',
'default' => array(),
'items' => array(
'type' => 'object',
'properties' => array(
'image_size' => array(
'type' => 'string',
'required' => true,
),
'width' => array(
'type' => 'integer',
'minimum' => 1,
),
'height' => array(
'type' => 'integer',
'minimum' => 1,
),
'file' => array(
'type' => 'string',
),
'mime_type' => array(
'type' => 'string',
'pattern' => '^image/.*',
),
'filesize' => array(
'type' => 'integer',
'minimum' => 1,
),
'original_image' => array(
'type' => 'string',
),
),
),
),
),
),
'allow_batch' => $this->allow_batch,
Expand Down Expand Up @@ -2082,16 +2118,21 @@ public function sideload_item( WP_REST_Request $request ) {

$image_size = $request['image_size'];

$metadata = wp_get_attachment_metadata( $attachment_id, true );

if ( ! $metadata ) {
$metadata = array();
}
/*
* Build sub-size data to return to the client.
* The client accumulates these and sends them all to the finalize
* endpoint, which writes the metadata in a single operation. This
* avoids the read-modify-write race that concurrent sideloads for the
* same attachment would otherwise hit.
*/
$sub_size_data = array(
'image_size' => $image_size,
);

if ( 'original' === $image_size ) {
$metadata['original_image'] = wp_basename( $path );
$sub_size_data['file'] = wp_basename( $path );
} elseif ( 'scaled' === $image_size ) {
// The current attached file is the original; record it as original_image.
// Record the current attached file as the original.
$current_file = get_attached_file( $attachment_id, true );

if ( ! $current_file ) {
Expand All @@ -2102,7 +2143,7 @@ public function sideload_item( WP_REST_Request $request ) {
);
}

$metadata['original_image'] = wp_basename( $current_file );
$sub_size_data['original_image'] = wp_basename( $current_file );

// Validate the scaled image before updating the attached file.
$size = wp_getimagesize( $path );
Expand All @@ -2117,6 +2158,7 @@ public function sideload_item( WP_REST_Request $request ) {
}

// Update the attached file to point to the scaled version.
// This writes to _wp_attached_file meta, not _wp_attachment_metadata.
if (
get_attached_file( $attachment_id, true ) !== $path &&
! update_attached_file( $attachment_id, $path )
Expand All @@ -2128,42 +2170,21 @@ public function sideload_item( WP_REST_Request $request ) {
);
}

$metadata['width'] = $size[0];
$metadata['height'] = $size[1];
$metadata['filesize'] = $filesize;
$metadata['file'] = _wp_relative_upload_path( $path );
$sub_size_data['width'] = $size[0];
$sub_size_data['height'] = $size[1];
$sub_size_data['filesize'] = $filesize;
$sub_size_data['file'] = _wp_relative_upload_path( $path );
} else {
$metadata['sizes'] = $metadata['sizes'] ?? array();

$size = wp_getimagesize( $path );

$metadata['sizes'][ $image_size ] = array(
'width' => $size ? $size[0] : 0,
'height' => $size ? $size[1] : 0,
'file' => wp_basename( $path ),
'mime-type' => $type,
'filesize' => wp_filesize( $path ),
);
}

wp_update_attachment_metadata( $attachment_id, $metadata );

$response_request = new WP_REST_Request(
WP_REST_Server::READABLE,
rest_get_route_for_post( $attachment_id )
);

$response_request['context'] = 'edit';

if ( isset( $request['_fields'] ) ) {
$response_request['_fields'] = $request['_fields'];
$sub_size_data['width'] = $size ? $size[0] : 0;
$sub_size_data['height'] = $size ? $size[1] : 0;
$sub_size_data['file'] = wp_basename( $path );
$sub_size_data['mime_type'] = $type;
$sub_size_data['filesize'] = wp_filesize( $path );
}

$response = $this->prepare_item_for_response( get_post( $attachment_id ), $response_request );

$response->header( 'Location', rest_url( rest_get_route_for_post( $attachment_id ) ) );

return $response;
return rest_ensure_response( $sub_size_data );
}

/**
Expand Down Expand Up @@ -2215,9 +2236,11 @@ private static function filter_wp_unique_filename( $filename, $dir, $number, $at
/**
* Finalizes an attachment after client-side media processing.
*
* Triggers the 'wp_generate_attachment_metadata' filter so that
* server-side plugins can process the attachment after all client-side
* operations (upload, thumbnail generation, sideloads) are complete.
* Applies the sub-size metadata collected from sideload responses in a
* single metadata update, then triggers the 'wp_generate_attachment_metadata'
* filter so that server-side plugins can process the attachment after all
* client-side operations (upload, thumbnail generation, sideloads) are
* complete.
*
* @since 7.1.0
*
Expand All @@ -2237,6 +2260,35 @@ public function finalize_item( WP_REST_Request $request ) {
$metadata = array();
}

// Apply all sub-size metadata collected from sideload responses.
$sub_sizes = $request['sub_sizes'] ?? array();

foreach ( $sub_sizes as $sub_size ) {
$image_size = $sub_size['image_size'];

if ( 'original' === $image_size ) {
$metadata['original_image'] = $sub_size['file'];
} elseif ( 'scaled' === $image_size ) {
if ( ! empty( $sub_size['original_image'] ) ) {
$metadata['original_image'] = $sub_size['original_image'];
}
$metadata['width'] = $sub_size['width'] ?? 0;
$metadata['height'] = $sub_size['height'] ?? 0;
$metadata['filesize'] = $sub_size['filesize'] ?? 0;
$metadata['file'] = $sub_size['file'] ?? '';
} else {
$metadata['sizes'] = $metadata['sizes'] ?? array();

$metadata['sizes'][ $image_size ] = array(
'width' => $sub_size['width'] ?? 0,
'height' => $sub_size['height'] ?? 0,
'file' => $sub_size['file'] ?? '',
'mime-type' => $sub_size['mime_type'] ?? '',
'filesize' => $sub_size['filesize'] ?? 0,
);
}
}

/** This filter is documented in wp-admin/includes/image.php */
$metadata = apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'update' );

Expand Down
Loading
Loading