Changeset 61703
- Timestamp:
- 02/20/2026 11:49:50 AM (5 weeks ago)
- Location:
- trunk
- Files:
-
- 13 edited
-
package.json (modified) (1 diff)
-
src/wp-admin/edit-form-blocks.php (modified) (1 diff)
-
src/wp-admin/site-editor.php (modified) (1 diff)
-
src/wp-includes/default-filters.php (modified) (1 diff)
-
src/wp-includes/media-template.php (modified) (2 diffs)
-
src/wp-includes/media.php (modified) (1 diff)
-
src/wp-includes/rest-api/class-wp-rest-server.php (modified) (1 diff)
-
src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php (modified) (12 diffs)
-
src/wp-includes/script-loader.php (modified) (1 diff)
-
tests/phpunit/tests/rest-api/rest-attachments-controller.php (modified) (1 diff)
-
tests/phpunit/tests/rest-api/rest-schema-setup.php (modified) (1 diff)
-
tests/qunit/fixtures/wp-api-generated.js (modified) (5 diffs)
-
tools/gutenberg/copy-gutenberg-build.js (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/package.json
r61699 r61703 8 8 }, 9 9 "gutenberg": { 10 "ref": " 7a11a53377a95cba4d3786d71cadd4c2f0c5ac52"10 "ref": "b441348bb7e05af351c250b74283f253acaf9138" 11 11 }, 12 12 "engines": { -
trunk/src/wp-admin/edit-form-blocks.php
r61568 r61703 93 93 'gmt_offset', 94 94 'home', 95 'image_sizes', 96 'image_size_threshold', 97 'image_output_formats', 98 'jpeg_interlaced', 99 'png_interlaced', 100 'gif_interlaced', 95 101 'name', 96 102 'site_icon', -
trunk/src/wp-admin/site-editor.php
r61568 r61703 219 219 'gmt_offset', 220 220 'home', 221 'image_sizes', 222 'image_size_threshold', 223 'image_output_formats', 224 'jpeg_interlaced', 225 'png_interlaced', 226 'gif_interlaced', 221 227 'name', 222 228 'site_icon', -
trunk/src/wp-includes/default-filters.php
r61689 r61703 676 676 add_filter( 'plupload_default_settings', 'wp_show_heic_upload_error' ); 677 677 678 // Client-side media processing. 679 add_action( 'admin_init', 'wp_set_client_side_media_processing_flag' ); 680 // Cross-origin isolation for client-side media processing. 681 add_action( 'load-post.php', 'wp_set_up_cross_origin_isolation' ); 682 add_action( 'load-post-new.php', 'wp_set_up_cross_origin_isolation' ); 683 add_action( 'load-site-editor.php', 'wp_set_up_cross_origin_isolation' ); 684 add_action( 'load-widgets.php', 'wp_set_up_cross_origin_isolation' ); 678 685 // Nav menu. 679 686 add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 ); -
trunk/src/wp-includes/media-template.php
r61651 r61703 157 157 $class = 'media-modal wp-core-ui'; 158 158 159 $is_cross_origin_isolation_enabled = wp_is_client_side_media_processing_enabled(); 160 161 if ( $is_cross_origin_isolation_enabled ) { 162 ob_start(); 163 } 164 159 165 $alt_text_description = sprintf( 160 166 /* translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */ … … 1583 1589 */ 1584 1590 do_action( 'print_media_templates' ); 1591 1592 if ( $is_cross_origin_isolation_enabled ) { 1593 $html = (string) ob_get_clean(); 1594 1595 /* 1596 * The media templates are inside <script type="text/html"> tags, 1597 * whose content is treated as raw text by the HTML Tag Processor. 1598 * Extract each script block's content, process it separately, 1599 * then reassemble the full output. 1600 */ 1601 $script_processor = new WP_HTML_Tag_Processor( $html ); 1602 while ( $script_processor->next_tag( 'SCRIPT' ) ) { 1603 if ( 'text/html' !== $script_processor->get_attribute( 'type' ) ) { 1604 continue; 1605 } 1606 /* 1607 * Unlike wp_add_crossorigin_attributes(), this does not check whether 1608 * URLs are actually cross-origin. Media templates use Underscore.js 1609 * template expressions (e.g. {{ data.url }}) as placeholder URLs, 1610 * so actual URLs are not available at parse time. 1611 * The crossorigin attribute is added unconditionally to all relevant 1612 * media tags to ensure cross-origin isolation works regardless of 1613 * the final URL value at render time. 1614 */ 1615 $template_processor = new WP_HTML_Tag_Processor( $script_processor->get_modifiable_text() ); 1616 while ( $template_processor->next_tag() ) { 1617 if ( 1618 in_array( $template_processor->get_tag(), array( 'AUDIO', 'IMG', 'VIDEO' ), true ) 1619 && ! is_string( $template_processor->get_attribute( 'crossorigin' ) ) 1620 ) { 1621 $template_processor->set_attribute( 'crossorigin', 'anonymous' ); 1622 } 1623 } 1624 $script_processor->set_modifiable_text( $template_processor->get_updated_html() ); 1625 } 1626 1627 echo $script_processor->get_updated_html(); 1628 } 1585 1629 } -
trunk/src/wp-includes/media.php
r61699 r61703 6360 6360 return apply_filters( 'image_editor_output_format', $output_format, $filename, $mime_type ); 6361 6361 } 6362 6363 /** 6364 * Checks whether client-side media processing is enabled. 6365 * 6366 * Client-side media processing uses the browser's capabilities to handle 6367 * tasks like image resizing and compression before uploading to the server. 6368 * 6369 * @since 7.0.0 6370 * 6371 * @return bool Whether client-side media processing is enabled. 6372 */ 6373 function wp_is_client_side_media_processing_enabled(): bool { 6374 /** 6375 * Filters whether client-side media processing is enabled. 6376 * 6377 * @since 7.0.0 6378 * 6379 * @param bool $enabled Whether client-side media processing is enabled. Default true. 6380 */ 6381 return (bool) apply_filters( 'wp_client_side_media_processing_enabled', true ); 6382 } 6383 6384 /** 6385 * Sets a global JS variable to indicate that client-side media processing is enabled. 6386 * 6387 * @since 7.0.0 6388 */ 6389 function wp_set_client_side_media_processing_flag(): void { 6390 if ( ! wp_is_client_side_media_processing_enabled() ) { 6391 return; 6392 } 6393 6394 wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true', 'before' ); 6395 6396 /* 6397 * Register the @wordpress/vips/worker script module as a dynamic dependency 6398 * of the wp-upload-media classic script. This ensures it is included in the 6399 * import map so that the dynamic import() in upload-media.js can resolve it. 6400 */ 6401 wp_scripts()->add_data( 6402 'wp-upload-media', 6403 'module_dependencies', 6404 array( '@wordpress/vips/worker' ) 6405 ); 6406 } 6407 6408 /** 6409 * Enables cross-origin isolation in the block editor. 6410 * 6411 * Required for enabling SharedArrayBuffer for WebAssembly-based 6412 * media processing in the editor. 6413 * 6414 * @since 7.0.0 6415 * 6416 * @link https://web.dev/coop-coep/ 6417 */ 6418 function wp_set_up_cross_origin_isolation(): void { 6419 if ( ! wp_is_client_side_media_processing_enabled() ) { 6420 return; 6421 } 6422 6423 $screen = get_current_screen(); 6424 6425 if ( ! $screen ) { 6426 return; 6427 } 6428 6429 if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) { 6430 return; 6431 } 6432 6433 // Cross-origin isolation is not needed if users can't upload files anyway. 6434 if ( ! current_user_can( 'upload_files' ) ) { 6435 return; 6436 } 6437 6438 wp_start_cross_origin_isolation_output_buffer(); 6439 } 6440 6441 /** 6442 * Starts an output buffer to send cross-origin isolation headers. 6443 * 6444 * Sends headers and uses an output buffer to add crossorigin="anonymous" 6445 * attributes where needed. 6446 * 6447 * @since 7.0.0 6448 * 6449 * @link https://web.dev/coop-coep/ 6450 * 6451 * @global bool $is_safari 6452 */ 6453 function wp_start_cross_origin_isolation_output_buffer(): void { 6454 global $is_safari; 6455 6456 $coep = $is_safari ? 'require-corp' : 'credentialless'; 6457 6458 ob_start( 6459 static function ( string $output ) use ( $coep ): string { 6460 header( 'Cross-Origin-Opener-Policy: same-origin' ); 6461 header( "Cross-Origin-Embedder-Policy: $coep" ); 6462 6463 return wp_add_crossorigin_attributes( $output ); 6464 } 6465 ); 6466 } 6467 6468 /** 6469 * Adds crossorigin="anonymous" to relevant tags in the given HTML string. 6470 * 6471 * @since 7.0.0 6472 * 6473 * @param string $html HTML input. 6474 * @return string Modified HTML. 6475 */ 6476 function wp_add_crossorigin_attributes( string $html ): string { 6477 $site_url = site_url(); 6478 6479 $processor = new WP_HTML_Tag_Processor( $html ); 6480 6481 // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin. 6482 $cross_origin_tag_attributes = array( 6483 'AUDIO' => array( 'src' => false ), 6484 'IMG' => array( 6485 'src' => false, 6486 'srcset' => true, 6487 ), 6488 'LINK' => array( 6489 'href' => false, 6490 'imagesrcset' => true, 6491 ), 6492 'SCRIPT' => array( 'src' => false ), 6493 'VIDEO' => array( 6494 'src' => false, 6495 'poster' => false, 6496 ), 6497 'SOURCE' => array( 'src' => false ), 6498 ); 6499 6500 while ( $processor->next_tag() ) { 6501 $tag = $processor->get_tag(); 6502 6503 if ( ! isset( $cross_origin_tag_attributes[ $tag ] ) ) { 6504 continue; 6505 } 6506 6507 if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) { 6508 $processor->set_bookmark( 'audio-video-parent' ); 6509 } 6510 6511 $processor->set_bookmark( 'resume' ); 6512 6513 $sought = false; 6514 6515 $crossorigin = $processor->get_attribute( 'crossorigin' ); 6516 6517 $is_cross_origin = false; 6518 6519 foreach ( $cross_origin_tag_attributes[ $tag ] as $attr => $is_srcset ) { 6520 if ( $is_srcset ) { 6521 $srcset = $processor->get_attribute( $attr ); 6522 if ( is_string( $srcset ) ) { 6523 foreach ( explode( ',', $srcset ) as $candidate ) { 6524 $candidate_url = strtok( trim( $candidate ), ' ' ); 6525 if ( is_string( $candidate_url ) && '' !== $candidate_url && ! str_starts_with( $candidate_url, $site_url ) && ! str_starts_with( $candidate_url, '/' ) ) { 6526 $is_cross_origin = true; 6527 break; 6528 } 6529 } 6530 } 6531 } else { 6532 $url = $processor->get_attribute( $attr ); 6533 if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) ) { 6534 $is_cross_origin = true; 6535 } 6536 } 6537 6538 if ( $is_cross_origin ) { 6539 break; 6540 } 6541 } 6542 6543 if ( $is_cross_origin && ! is_string( $crossorigin ) ) { 6544 if ( 'SOURCE' === $tag ) { 6545 $sought = $processor->seek( 'audio-video-parent' ); 6546 6547 if ( $sought ) { 6548 $processor->set_attribute( 'crossorigin', 'anonymous' ); 6549 } 6550 } else { 6551 $processor->set_attribute( 'crossorigin', 'anonymous' ); 6552 } 6553 6554 if ( $sought ) { 6555 $processor->seek( 'resume' ); 6556 $processor->release_bookmark( 'audio-video-parent' ); 6557 } 6558 } 6559 } 6560 6561 return $processor->get_updated_html(); 6562 } 6563 -
trunk/src/wp-includes/rest-api/class-wp-rest-server.php
r61463 r61703 1369 1369 ); 1370 1370 1371 // Add media processing settings for users who can upload files. 1372 if ( wp_is_client_side_media_processing_enabled() && current_user_can( 'upload_files' ) ) { 1373 // Image sizes keyed by name for client-side media processing. 1374 $available['image_sizes'] = array(); 1375 foreach ( wp_get_registered_image_subsizes() as $name => $size ) { 1376 $available['image_sizes'][ $name ] = $size; 1377 } 1378 1379 /** This filter is documented in wp-admin/includes/image.php */ 1380 $available['image_size_threshold'] = (int) apply_filters( 'big_image_size_threshold', 2560, array( 0, 0 ), '', 0 ); 1381 1382 // Image output formats. 1383 $input_formats = array( 'image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/avif', 'image/heic' ); 1384 $output_formats = array(); 1385 foreach ( $input_formats as $mime_type ) { 1386 /** This filter is documented in wp-includes/class-wp-image-editor.php */ 1387 $output_formats = apply_filters( 'image_editor_output_format', $output_formats, '', $mime_type ); 1388 } 1389 $available['image_output_formats'] = (object) $output_formats; 1390 1391 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */ 1392 $available['jpeg_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/jpeg' ); 1393 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */ 1394 $available['png_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/png' ); 1395 /** This filter is documented in wp-includes/class-wp-image-editor-imagick.php */ 1396 $available['gif_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/gif' ); 1397 } 1398 1371 1399 $response = new WP_REST_Response( $available ); 1372 1400 -
trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php
r61429 r61703 64 64 ) 65 65 ); 66 67 if ( wp_is_client_side_media_processing_enabled() ) { 68 $valid_image_sizes = array_keys( wp_get_registered_image_subsizes() ); 69 // Special case to set 'original_image' in attachment metadata. 70 $valid_image_sizes[] = 'original'; 71 // Used for PDF thumbnails. 72 $valid_image_sizes[] = 'full'; 73 74 register_rest_route( 75 $this->namespace, 76 '/' . $this->rest_base . '/(?P<id>[\d]+)/sideload', 77 array( 78 array( 79 'methods' => WP_REST_Server::CREATABLE, 80 'callback' => array( $this, 'sideload_item' ), 81 'permission_callback' => array( $this, 'sideload_item_permissions_check' ), 82 'args' => array( 83 'id' => array( 84 'description' => __( 'Unique identifier for the attachment.' ), 85 'type' => 'integer', 86 ), 87 'image_size' => array( 88 'description' => __( 'Image size.' ), 89 'type' => 'string', 90 'enum' => $valid_image_sizes, 91 'required' => true, 92 ), 93 'convert_format' => array( 94 'type' => 'boolean', 95 'default' => true, 96 'description' => __( 'Whether to convert image formats.' ), 97 ), 98 ), 99 ), 100 'allow_batch' => $this->allow_batch, 101 'schema' => array( $this, 'get_public_item_schema' ), 102 ) 103 ); 104 } 105 } 106 107 /** 108 * Retrieves the query params for the attachments collection. 109 * 110 * @since 7.0.0 111 * 112 * @param string $method Optional. HTTP method of the request. 113 * The arguments for `CREATABLE` requests are 114 * checked for required values and may fall-back to a given default. 115 * Default WP_REST_Server::CREATABLE. 116 * @return array<string, array<string, mixed>> Endpoint arguments. 117 */ 118 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { 119 $args = parent::get_endpoint_args_for_item_schema( $method ); 120 121 if ( WP_REST_Server::CREATABLE === $method && wp_is_client_side_media_processing_enabled() ) { 122 $args['generate_sub_sizes'] = array( 123 'type' => 'boolean', 124 'default' => true, 125 'description' => __( 'Whether to generate image sub sizes.' ), 126 ); 127 $args['convert_format'] = array( 128 'type' => 'boolean', 129 'default' => true, 130 'description' => __( 'Whether to convert image formats.' ), 131 ); 132 } 133 134 return $args; 66 135 } 67 136 … … 193 262 * 194 263 * @since 4.7.0 264 * @since 7.0.0 Added `generate_sub_sizes` and `convert_format` parameters. 195 265 * 196 266 * @param WP_REST_Request $request Full details about the request. … … 206 276 } 207 277 278 // Handle generate_sub_sizes parameter. 279 if ( isset( $request['generate_sub_sizes'] ) && ! $request['generate_sub_sizes'] ) { 280 add_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 ); 281 add_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); 282 // Disable server-side EXIF rotation so the client can handle it. 283 // This preserves the original orientation value in the metadata. 284 add_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); 285 } 286 287 // Handle convert_format parameter. 288 if ( isset( $request['convert_format'] ) && ! $request['convert_format'] ) { 289 add_filter( 'image_editor_output_format', '__return_empty_array', 100 ); 290 } 291 208 292 $insert = $this->insert_attachment( $request ); 209 293 210 294 if ( is_wp_error( $insert ) ) { 295 $this->remove_client_side_media_processing_filters(); 211 296 return $insert; 212 297 } … … 226 311 227 312 if ( is_wp_error( $thumbnail_update ) ) { 313 $this->remove_client_side_media_processing_filters(); 228 314 return $thumbnail_update; 229 315 } … … 234 320 235 321 if ( is_wp_error( $meta_update ) ) { 322 $this->remove_client_side_media_processing_filters(); 236 323 return $meta_update; 237 324 } … … 242 329 243 330 if ( is_wp_error( $fields_update ) ) { 331 $this->remove_client_side_media_processing_filters(); 244 332 return $fields_update; 245 333 } … … 248 336 249 337 if ( is_wp_error( $terms_update ) ) { 338 $this->remove_client_side_media_processing_filters(); 250 339 return $terms_update; 251 340 } … … 284 373 wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) ); 285 374 375 $this->remove_client_side_media_processing_filters(); 376 286 377 $response = $this->prepare_item_for_response( $attachment, $request ); 287 378 $response = rest_ensure_response( $response ); … … 290 381 291 382 return $response; 383 } 384 385 /** 386 * Removes filters added for client-side media processing. 387 * 388 * @since 7.0.0 389 */ 390 private function remove_client_side_media_processing_filters() { 391 remove_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 ); 392 remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); 393 remove_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 ); 394 remove_filter( 'image_editor_output_format', '__return_empty_array', 100 ); 292 395 } 293 396 … … 989 1092 require_once ABSPATH . 'wp-admin/includes/image.php'; 990 1093 $data['missing_image_sizes'] = array_keys( wp_get_missing_image_subsizes( $post->ID ) ); 1094 1095 // Handle PDFs which don't use wp_get_missing_image_subsizes(). 1096 if ( empty( $data['missing_image_sizes'] ) && 'application/pdf' === get_post_mime_type( $post ) ) { 1097 $metadata = wp_get_attachment_metadata( $post->ID, true ); 1098 1099 if ( ! is_array( $metadata ) ) { 1100 $metadata = array(); 1101 } 1102 1103 $metadata['sizes'] = $metadata['sizes'] ?? array(); 1104 1105 $fallback_sizes = array( 1106 'thumbnail', 1107 'medium', 1108 'large', 1109 ); 1110 1111 // The filter might have been added by ::create_item(). 1112 remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 ); 1113 1114 /** This filter is documented in wp-admin/includes/image.php */ 1115 $fallback_sizes = apply_filters( 'fallback_intermediate_image_sizes', $fallback_sizes, $metadata ); 1116 1117 $registered_sizes = wp_get_registered_image_subsizes(); 1118 $merged_sizes = array_keys( array_intersect_key( $registered_sizes, array_flip( $fallback_sizes ) ) ); 1119 1120 $data['missing_image_sizes'] = array_values( array_diff( $merged_sizes, array_keys( $metadata['sizes'] ) ) ); 1121 } 1122 } 1123 1124 if ( in_array( 'filename', $fields, true ) ) { 1125 $data['filename'] = $this->get_attachment_filename( $post->ID ); 1126 } 1127 1128 if ( in_array( 'filesize', $fields, true ) ) { 1129 $data['filesize'] = $this->get_attachment_filesize( $post->ID ); 1130 } 1131 1132 if ( in_array( 'exif_orientation', $fields, true ) && wp_attachment_is_image( $post ) ) { 1133 $metadata = wp_get_attachment_metadata( $post->ID, true ); 1134 1135 // Default to 1 (no rotation needed) if orientation not set. 1136 $orientation = 1; 1137 1138 if ( 1139 is_array( $metadata ) && 1140 isset( $metadata['image_meta']['orientation'] ) && 1141 (int) $metadata['image_meta']['orientation'] > 0 1142 ) { 1143 $orientation = (int) $metadata['image_meta']['orientation']; 1144 } 1145 1146 $data['exif_orientation'] = $orientation; 991 1147 } 992 1148 … … 1156 1312 'type' => 'array', 1157 1313 'items' => array( 'type' => 'string' ), 1314 'context' => array( 'edit' ), 1315 'readonly' => true, 1316 ); 1317 1318 $schema['properties']['filename'] = array( 1319 'description' => __( 'Original attachment file name.' ), 1320 'type' => 'string', 1321 'context' => array( 'view', 'edit' ), 1322 'readonly' => true, 1323 ); 1324 1325 $schema['properties']['filesize'] = array( 1326 'description' => __( 'Attachment file size in bytes.' ), 1327 'type' => 'integer', 1328 'context' => array( 'view', 'edit' ), 1329 'readonly' => true, 1330 ); 1331 1332 $schema['properties']['exif_orientation'] = array( 1333 'description' => __( 'EXIF orientation value. Values 1-8 follow the EXIF specification, where 1 means no rotation needed.' ), 1334 'type' => 'integer', 1158 1335 'context' => array( 'edit' ), 1159 1336 'readonly' => true, … … 1725 1902 return $args; 1726 1903 } 1904 1905 /** 1906 * Gets the attachment's original file name. 1907 * 1908 * @since 7.0.0 1909 * 1910 * @param int $attachment_id Attachment ID. 1911 * @return string|null Attachment file name, or null if not found. 1912 */ 1913 protected function get_attachment_filename( int $attachment_id ): ?string { 1914 $path = wp_get_original_image_path( $attachment_id ); 1915 1916 if ( $path ) { 1917 return wp_basename( $path ); 1918 } 1919 1920 $path = get_attached_file( $attachment_id ); 1921 1922 if ( $path ) { 1923 return wp_basename( $path ); 1924 } 1925 1926 return null; 1927 } 1928 1929 /** 1930 * Gets the attachment's file size in bytes. 1931 * 1932 * @since 7.0.0 1933 * 1934 * @param int $attachment_id Attachment ID. 1935 * @return int|null Attachment file size in bytes, or null if not available. 1936 */ 1937 protected function get_attachment_filesize( int $attachment_id ): ?int { 1938 $meta = wp_get_attachment_metadata( $attachment_id ); 1939 1940 if ( isset( $meta['filesize'] ) ) { 1941 return $meta['filesize']; 1942 } 1943 1944 $original_path = wp_get_original_image_path( $attachment_id ); 1945 $attached_file = $original_path ? $original_path : get_attached_file( $attachment_id ); 1946 1947 if ( is_string( $attached_file ) && is_readable( $attached_file ) ) { 1948 return wp_filesize( $attached_file ); 1949 } 1950 1951 return null; 1952 } 1953 1954 /** 1955 * Checks if a given request has access to sideload a file. 1956 * 1957 * Sideloading a file for an existing attachment 1958 * requires both update and create permissions. 1959 * 1960 * @since 7.0.0 1961 * 1962 * @param WP_REST_Request $request Full details about the request. 1963 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise. 1964 */ 1965 public function sideload_item_permissions_check( $request ) { 1966 return $this->edit_media_item_permissions_check( $request ); 1967 } 1968 1969 /** 1970 * Side-loads a media file without creating a new attachment. 1971 * 1972 * @since 7.0.0 1973 * 1974 * @param WP_REST_Request $request Full details about the request. 1975 * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure. 1976 */ 1977 public function sideload_item( WP_REST_Request $request ) { 1978 $attachment_id = $request['id']; 1979 1980 $post = $this->get_post( $attachment_id ); 1981 1982 if ( is_wp_error( $post ) ) { 1983 return $post; 1984 } 1985 1986 if ( 1987 ! wp_attachment_is_image( $post ) && 1988 ! wp_attachment_is( 'pdf', $post ) 1989 ) { 1990 return new WP_Error( 1991 'rest_post_invalid_id', 1992 __( 'Invalid post ID. Only images and PDFs can be sideloaded.' ), 1993 array( 'status' => 400 ) 1994 ); 1995 } 1996 1997 if ( isset( $request['convert_format'] ) && ! $request['convert_format'] ) { 1998 // Prevent image conversion as that is done client-side. 1999 add_filter( 'image_editor_output_format', '__return_empty_array', 100 ); 2000 } 2001 2002 // Get the file via $_FILES or raw data. 2003 $files = $request->get_file_params(); 2004 $headers = $request->get_headers(); 2005 2006 /* 2007 * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts. 2008 * See /wp-includes/functions.php. 2009 * With the following filter we can work around this safeguard. 2010 */ 2011 $attachment_filename = get_attached_file( $attachment_id, true ); 2012 $attachment_filename = $attachment_filename ? wp_basename( $attachment_filename ) : null; 2013 2014 $filter_filename = static function ( $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number ) use ( $attachment_filename ) { 2015 return self::filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ); 2016 }; 2017 2018 add_filter( 'wp_unique_filename', $filter_filename, 10, 6 ); 2019 2020 $parent_post = get_post_parent( $attachment_id ); 2021 2022 $time = null; 2023 2024 // Matches logic in media_handle_upload(). 2025 // The post date doesn't usually matter for pages, so don't backdate this upload. 2026 if ( $parent_post && 'page' !== $parent_post->post_type && ! str_starts_with( $parent_post->post_date, '0000-00-00' ) ) { 2027 $time = $parent_post->post_date; 2028 } 2029 2030 if ( ! empty( $files ) ) { 2031 $file = $this->upload_from_file( $files, $headers, $time ); 2032 } else { 2033 $file = $this->upload_from_data( $request->get_body(), $headers, $time ); 2034 } 2035 2036 remove_filter( 'wp_unique_filename', $filter_filename ); 2037 remove_filter( 'image_editor_output_format', '__return_empty_array', 100 ); 2038 2039 if ( is_wp_error( $file ) ) { 2040 return $file; 2041 } 2042 2043 $type = $file['type']; 2044 $path = $file['file']; 2045 2046 $image_size = $request['image_size']; 2047 2048 $metadata = wp_get_attachment_metadata( $attachment_id, true ); 2049 2050 if ( ! $metadata ) { 2051 $metadata = array(); 2052 } 2053 2054 if ( 'original' === $image_size ) { 2055 $metadata['original_image'] = wp_basename( $path ); 2056 } else { 2057 $metadata['sizes'] = $metadata['sizes'] ?? array(); 2058 2059 $size = wp_getimagesize( $path ); 2060 2061 $metadata['sizes'][ $image_size ] = array( 2062 'width' => $size ? $size[0] : 0, 2063 'height' => $size ? $size[1] : 0, 2064 'file' => wp_basename( $path ), 2065 'mime-type' => $type, 2066 'filesize' => wp_filesize( $path ), 2067 ); 2068 } 2069 2070 wp_update_attachment_metadata( $attachment_id, $metadata ); 2071 2072 $response_request = new WP_REST_Request( 2073 WP_REST_Server::READABLE, 2074 rest_get_route_for_post( $attachment_id ) 2075 ); 2076 2077 $response_request['context'] = 'edit'; 2078 2079 if ( isset( $request['_fields'] ) ) { 2080 $response_request['_fields'] = $request['_fields']; 2081 } 2082 2083 $response = $this->prepare_item_for_response( get_post( $attachment_id ), $response_request ); 2084 2085 $response->header( 'Location', rest_url( rest_get_route_for_post( $attachment_id ) ) ); 2086 2087 return $response; 2088 } 2089 2090 /** 2091 * Filters wp_unique_filename during sideloads. 2092 * 2093 * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts. 2094 * Adding this closure to the filter helps work around this safeguard. 2095 * 2096 * Example: when uploading myphoto.jpeg, WordPress normally creates myphoto-150x150.jpeg, 2097 * and when uploading myphoto-150x150.jpeg, it will be renamed to myphoto-150x150-1.jpeg 2098 * However, here it is desired not to add the suffix in order to maintain the same 2099 * naming convention as if the file was uploaded regularly. 2100 * 2101 * @since 7.0.0 2102 * 2103 * @link https://github.com/WordPress/wordpress-develop/blob/30954f7ac0840cfdad464928021d7f380940c347/src/wp-includes/functions.php#L2576-L2582 2104 * 2105 * @param string $filename Unique file name. 2106 * @param string $dir Directory path. 2107 * @param int|string $number The highest number that was used to make the file name unique 2108 * or an empty string if unused. 2109 * @param string|null $attachment_filename Original attachment file name. 2110 * @return string Filtered file name. 2111 */ 2112 private static function filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ) { 2113 if ( empty( $number ) || ! $attachment_filename ) { 2114 return $filename; 2115 } 2116 2117 $ext = pathinfo( $filename, PATHINFO_EXTENSION ); 2118 $name = pathinfo( $filename, PATHINFO_FILENAME ); 2119 $orig_name = pathinfo( $attachment_filename, PATHINFO_FILENAME ); 2120 2121 if ( ! $ext || ! $name ) { 2122 return $filename; 2123 } 2124 2125 $matches = array(); 2126 if ( preg_match( '/(.*)(-\d+x\d+)-' . $number . '$/', $name, $matches ) ) { 2127 $filename_without_suffix = $matches[1] . $matches[2] . ".$ext"; 2128 if ( $matches[1] === $orig_name && ! file_exists( "$dir/$filename_without_suffix" ) ) { 2129 return $filename_without_suffix; 2130 } 2131 } 2132 2133 return $filename; 2134 } 1727 2135 } -
trunk/src/wp-includes/script-loader.php
r61686 r61703 310 310 311 311 $scripts->add( $handle, $path, $dependencies, $package_data['version'], 1 ); 312 313 if ( ! empty( $package_data['module_dependencies'] ) ) { 314 $scripts->add_data( $handle, 'module_dependencies', $package_data['module_dependencies'] ); 315 } 312 316 313 317 if ( in_array( 'wp-i18n', $dependencies, true ) ) { -
trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php
r61065 r61703 1940 1940 $data = $response->get_data(); 1941 1941 $properties = $data['schema']['properties']; 1942 $this->assertCount( 29, $properties );1942 $this->assertCount( 32, $properties ); 1943 1943 $this->assertArrayHasKey( 'author', $properties ); 1944 1944 $this->assertArrayHasKey( 'alt_text', $properties ); 1945 $this->assertArrayHasKey( 'exif_orientation', $properties ); 1946 $this->assertArrayHasKey( 'filename', $properties ); 1947 $this->assertArrayHasKey( 'filesize', $properties ); 1945 1948 $this->assertArrayHasKey( 'caption', $properties ); 1946 1949 $this->assertArrayHasKey( 'raw', $properties['caption']['properties'] ); -
trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php
r61674 r61703 110 110 '/wp/v2/media/(?P<id>[\\d]+)/post-process', 111 111 '/wp/v2/media/(?P<id>[\\d]+)/edit', 112 '/wp/v2/media/(?P<id>[\\d]+)/sideload', 112 113 '/wp/v2/blocks', 113 114 '/wp/v2/blocks/(?P<id>[\d]+)', -
trunk/tests/qunit/fixtures/wp-api-generated.js
r61702 r61703 3148 3148 "description": "The ID for the associated post of the attachment.", 3149 3149 "type": "integer", 3150 "required": false 3151 }, 3152 "generate_sub_sizes": { 3153 "type": "boolean", 3154 "default": true, 3155 "description": "Whether to generate image sub sizes.", 3156 "required": false 3157 }, 3158 "convert_format": { 3159 "type": "boolean", 3160 "default": true, 3161 "description": "Whether to convert image formats.", 3150 3162 "required": false 3151 3163 } … … 3665 3677 ] 3666 3678 }, 3679 "/wp/v2/media/(?P<id>[\\d]+)/sideload": { 3680 "namespace": "wp/v2", 3681 "methods": [ 3682 "POST" 3683 ], 3684 "endpoints": [ 3685 { 3686 "methods": [ 3687 "POST" 3688 ], 3689 "args": { 3690 "id": { 3691 "description": "Unique identifier for the attachment.", 3692 "type": "integer", 3693 "required": false 3694 }, 3695 "image_size": { 3696 "description": "Image size.", 3697 "type": "string", 3698 "enum": [ 3699 "thumbnail", 3700 "medium", 3701 "medium_large", 3702 "large", 3703 "1536x1536", 3704 "2048x2048", 3705 "original", 3706 "full" 3707 ], 3708 "required": true 3709 }, 3710 "convert_format": { 3711 "type": "boolean", 3712 "default": true, 3713 "description": "Whether to convert image formats.", 3714 "required": false 3715 } 3716 } 3717 } 3718 ] 3719 }, 3667 3720 "/wp/v2/menu-items": { 3668 3721 "namespace": "wp/v2", … … 12701 12754 } 12702 12755 }, 12756 "image_sizes": { 12757 "thumbnail": { 12758 "width": 150, 12759 "height": 150, 12760 "crop": true 12761 }, 12762 "medium": { 12763 "width": 300, 12764 "height": 300, 12765 "crop": false 12766 }, 12767 "medium_large": { 12768 "width": 768, 12769 "height": 0, 12770 "crop": false 12771 }, 12772 "large": { 12773 "width": 1024, 12774 "height": 1024, 12775 "crop": false 12776 }, 12777 "1536x1536": { 12778 "width": 1536, 12779 "height": 1536, 12780 "crop": false 12781 }, 12782 "2048x2048": { 12783 "width": 2048, 12784 "height": 2048, 12785 "crop": false 12786 } 12787 }, 12788 "image_size_threshold": 2560, 12789 "image_output_formats": {}, 12790 "jpeg_interlaced": false, 12791 "png_interlaced": false, 12792 "gif_interlaced": false, 12703 12793 "site_logo": 0, 12704 12794 "site_icon": 0, … … 13513 13603 "post": null, 13514 13604 "source_url": "http://example.org/wp-content/uploads//tmp/canola.jpg", 13605 "filename": "canola.jpg", 13606 "filesize": null, 13515 13607 "_links": { 13516 13608 "self": [ … … 13590 13682 "media_details": {}, 13591 13683 "post": null, 13592 "source_url": "http://example.org/wp-content/uploads//tmp/canola.jpg" 13684 "source_url": "http://example.org/wp-content/uploads//tmp/canola.jpg", 13685 "filename": "canola.jpg", 13686 "filesize": null 13593 13687 }; 13594 13688 -
trunk/tools/gutenberg/copy-gutenberg-build.js
r61677 r61703 442 442 const assetData = parsePHPArray( match[ 1 ] ); 443 443 444 // For regular scripts, use dependencies as-is 445 // Keep dependencies array (don't use module_dependencies) 444 // For regular scripts, use dependencies as-is. 446 445 if ( ! assetData.dependencies ) { 447 446 assetData.dependencies = []; 448 447 } 449 450 // Remove module_dependencies if present (not used for regular scripts)451 delete assetData.module_dependencies;452 448 453 449 // Create entries for both minified and non-minified versions … … 921 917 const scriptsDest = path.join( wpIncludesDir, scriptsConfig.destination ); 922 918 923 // Transform function to remove source map comments from all JS files 919 // Transform function to remove source map comments from all JS files. 920 // Only match actual source map comments at the start of a line (possibly 921 // with whitespace), not occurrences inside string literals. 924 922 const removeSourceMaps = ( content ) => { 925 return content.replace( / \/\/# sourceMappingURL=.*$/gm, '' ).trimEnd();923 return content.replace( /^\s*\/\/# sourceMappingURL=.*$/gm, '' ).trimEnd(); 926 924 }; 927 925
Note: See TracChangeset
for help on using the changeset viewer.