Media: Re-introduce client-side media processing feature.

Reverts the removal in [62081] now that WordPress 7.1 has forked. Restores all PHP functions, REST API endpoints, cross-origin isolation infrastructure, VIPS script module handling, build configuration, and associated tests.

Follow-up to [62081].

Props adamsilverstein, jorbin, westonruter.
Fixes #64919.
See #64906.


Built from https://develop.svn.wordpress.org/trunk@62428


git-svn-id: http://core.svn.wordpress.org/trunk@61709 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Adam Silverstein
2026-05-28 15:28:46 +00:00
parent ff4453309e
commit 52aeb83024
13 changed files with 5981 additions and 3 deletions
@@ -843,7 +843,10 @@
'wp-url'
),
'module_dependencies' => array(
array(
'id' => '@wordpress/vips/worker',
'import' => 'dynamic'
)
),
'version' => 'd359c2cccf866d7082d2'
),
@@ -284,6 +284,24 @@
),
'version' => 'c5843b6c5e84b352f43b'
),
'vips/loader.js' => array(
'dependencies' => array(
),
'module_dependencies' => array(
array(
'id' => '@wordpress/vips/worker',
'import' => 'dynamic'
)
),
'version' => '07c9acb45d3e5d81829a'
),
'vips/worker.js' => array(
'dependencies' => array(
),
'version' => 'aff5e5c5b28ae6b73aaa'
),
'workflow/index.js' => array(
'dependencies' => array(
'react',
+7
View File
@@ -684,6 +684,13 @@ add_action( 'customize_controls_enqueue_scripts', 'wp_plupload_default_settings'
add_action( 'plugins_loaded', '_wp_add_additional_image_sizes', 0 );
add_filter( 'plupload_default_settings', 'wp_show_heic_upload_error' );
// Client-side media processing.
add_action( 'admin_init', 'wp_set_client_side_media_processing_flag' );
// Cross-origin isolation for client-side media processing.
add_action( 'load-post.php', 'wp_set_up_cross_origin_isolation' );
add_action( 'load-post-new.php', 'wp_set_up_cross_origin_isolation' );
add_action( 'load-site-editor.php', 'wp_set_up_cross_origin_isolation' );
add_action( 'load-widgets.php', 'wp_set_up_cross_origin_isolation' );
// Nav menu.
add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 );
add_filter( 'nav_menu_css_class', 'wp_nav_menu_remove_menu_item_has_children_class', 10, 4 );
@@ -0,0 +1 @@
<?php return array('dependencies' => array(), 'module_dependencies' => array(array('id' => '@wordpress/vips/worker', 'import' => 'dynamic')), 'version' => '07c9acb45d3e5d81829a');
+1
View File
@@ -0,0 +1 @@
function r(){return import("@wordpress/vips/worker")}export{r as default};
@@ -0,0 +1 @@
<?php return array('dependencies' => array(), 'version' => 'aff5e5c5b28ae6b73aaa');
File diff suppressed because one or more lines are too long
+44
View File
@@ -156,6 +156,12 @@ function wp_underscore_video_template() {
function wp_print_media_templates() {
$class = 'media-modal wp-core-ui';
$is_cross_origin_isolation_enabled = wp_is_client_side_media_processing_enabled();
if ( $is_cross_origin_isolation_enabled ) {
ob_start();
}
$alt_text_description = sprintf(
/* translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */
__( '<a href="%1$s" %2$s>Learn how to describe the purpose of the image%3$s</a>. Leave empty if the image is purely decorative.' ),
@@ -1582,4 +1588,42 @@ function wp_print_media_templates() {
* @since 3.5.0
*/
do_action( 'print_media_templates' );
if ( $is_cross_origin_isolation_enabled ) {
$html = (string) ob_get_clean();
/*
* The media templates are inside <script type="text/html"> tags,
* whose content is treated as raw text by the HTML Tag Processor.
* Extract each script block's content, process it separately,
* then reassemble the full output.
*/
$script_processor = new WP_HTML_Tag_Processor( $html );
while ( $script_processor->next_tag( 'SCRIPT' ) ) {
if ( 'text/html' !== $script_processor->get_attribute( 'type' ) ) {
continue;
}
/*
* Unlike wp_add_crossorigin_attributes(), this does not check whether
* URLs are actually cross-origin. Media templates use Underscore.js
* template expressions (e.g. {{ data.url }}) as placeholder URLs,
* so actual URLs are not available at parse time.
* The crossorigin attribute is added unconditionally to all relevant
* media tags to ensure cross-origin isolation works regardless of
* the final URL value at render time.
*/
$template_processor = new WP_HTML_Tag_Processor( $script_processor->get_modifiable_text() );
while ( $template_processor->next_tag() ) {
if (
in_array( $template_processor->get_tag(), array( 'AUDIO', 'IMG', 'VIDEO' ), true )
&& ! is_string( $template_processor->get_attribute( 'crossorigin' ) )
) {
$template_processor->set_attribute( 'crossorigin', 'anonymous' );
}
}
$script_processor->set_modifiable_text( $template_processor->get_updated_html() );
}
echo $script_processor->get_updated_html();
}
}
+217
View File
@@ -6447,3 +6447,220 @@ function wp_get_image_editor_output_format( $filename, $mime_type ) {
return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type );
}
/**
* Checks whether client-side media processing is enabled.
*
* Client-side media processing uses the browser's capabilities to handle
* tasks like image resizing and compression before uploading to the server.
*
* @since 7.1.0
*
* @return bool Whether client-side media processing is enabled.
*/
function wp_is_client_side_media_processing_enabled(): bool {
// This is due to SharedArrayBuffer requiring a secure context.
$host = strtolower( (string) strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) );
$enabled = ( is_ssl() || 'localhost' === $host || str_ends_with( $host, '.localhost' ) );
/**
* Filters whether client-side media processing is enabled.
*
* @since 7.1.0
*
* @param bool $enabled Whether client-side media processing is enabled. Default true if the page is served in a secure context.
*/
return (bool) apply_filters( 'wp_client_side_media_processing_enabled', $enabled );
}
/**
* Sets a global JS variable to indicate that client-side media processing is enabled.
*
* @since 7.1.0
*/
function wp_set_client_side_media_processing_flag(): void {
if ( ! wp_is_client_side_media_processing_enabled() ) {
return;
}
wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true;', 'before' );
$chromium_version = wp_get_chromium_major_version();
if ( null !== $chromium_version && $chromium_version >= 137 ) {
wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true;', 'before' );
}
/*
* Register the @wordpress/vips/worker script module as a dynamic dependency
* of the wp-upload-media classic script. This ensures it is included in the
* import map so that the dynamic import() in upload-media.js can resolve it.
*/
wp_scripts()->add_data(
'wp-upload-media',
'module_dependencies',
array( '@wordpress/vips/worker' )
);
}
/**
* Returns the major Chrome/Chromium version from the current request's User-Agent.
*
* Matches all Chromium-based browsers (Chrome, Edge, Opera, Brave).
*
* @since 7.1.0
*
* @return int|null The major Chrome version, or null if not a Chromium browser.
*/
function wp_get_chromium_major_version(): ?int {
if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
return null;
}
if ( preg_match( '#Chrome/(\d+)#', $_SERVER['HTTP_USER_AGENT'], $matches ) ) {
return (int) $matches[1];
}
return null;
}
/**
* Enables cross-origin isolation in the block editor.
*
* Required for enabling SharedArrayBuffer for WebAssembly-based
* media processing in the editor. Uses Document-Isolation-Policy
* on supported browsers (Chromium 137+).
*
* Skips setup when a third-party page builder overrides the block
* editor via a custom `action` query parameter, as DIP would block
* same-origin iframe access that these editors rely on.
*
* @since 7.1.0
*/
function wp_set_up_cross_origin_isolation(): void {
if ( ! wp_is_client_side_media_processing_enabled() ) {
return;
}
$screen = get_current_screen();
if ( ! $screen ) {
return;
}
if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) {
return;
}
/*
* Skip when a third-party page builder overrides the block editor.
* DIP isolates the document into its own agent cluster,
* which blocks same-origin iframe access that these editors rely on.
*/
if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) {
return;
}
// Cross-origin isolation is not needed if users can't upload files anyway.
if ( ! current_user_can( 'upload_files' ) ) {
return;
}
wp_start_cross_origin_isolation_output_buffer();
}
/**
* Sends the Document-Isolation-Policy header for cross-origin isolation.
*
* Uses an output buffer to add crossorigin="anonymous" where needed.
*
* @since 7.1.0
*/
function wp_start_cross_origin_isolation_output_buffer(): void {
$chromium_version = wp_get_chromium_major_version();
if ( null === $chromium_version || $chromium_version < 137 ) {
return;
}
ob_start(
static function ( string $output ): string {
header( 'Document-Isolation-Policy: isolate-and-credentialless' );
return wp_add_crossorigin_attributes( $output );
}
);
}
/**
* Adds crossorigin="anonymous" to relevant tags in the given HTML string.
*
* @since 7.1.0
*
* @param string $html HTML input.
* @return string Modified HTML.
*/
function wp_add_crossorigin_attributes( string $html ): string {
$site_url = site_url();
$processor = new WP_HTML_Tag_Processor( $html );
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
$cross_origin_tag_attributes = array(
'AUDIO' => array( 'src' ),
'LINK' => array( 'href' ),
'SCRIPT' => array( 'src' ),
'VIDEO' => array( 'src', 'poster' ),
'SOURCE' => array( 'src' ),
);
while ( $processor->next_tag() ) {
$tag = $processor->get_tag();
if ( ! isset( $cross_origin_tag_attributes[ $tag ] ) ) {
continue;
}
$crossorigin = $processor->get_attribute( 'crossorigin' );
if ( null !== $crossorigin ) {
continue;
}
if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) {
$processor->set_bookmark( 'audio-video-parent' );
}
$processor->set_bookmark( 'resume' );
$sought = false;
$is_cross_origin = false;
foreach ( $cross_origin_tag_attributes[ $tag ] as $attr ) {
$url = $processor->get_attribute( $attr );
if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) ) {
$is_cross_origin = true;
}
if ( $is_cross_origin ) {
break;
}
}
if ( $is_cross_origin ) {
if ( 'SOURCE' === $tag ) {
$sought = $processor->seek( 'audio-video-parent' );
if ( $sought ) {
$processor->set_attribute( 'crossorigin', 'anonymous' );
}
} else {
$processor->set_attribute( 'crossorigin', 'anonymous' );
}
if ( $sought ) {
$processor->seek( 'resume' );
$processor->release_bookmark( 'audio-video-parent' );
}
}
}
return $processor->get_updated_html();
}
@@ -1368,6 +1368,34 @@ class WP_REST_Server {
'routes' => $this->get_data_for_routes( $this->get_routes(), $request['context'] ),
);
// Add media processing settings for users who can upload files.
if ( wp_is_client_side_media_processing_enabled() && current_user_can( 'upload_files' ) ) {
// Image sizes keyed by name for client-side media processing.
$available['image_sizes'] = array();
foreach ( wp_get_registered_image_subsizes() as $name => $size ) {
$available['image_sizes'][ $name ] = $size;
}
/** This filter is documented in wp-admin/includes/image.php */
$available['image_size_threshold'] = (int) apply_filters( 'big_image_size_threshold', 2560, array( 0, 0 ), '', 0 );
// Image output formats.
$input_formats = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' );
$output_formats = array();
foreach ( $input_formats as $mime_type ) {
/** This filter is documented in wp-includes/media.php */
$output_formats = apply_filters( 'image_editor_output_format', $output_formats, '', $mime_type );
}
$available['image_output_formats'] = (object) $output_formats;
/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
$available['jpeg_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/jpeg' );
/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
$available['png_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/png' );
/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
$available['gif_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/gif' );
}
$response = new WP_REST_Response( $available );
$fields = $request['_fields'] ?? '';
@@ -63,6 +63,97 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
'args' => $this->get_edit_media_item_args(),
)
);
if ( wp_is_client_side_media_processing_enabled() ) {
$valid_image_sizes = array_keys( wp_get_registered_image_subsizes() );
// Special case to set 'original_image' in attachment metadata.
$valid_image_sizes[] = 'original';
// Used for PDF thumbnails.
$valid_image_sizes[] = 'full';
// Client-side big image threshold: sideload the scaled version.
$valid_image_sizes[] = 'scaled';
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)/sideload',
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'sideload_item' ),
'permission_callback' => array( $this, 'sideload_item_permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the attachment.' ),
'type' => 'integer',
),
'image_size' => array(
'description' => __( 'Image size.' ),
'type' => 'string',
'enum' => $valid_image_sizes,
'required' => true,
),
'convert_format' => array(
'type' => 'boolean',
'default' => true,
'description' => __( 'Whether to convert image formats.' ),
),
),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d]+)/finalize',
array(
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'finalize_item' ),
'permission_callback' => array( $this, 'edit_media_item_permissions_check' ),
'args' => array(
'id' => array(
'description' => __( 'Unique identifier for the attachment.' ),
'type' => 'integer',
),
),
),
'allow_batch' => $this->allow_batch,
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
}
/**
* Retrieves the query params for the attachments collection.
*
* @since 7.1.0
*
* @param string $method Optional. HTTP method of the request.
* The arguments for `CREATABLE` requests are
* checked for required values and may fall-back to a given default.
* Default WP_REST_Server::CREATABLE.
* @return array<string, array<string, mixed>> Endpoint arguments.
*/
public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
$args = parent::get_endpoint_args_for_item_schema( $method );
if ( WP_REST_Server::CREATABLE === $method && wp_is_client_side_media_processing_enabled() ) {
$args['generate_sub_sizes'] = array(
'type' => 'boolean',
'default' => true,
'description' => __( 'Whether to generate image sub sizes.' ),
);
$args['convert_format'] = array(
'type' => 'boolean',
'default' => true,
'description' => __( 'Whether to convert image formats.' ),
);
}
return $args;
}
/**
@@ -161,6 +252,12 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
*/
$prevent_unsupported_uploads = apply_filters( 'wp_prevent_unsupported_mime_type_uploads', true, $files['file']['type'] ?? null );
// When the client handles image processing (generate_sub_sizes is false),
// skip the server-side image editor support check.
if ( false === $request['generate_sub_sizes'] ) {
$prevent_unsupported_uploads = false;
}
// If the upload is an image, check if the server can handle the mime type.
if (
$prevent_unsupported_uploads &&
@@ -192,6 +289,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
* Creates a single attachment.
*
* @since 4.7.0
* @since 7.1.0 Added `generate_sub_sizes` and `convert_format` parameters.
*
* @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.
@@ -205,9 +303,24 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
);
}
// Handle generate_sub_sizes parameter.
if ( false === $request['generate_sub_sizes'] ) {
add_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 );
add_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 );
// Disable server-side EXIF rotation so the client can handle it.
// This preserves the original orientation value in the metadata.
add_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 );
}
// Handle convert_format parameter.
if ( false === $request['convert_format'] ) {
add_filter( 'image_editor_output_format', '__return_empty_array', 100 );
}
$insert = $this->insert_attachment( $request );
if ( is_wp_error( $insert ) ) {
$this->remove_client_side_media_processing_filters();
return $insert;
}
@@ -225,6 +338,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
$thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id );
if ( is_wp_error( $thumbnail_update ) ) {
$this->remove_client_side_media_processing_filters();
return $thumbnail_update;
}
}
@@ -233,6 +347,7 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
$meta_update = $this->meta->update_value( $request['meta'], $attachment_id );
if ( is_wp_error( $meta_update ) ) {
$this->remove_client_side_media_processing_filters();
return $meta_update;
}
}
@@ -241,12 +356,14 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
if ( is_wp_error( $fields_update ) ) {
$this->remove_client_side_media_processing_filters();
return $fields_update;
}
$terms_update = $this->handle_terms( $attachment_id, $request );
if ( is_wp_error( $terms_update ) ) {
$this->remove_client_side_media_processing_filters();
return $terms_update;
}
@@ -283,6 +400,8 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
*/
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
$this->remove_client_side_media_processing_filters();
$response = $this->prepare_item_for_response( $attachment, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
@@ -291,6 +410,18 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
return $response;
}
/**
* Removes filters added for client-side media processing.
*
* @since 7.1.0
*/
private function remove_client_side_media_processing_filters(): void {
remove_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 );
remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 );
remove_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 );
remove_filter( 'image_editor_output_format', '__return_empty_array', 100 );
}
/**
* Inserts the attachment post in the database. Does not update the attachment meta.
*
@@ -1856,4 +1987,272 @@ class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
return null;
}
/**
* Checks if a given request has access to sideload a file.
*
* Sideloading a file for an existing attachment
* requires both update and create permissions.
*
* @since 7.1.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
*/
public function sideload_item_permissions_check( $request ) {
return $this->edit_media_item_permissions_check( $request );
}
/**
* Side-loads a media file without creating a new 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.
*/
public function sideload_item( WP_REST_Request $request ) {
$attachment_id = (int) $request['id'];
$post = $this->get_post( $attachment_id );
if ( is_wp_error( $post ) ) {
return $post;
}
if (
! wp_attachment_is_image( $post ) &&
! wp_attachment_is( 'pdf', $post )
) {
return new WP_Error(
'rest_post_invalid_id',
__( 'Invalid post ID. Only images and PDFs can be sideloaded.' ),
array( 'status' => 400 )
);
}
if ( false === $request['convert_format'] ) {
// Prevent image conversion as that is done client-side.
add_filter( 'image_editor_output_format', '__return_empty_array', 100 );
}
// Get the file via $_FILES or raw data.
$files = $request->get_file_params();
$headers = $request->get_headers();
/*
* wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts.
* See /wp-includes/functions.php.
* With the following filter we can work around this safeguard.
*/
$attachment_filename = get_attached_file( $attachment_id, true );
$attachment_filename = $attachment_filename ? wp_basename( $attachment_filename ) : null;
$filter_filename = static function ( $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number ) use ( $attachment_filename ) {
return self::filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename );
};
add_filter( 'wp_unique_filename', $filter_filename, 10, 6 );
$parent_post = get_post_parent( $attachment_id );
$time = null;
// Matches logic in media_handle_upload().
// The post date doesn't usually matter for pages, so don't backdate this upload.
if ( $parent_post && 'page' !== $parent_post->post_type && ! str_starts_with( $parent_post->post_date, '0000-00-00' ) ) {
$time = $parent_post->post_date;
}
if ( ! empty( $files ) ) {
$file = $this->upload_from_file( $files, $headers, $time );
} else {
$file = $this->upload_from_data( $request->get_body(), $headers, $time );
}
remove_filter( 'wp_unique_filename', $filter_filename );
remove_filter( 'image_editor_output_format', '__return_empty_array', 100 );
if ( is_wp_error( $file ) ) {
return $file;
}
$type = $file['type'];
$path = $file['file'];
$image_size = $request['image_size'];
$metadata = wp_get_attachment_metadata( $attachment_id, true );
if ( ! $metadata ) {
$metadata = array();
}
if ( 'original' === $image_size ) {
$metadata['original_image'] = wp_basename( $path );
} elseif ( 'scaled' === $image_size ) {
// The current attached file is the original; record it as original_image.
$current_file = get_attached_file( $attachment_id, true );
if ( ! $current_file ) {
return new WP_Error(
'rest_sideload_no_attached_file',
__( 'Unable to retrieve the attached file for this attachment.' ),
array( 'status' => 404 )
);
}
$metadata['original_image'] = wp_basename( $current_file );
// Validate the scaled image before updating the attached file.
$size = wp_getimagesize( $path );
$filesize = wp_filesize( $path );
if ( ! $size || ! $filesize ) {
return new WP_Error(
'rest_sideload_invalid_image',
__( 'Unable to read the scaled image file.' ),
array( 'status' => 500 )
);
}
// Update the attached file to point to the scaled version.
if (
get_attached_file( $attachment_id, true ) !== $path &&
! update_attached_file( $attachment_id, $path )
) {
return new WP_Error(
'rest_sideload_update_attached_file_failed',
__( 'Unable to update the attached file for this attachment.' ),
array( 'status' => 500 )
);
}
$metadata['width'] = $size[0];
$metadata['height'] = $size[1];
$metadata['filesize'] = $filesize;
$metadata['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'];
}
$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;
}
/**
* Filters wp_unique_filename during sideloads.
*
* wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts.
* Adding this closure to the filter helps work around this safeguard.
*
* Example: when uploading myphoto.jpeg, WordPress normally creates myphoto-150x150.jpeg,
* and when uploading myphoto-150x150.jpeg, it will be renamed to myphoto-150x150-1.jpeg
* However, here it is desired not to add the suffix in order to maintain the same
* naming convention as if the file was uploaded regularly.
*
* @since 7.1.0
*
* @link https://github.com/WordPress/wordpress-develop/blob/30954f7ac0840cfdad464928021d7f380940c347/src/wp-includes/functions.php#L2576-L2582
*
* @param string $filename Unique file name.
* @param string $dir Directory path.
* @param int|string $number The highest number that was used to make the file name unique
* or an empty string if unused.
* @param string|null $attachment_filename Original attachment file name.
* @return string Filtered file name.
*/
private static function filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ) {
if ( ! is_int( $number ) || ! $attachment_filename ) {
return $filename;
}
$ext = pathinfo( $filename, PATHINFO_EXTENSION );
$name = pathinfo( $filename, PATHINFO_FILENAME );
$orig_name = pathinfo( $attachment_filename, PATHINFO_FILENAME );
if ( ! $ext || ! $name ) {
return $filename;
}
$matches = array();
if ( preg_match( '/(.*)-(\d+x\d+|scaled)-' . $number . '$/', $name, $matches ) ) {
$filename_without_suffix = $matches[1] . '-' . $matches[2] . ".$ext";
if ( $matches[1] === $orig_name && ! file_exists( "$dir/$filename_without_suffix" ) ) {
return $filename_without_suffix;
}
}
return $filename;
}
/**
* 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.
*
* @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.
*/
public function finalize_item( WP_REST_Request $request ) {
$attachment_id = (int) $request['id'];
$post = $this->get_post( $attachment_id );
if ( is_wp_error( $post ) ) {
return $post;
}
$metadata = wp_get_attachment_metadata( $attachment_id );
if ( ! is_array( $metadata ) ) {
$metadata = array();
}
/** This filter is documented in wp-admin/includes/image.php */
$metadata = apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'update' );
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'];
}
return $this->prepare_item_for_response( $post, $response_request );
}
}
+5 -1
View File
@@ -211,7 +211,11 @@ function wp_default_script_modules() {
wp_interactivity()->add_client_navigation_support_to_script_module( $script_module_id );
}
if ( '' !== $suffix ) {
// VIPS files are always minified — the non-minified versions are not
// shipped because they are ~10MB of inlined WASM with no debugging value.
if ( str_starts_with( $file_name, 'vips/' ) ) {
$file_name = str_replace( '.js', '.min.js', $file_name );
} elseif ( '' !== $suffix ) {
$file_name = str_replace( '.js', $suffix . '.js', $file_name );
}
+1 -1
View File
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
$wp_version = '7.1-alpha-62427';
$wp_version = '7.1-alpha-62428';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.