Make WordPress Core

Changeset 62081


Ignore:
Timestamp:
03/20/2026 05:09:14 PM (5 months ago)
Author:
adamsilverstein
Message:

Media: Remove client-side media processing feature for now.

Punt the wasm-vips client-side media processing feature to a future release when it can include more features. The VIPS WASM worker adds too much build size overhead for the current value provided. Removes all PHP functions, REST API endpoints, cross-origin isolation infrastructure, VIPS script module handling, build configuration, and associated tests.

Props adamsilverstein, jorbin.
Fixes #64906.

Location:
trunk
Files:
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/Gruntfile.js

    r62079 r62081  
    660660                                                '**/*',
    661661                                                '!**/*.map',
    662                                                 // Skip non-minified VIPS files — they are ~16MB of inlined WASM
    663                                                 // with no debugging value over the minified versions.
    664                                                 '!vips/!(*.min).js',
     662                                                '!vips/**',
    665663                                        ],
    666664                                        dest: WORKING_DIR + 'wp-includes/js/dist/script-modules/',
  • trunk/src/wp-includes/default-filters.php

    r62058 r62081  
    679679add_filter( 'plupload_default_settings', 'wp_show_heic_upload_error' );
    680680
    681 // Client-side media processing.
    682 add_action( 'admin_init', 'wp_set_client_side_media_processing_flag' );
    683 // Cross-origin isolation for client-side media processing.
    684 add_action( 'load-post.php', 'wp_set_up_cross_origin_isolation' );
    685 add_action( 'load-post-new.php', 'wp_set_up_cross_origin_isolation' );
    686 add_action( 'load-site-editor.php', 'wp_set_up_cross_origin_isolation' );
    687 add_action( 'load-widgets.php', 'wp_set_up_cross_origin_isolation' );
    688681// Nav menu.
    689682add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 );
  • trunk/src/wp-includes/media-template.php

    r61757 r62081  
    157157        $class = 'media-modal wp-core-ui';
    158158
    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 
    165159        $alt_text_description = sprintf(
    166160                /* translators: 1: Link to tutorial, 2: Additional link attributes, 3: Accessibility text. */
     
    15891583         */
    15901584        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         }
    16291585}
  • trunk/src/wp-includes/media.php

    r62048 r62081  
    64016401}
    64026402
    6403 /**
    6404  * Checks whether client-side media processing is enabled.
    6405  *
    6406  * Client-side media processing uses the browser's capabilities to handle
    6407  * tasks like image resizing and compression before uploading to the server.
    6408  *
    6409  * @since 7.0.0
    6410  *
    6411  * @return bool Whether client-side media processing is enabled.
    6412  */
    6413 function wp_is_client_side_media_processing_enabled(): bool {
    6414         // This is due to SharedArrayBuffer requiring a secure context.
    6415         $host    = strtolower( (string) strtok( $_SERVER['HTTP_HOST'] ?? '', ':' ) );
    6416         $enabled = ( is_ssl() || 'localhost' === $host || str_ends_with( $host, '.localhost' ) );
    6417 
    6418         /**
    6419          * Filters whether client-side media processing is enabled.
    6420          *
    6421          * @since 7.0.0
    6422          *
    6423          * @param bool $enabled Whether client-side media processing is enabled. Default true if the page is served in a secure context.
    6424          */
    6425         return (bool) apply_filters( 'wp_client_side_media_processing_enabled', $enabled );
    6426 }
    6427 
    6428 /**
    6429  * Sets a global JS variable to indicate that client-side media processing is enabled.
    6430  *
    6431  * @since 7.0.0
    6432  */
    6433 function wp_set_client_side_media_processing_flag(): void {
    6434         if ( ! wp_is_client_side_media_processing_enabled() ) {
    6435                 return;
    6436         }
    6437 
    6438         wp_add_inline_script( 'wp-block-editor', 'window.__clientSideMediaProcessing = true;', 'before' );
    6439 
    6440         $chromium_version = wp_get_chromium_major_version();
    6441 
    6442         if ( null !== $chromium_version && $chromium_version >= 137 ) {
    6443                 wp_add_inline_script( 'wp-block-editor', 'window.__documentIsolationPolicy = true;', 'before' );
    6444         }
    6445 
    6446         /*
    6447          * Register the @wordpress/vips/worker script module as a dynamic dependency
    6448          * of the wp-upload-media classic script. This ensures it is included in the
    6449          * import map so that the dynamic import() in upload-media.js can resolve it.
    6450          */
    6451         wp_scripts()->add_data(
    6452                 'wp-upload-media',
    6453                 'module_dependencies',
    6454                 array( '@wordpress/vips/worker' )
    6455         );
    6456 }
    6457 
    6458 /**
    6459  * Returns the major Chrome/Chromium version from the current request's User-Agent.
    6460  *
    6461  * Matches all Chromium-based browsers (Chrome, Edge, Opera, Brave).
    6462  *
    6463  * @since 7.0.0
    6464  *
    6465  * @return int|null The major Chrome version, or null if not a Chromium browser.
    6466  */
    6467 function wp_get_chromium_major_version(): ?int {
    6468         if ( empty( $_SERVER['HTTP_USER_AGENT'] ) ) {
    6469                 return null;
    6470         }
    6471         if ( preg_match( '#Chrome/(\d+)#', $_SERVER['HTTP_USER_AGENT'], $matches ) ) {
    6472                 return (int) $matches[1];
    6473         }
    6474         return null;
    6475 }
    6476 
    6477 /**
    6478  * Enables cross-origin isolation in the block editor.
    6479  *
    6480  * Required for enabling SharedArrayBuffer for WebAssembly-based
    6481  * media processing in the editor. Uses Document-Isolation-Policy
    6482  * on supported browsers (Chromium 137+).
    6483  *
    6484  * Skips setup when a third-party page builder overrides the block
    6485  * editor via a custom `action` query parameter, as DIP would block
    6486  * same-origin iframe access that these editors rely on.
    6487  *
    6488  * @since 7.0.0
    6489  */
    6490 function wp_set_up_cross_origin_isolation(): void {
    6491         if ( ! wp_is_client_side_media_processing_enabled() ) {
    6492                 return;
    6493         }
    6494 
    6495         $screen = get_current_screen();
    6496 
    6497         if ( ! $screen ) {
    6498                 return;
    6499         }
    6500 
    6501         if ( ! $screen->is_block_editor() && 'site-editor' !== $screen->id && ! ( 'widgets' === $screen->id && wp_use_widgets_block_editor() ) ) {
    6502                 return;
    6503         }
    6504 
    6505         /*
    6506          * Skip when a third-party page builder overrides the block editor.
    6507          * DIP isolates the document into its own agent cluster,
    6508          * which blocks same-origin iframe access that these editors rely on.
    6509          */
    6510         if ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) {
    6511                 return;
    6512         }
    6513 
    6514         // Cross-origin isolation is not needed if users can't upload files anyway.
    6515         if ( ! current_user_can( 'upload_files' ) ) {
    6516                 return;
    6517         }
    6518 
    6519         wp_start_cross_origin_isolation_output_buffer();
    6520 }
    6521 
    6522 /**
    6523  * Sends the Document-Isolation-Policy header for cross-origin isolation.
    6524  *
    6525  * Uses an output buffer to add crossorigin="anonymous" where needed.
    6526  *
    6527  * @since 7.0.0
    6528  */
    6529 function wp_start_cross_origin_isolation_output_buffer(): void {
    6530         $chromium_version = wp_get_chromium_major_version();
    6531 
    6532         if ( null === $chromium_version || $chromium_version < 137 ) {
    6533                 return;
    6534         }
    6535 
    6536         ob_start(
    6537                 static function ( string $output ): string {
    6538                         header( 'Document-Isolation-Policy: isolate-and-credentialless' );
    6539 
    6540                         return wp_add_crossorigin_attributes( $output );
    6541                 }
    6542         );
    6543 }
    6544 
    6545 /**
    6546  * Adds crossorigin="anonymous" to relevant tags in the given HTML string.
    6547  *
    6548  * @since 7.0.0
    6549  *
    6550  * @param string $html HTML input.
    6551  * @return string Modified HTML.
    6552  */
    6553 function wp_add_crossorigin_attributes( string $html ): string {
    6554         $site_url = site_url();
    6555 
    6556         $processor = new WP_HTML_Tag_Processor( $html );
    6557 
    6558         // See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin.
    6559         $cross_origin_tag_attributes = array(
    6560                 'AUDIO'  => array( 'src' => false ),
    6561                 'LINK'   => array( 'href' => false ),
    6562                 'SCRIPT' => array( 'src' => false ),
    6563                 'VIDEO'  => array(
    6564                         'src'    => false,
    6565                         'poster' => false,
    6566                 ),
    6567                 'SOURCE' => array( 'src' => false ),
    6568         );
    6569 
    6570         while ( $processor->next_tag() ) {
    6571                 $tag = $processor->get_tag();
    6572 
    6573                 if ( ! isset( $cross_origin_tag_attributes[ $tag ] ) ) {
    6574                         continue;
    6575                 }
    6576 
    6577                 if ( 'AUDIO' === $tag || 'VIDEO' === $tag ) {
    6578                         $processor->set_bookmark( 'audio-video-parent' );
    6579                 }
    6580 
    6581                 $processor->set_bookmark( 'resume' );
    6582 
    6583                 $sought = false;
    6584 
    6585                 $crossorigin = $processor->get_attribute( 'crossorigin' );
    6586 
    6587                 $is_cross_origin = false;
    6588 
    6589                 foreach ( $cross_origin_tag_attributes[ $tag ] as $attr => $is_srcset ) {
    6590                         if ( $is_srcset ) {
    6591                                 $srcset = $processor->get_attribute( $attr );
    6592                                 if ( is_string( $srcset ) ) {
    6593                                         foreach ( explode( ',', $srcset ) as $candidate ) {
    6594                                                 $candidate_url = strtok( trim( $candidate ), ' ' );
    6595                                                 if ( is_string( $candidate_url ) && '' !== $candidate_url && ! str_starts_with( $candidate_url, $site_url ) && ! str_starts_with( $candidate_url, '/' ) ) {
    6596                                                         $is_cross_origin = true;
    6597                                                         break;
    6598                                                 }
    6599                                         }
    6600                                 }
    6601                         } else {
    6602                                 $url = $processor->get_attribute( $attr );
    6603                                 if ( is_string( $url ) && ! str_starts_with( $url, $site_url ) && ! str_starts_with( $url, '/' ) ) {
    6604                                         $is_cross_origin = true;
    6605                                 }
    6606                         }
    6607 
    6608                         if ( $is_cross_origin ) {
    6609                                 break;
    6610                         }
    6611                 }
    6612 
    6613                 if ( $is_cross_origin && ! is_string( $crossorigin ) ) {
    6614                         if ( 'SOURCE' === $tag ) {
    6615                                 $sought = $processor->seek( 'audio-video-parent' );
    6616 
    6617                                 if ( $sought ) {
    6618                                         $processor->set_attribute( 'crossorigin', 'anonymous' );
    6619                                 }
    6620                         } else {
    6621                                 $processor->set_attribute( 'crossorigin', 'anonymous' );
    6622                         }
    6623 
    6624                         if ( $sought ) {
    6625                                 $processor->seek( 'resume' );
    6626                                 $processor->release_bookmark( 'audio-video-parent' );
    6627                         }
    6628                 }
    6629         }
    6630 
    6631         return $processor->get_updated_html();
    6632 }
    6633 
  • trunk/src/wp-includes/rest-api/class-wp-rest-server.php

    r61878 r62081  
    13691369                );
    13701370
    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/media.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-gd.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-gd.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-gd.php */
    1396                         $available['gif_interlaced'] = (bool) apply_filters( 'image_save_progressive', false, 'image/gif' );
    1397                 }
    1398 
    13991371                $response = new WP_REST_Response( $available );
    14001372
  • trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

    r61982 r62081  
    6464                        )
    6565                );
    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                         // Client-side big image threshold: sideload the scaled version.
    74                         $valid_image_sizes[] = 'scaled';
    75 
    76                         register_rest_route(
    77                                 $this->namespace,
    78                                 '/' . $this->rest_base . '/(?P<id>[\d]+)/sideload',
    79                                 array(
    80                                         array(
    81                                                 'methods'             => WP_REST_Server::CREATABLE,
    82                                                 'callback'            => array( $this, 'sideload_item' ),
    83                                                 'permission_callback' => array( $this, 'sideload_item_permissions_check' ),
    84                                                 'args'                => array(
    85                                                         'id'             => array(
    86                                                                 'description' => __( 'Unique identifier for the attachment.' ),
    87                                                                 'type'        => 'integer',
    88                                                         ),
    89                                                         'image_size'     => array(
    90                                                                 'description' => __( 'Image size.' ),
    91                                                                 'type'        => 'string',
    92                                                                 'enum'        => $valid_image_sizes,
    93                                                                 'required'    => true,
    94                                                         ),
    95                                                         'convert_format' => array(
    96                                                                 'type'        => 'boolean',
    97                                                                 'default'     => true,
    98                                                                 'description' => __( 'Whether to convert image formats.' ),
    99                                                         ),
    100                                                 ),
    101                                         ),
    102                                         'allow_batch' => $this->allow_batch,
    103                                         'schema'      => array( $this, 'get_public_item_schema' ),
    104                                 )
    105                         );
    106 
    107                         register_rest_route(
    108                                 $this->namespace,
    109                                 '/' . $this->rest_base . '/(?P<id>[\d]+)/finalize',
    110                                 array(
    111                                         array(
    112                                                 'methods'             => WP_REST_Server::CREATABLE,
    113                                                 'callback'            => array( $this, 'finalize_item' ),
    114                                                 'permission_callback' => array( $this, 'edit_media_item_permissions_check' ),
    115                                                 'args'                => array(
    116                                                         'id' => array(
    117                                                                 'description' => __( 'Unique identifier for the attachment.' ),
    118                                                                 'type'        => 'integer',
    119                                                         ),
    120                                                 ),
    121                                         ),
    122                                         'allow_batch' => $this->allow_batch,
    123                                         'schema'      => array( $this, 'get_public_item_schema' ),
    124                                 )
    125                         );
    126                 }
    127         }
    128 
    129         /**
    130          * Retrieves the query params for the attachments collection.
    131          *
    132          * @since 7.0.0
    133          *
    134          * @param string $method Optional. HTTP method of the request.
    135          *                       The arguments for `CREATABLE` requests are
    136          *                       checked for required values and may fall-back to a given default.
    137          *                       Default WP_REST_Server::CREATABLE.
    138          * @return array<string, array<string, mixed>> Endpoint arguments.
    139          */
    140         public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
    141                 $args = parent::get_endpoint_args_for_item_schema( $method );
    142 
    143                 if ( WP_REST_Server::CREATABLE === $method && wp_is_client_side_media_processing_enabled() ) {
    144                         $args['generate_sub_sizes'] = array(
    145                                 'type'        => 'boolean',
    146                                 'default'     => true,
    147                                 'description' => __( 'Whether to generate image sub sizes.' ),
    148                         );
    149                         $args['convert_format']     = array(
    150                                 'type'        => 'boolean',
    151                                 'default'     => true,
    152                                 'description' => __( 'Whether to convert image formats.' ),
    153                         );
    154                 }
    155 
    156                 return $args;
    15766        }
    15867
     
    253162                $prevent_unsupported_uploads = apply_filters( 'wp_prevent_unsupported_mime_type_uploads', true, $files['file']['type'] ?? null );
    254163
    255                 // When the client handles image processing (generate_sub_sizes is false),
    256                 // skip the server-side image editor support check.
    257                 if ( false === $request['generate_sub_sizes'] ) {
    258                         $prevent_unsupported_uploads = false;
    259                 }
    260 
    261164                // If the upload is an image, check if the server can handle the mime type.
    262165                if (
     
    290193         *
    291194         * @since 4.7.0
    292          * @since 7.0.0 Added `generate_sub_sizes` and `convert_format` parameters.
    293195         *
    294196         * @param WP_REST_Request $request Full details about the request.
     
    304206                }
    305207
    306                 // Handle generate_sub_sizes parameter.
    307                 if ( false === $request['generate_sub_sizes'] ) {
    308                         add_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 );
    309                         add_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 );
    310                         // Disable server-side EXIF rotation so the client can handle it.
    311                         // This preserves the original orientation value in the metadata.
    312                         add_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 );
    313                 }
    314 
    315                 // Handle convert_format parameter.
    316                 if ( isset( $request['convert_format'] ) && ! $request['convert_format'] ) {
    317                         add_filter( 'image_editor_output_format', '__return_empty_array', 100 );
    318                 }
    319 
    320208                $insert = $this->insert_attachment( $request );
    321209
    322210                if ( is_wp_error( $insert ) ) {
    323                         $this->remove_client_side_media_processing_filters();
    324211                        return $insert;
    325212                }
     
    339226
    340227                        if ( is_wp_error( $thumbnail_update ) ) {
    341                                 $this->remove_client_side_media_processing_filters();
    342228                                return $thumbnail_update;
    343229                        }
     
    348234
    349235                        if ( is_wp_error( $meta_update ) ) {
    350                                 $this->remove_client_side_media_processing_filters();
    351236                                return $meta_update;
    352237                        }
     
    357242
    358243                if ( is_wp_error( $fields_update ) ) {
    359                         $this->remove_client_side_media_processing_filters();
    360244                        return $fields_update;
    361245                }
     
    364248
    365249                if ( is_wp_error( $terms_update ) ) {
    366                         $this->remove_client_side_media_processing_filters();
    367250                        return $terms_update;
    368251                }
     
    401284                wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $file ) );
    402285
    403                 $this->remove_client_side_media_processing_filters();
    404 
    405286                $response = $this->prepare_item_for_response( $attachment, $request );
    406287                $response = rest_ensure_response( $response );
     
    409290
    410291                return $response;
    411         }
    412 
    413         /**
    414          * Removes filters added for client-side media processing.
    415          *
    416          * @since 7.0.0
    417          */
    418         private function remove_client_side_media_processing_filters() {
    419                 remove_filter( 'intermediate_image_sizes_advanced', '__return_empty_array', 100 );
    420                 remove_filter( 'fallback_intermediate_image_sizes', '__return_empty_array', 100 );
    421                 remove_filter( 'wp_image_maybe_exif_rotate', '__return_false', 100 );
    422                 remove_filter( 'image_editor_output_format', '__return_empty_array', 100 );
    423292        }
    424293
     
    19881857                return null;
    19891858        }
    1990 
    1991         /**
    1992          * Checks if a given request has access to sideload a file.
    1993          *
    1994          * Sideloading a file for an existing attachment
    1995          * requires both update and create permissions.
    1996          *
    1997          * @since 7.0.0
    1998          *
    1999          * @param WP_REST_Request $request Full details about the request.
    2000          * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
    2001          */
    2002         public function sideload_item_permissions_check( $request ) {
    2003                 return $this->edit_media_item_permissions_check( $request );
    2004         }
    2005 
    2006         /**
    2007          * Side-loads a media file without creating a new attachment.
    2008          *
    2009          * @since 7.0.0
    2010          *
    2011          * @param WP_REST_Request $request Full details about the request.
    2012          * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
    2013          */
    2014         public function sideload_item( WP_REST_Request $request ) {
    2015                 $attachment_id = $request['id'];
    2016 
    2017                 $post = $this->get_post( $attachment_id );
    2018 
    2019                 if ( is_wp_error( $post ) ) {
    2020                         return $post;
    2021                 }
    2022 
    2023                 if (
    2024                         ! wp_attachment_is_image( $post ) &&
    2025                         ! wp_attachment_is( 'pdf', $post )
    2026                 ) {
    2027                         return new WP_Error(
    2028                                 'rest_post_invalid_id',
    2029                                 __( 'Invalid post ID. Only images and PDFs can be sideloaded.' ),
    2030                                 array( 'status' => 400 )
    2031                         );
    2032                 }
    2033 
    2034                 if ( isset( $request['convert_format'] ) && ! $request['convert_format'] ) {
    2035                         // Prevent image conversion as that is done client-side.
    2036                         add_filter( 'image_editor_output_format', '__return_empty_array', 100 );
    2037                 }
    2038 
    2039                 // Get the file via $_FILES or raw data.
    2040                 $files   = $request->get_file_params();
    2041                 $headers = $request->get_headers();
    2042 
    2043                 /*
    2044                  * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts.
    2045                  * See /wp-includes/functions.php.
    2046                  * With the following filter we can work around this safeguard.
    2047                  */
    2048                 $attachment_filename = get_attached_file( $attachment_id, true );
    2049                 $attachment_filename = $attachment_filename ? wp_basename( $attachment_filename ) : null;
    2050 
    2051                 $filter_filename = static function ( $filename, $ext, $dir, $unique_filename_callback, $alt_filenames, $number ) use ( $attachment_filename ) {
    2052                         return self::filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename );
    2053                 };
    2054 
    2055                 add_filter( 'wp_unique_filename', $filter_filename, 10, 6 );
    2056 
    2057                 $parent_post = get_post_parent( $attachment_id );
    2058 
    2059                 $time = null;
    2060 
    2061                 // Matches logic in media_handle_upload().
    2062                 // The post date doesn't usually matter for pages, so don't backdate this upload.
    2063                 if ( $parent_post && 'page' !== $parent_post->post_type && ! str_starts_with( $parent_post->post_date, '0000-00-00' ) ) {
    2064                         $time = $parent_post->post_date;
    2065                 }
    2066 
    2067                 if ( ! empty( $files ) ) {
    2068                         $file = $this->upload_from_file( $files, $headers, $time );
    2069                 } else {
    2070                         $file = $this->upload_from_data( $request->get_body(), $headers, $time );
    2071                 }
    2072 
    2073                 remove_filter( 'wp_unique_filename', $filter_filename );
    2074                 remove_filter( 'image_editor_output_format', '__return_empty_array', 100 );
    2075 
    2076                 if ( is_wp_error( $file ) ) {
    2077                         return $file;
    2078                 }
    2079 
    2080                 $type = $file['type'];
    2081                 $path = $file['file'];
    2082 
    2083                 $image_size = $request['image_size'];
    2084 
    2085                 $metadata = wp_get_attachment_metadata( $attachment_id, true );
    2086 
    2087                 if ( ! $metadata ) {
    2088                         $metadata = array();
    2089                 }
    2090 
    2091                 if ( 'original' === $image_size ) {
    2092                         $metadata['original_image'] = wp_basename( $path );
    2093                 } elseif ( 'scaled' === $image_size ) {
    2094                         // The current attached file is the original; record it as original_image.
    2095                         $current_file = get_attached_file( $attachment_id, true );
    2096 
    2097                         if ( ! $current_file ) {
    2098                                 return new WP_Error(
    2099                                         'rest_sideload_no_attached_file',
    2100                                         __( 'Unable to retrieve the attached file for this attachment.' ),
    2101                                         array( 'status' => 404 )
    2102                                 );
    2103                         }
    2104 
    2105                         $metadata['original_image'] = wp_basename( $current_file );
    2106 
    2107                         // Validate the scaled image before updating the attached file.
    2108                         $size     = wp_getimagesize( $path );
    2109                         $filesize = wp_filesize( $path );
    2110 
    2111                         if ( ! $size || ! $filesize ) {
    2112                                 return new WP_Error(
    2113                                         'rest_sideload_invalid_image',
    2114                                         __( 'Unable to read the scaled image file.' ),
    2115                                         array( 'status' => 500 )
    2116                                 );
    2117                         }
    2118 
    2119                         // Update the attached file to point to the scaled version.
    2120                         if (
    2121                                 get_attached_file( $attachment_id, true ) !== $path &&
    2122                                 ! update_attached_file( $attachment_id, $path )
    2123                         ) {
    2124                                 return new WP_Error(
    2125                                         'rest_sideload_update_attached_file_failed',
    2126                                         __( 'Unable to update the attached file for this attachment.' ),
    2127                                         array( 'status' => 500 )
    2128                                 );
    2129                         }
    2130 
    2131                         $metadata['width']    = $size[0];
    2132                         $metadata['height']   = $size[1];
    2133                         $metadata['filesize'] = $filesize;
    2134                         $metadata['file']     = _wp_relative_upload_path( $path );
    2135                 } else {
    2136                         $metadata['sizes'] = $metadata['sizes'] ?? array();
    2137 
    2138                         $size = wp_getimagesize( $path );
    2139 
    2140                         $metadata['sizes'][ $image_size ] = array(
    2141                                 'width'     => $size ? $size[0] : 0,
    2142                                 'height'    => $size ? $size[1] : 0,
    2143                                 'file'      => wp_basename( $path ),
    2144                                 'mime-type' => $type,
    2145                                 'filesize'  => wp_filesize( $path ),
    2146                         );
    2147                 }
    2148 
    2149                 wp_update_attachment_metadata( $attachment_id, $metadata );
    2150 
    2151                 $response_request = new WP_REST_Request(
    2152                         WP_REST_Server::READABLE,
    2153                         rest_get_route_for_post( $attachment_id )
    2154                 );
    2155 
    2156                 $response_request['context'] = 'edit';
    2157 
    2158                 if ( isset( $request['_fields'] ) ) {
    2159                         $response_request['_fields'] = $request['_fields'];
    2160                 }
    2161 
    2162                 $response = $this->prepare_item_for_response( get_post( $attachment_id ), $response_request );
    2163 
    2164                 $response->header( 'Location', rest_url( rest_get_route_for_post( $attachment_id ) ) );
    2165 
    2166                 return $response;
    2167         }
    2168 
    2169         /**
    2170          * Filters wp_unique_filename during sideloads.
    2171          *
    2172          * wp_unique_filename() will always add numeric suffix if the name looks like a sub-size to avoid conflicts.
    2173          * Adding this closure to the filter helps work around this safeguard.
    2174          *
    2175          * Example: when uploading myphoto.jpeg, WordPress normally creates myphoto-150x150.jpeg,
    2176          * and when uploading myphoto-150x150.jpeg, it will be renamed to myphoto-150x150-1.jpeg
    2177          * However, here it is desired not to add the suffix in order to maintain the same
    2178          * naming convention as if the file was uploaded regularly.
    2179          *
    2180          * @since 7.0.0
    2181          *
    2182          * @link https://github.com/WordPress/wordpress-develop/blob/30954f7ac0840cfdad464928021d7f380940c347/src/wp-includes/functions.php#L2576-L2582
    2183          *
    2184          * @param string      $filename            Unique file name.
    2185          * @param string      $dir                 Directory path.
    2186          * @param int|string  $number              The highest number that was used to make the file name unique
    2187          *                                         or an empty string if unused.
    2188          * @param string|null $attachment_filename Original attachment file name.
    2189          * @return string Filtered file name.
    2190          */
    2191         private static function filter_wp_unique_filename( $filename, $dir, $number, $attachment_filename ) {
    2192                 if ( ! is_int( $number ) || ! $attachment_filename ) {
    2193                         return $filename;
    2194                 }
    2195 
    2196                 $ext       = pathinfo( $filename, PATHINFO_EXTENSION );
    2197                 $name      = pathinfo( $filename, PATHINFO_FILENAME );
    2198                 $orig_name = pathinfo( $attachment_filename, PATHINFO_FILENAME );
    2199 
    2200                 if ( ! $ext || ! $name ) {
    2201                         return $filename;
    2202                 }
    2203 
    2204                 $matches = array();
    2205                 if ( preg_match( '/(.*)-(\d+x\d+|scaled)-' . $number . '$/', $name, $matches ) ) {
    2206                         $filename_without_suffix = $matches[1] . '-' . $matches[2] . ".$ext";
    2207                         if ( $matches[1] === $orig_name && ! file_exists( "$dir/$filename_without_suffix" ) ) {
    2208                                 return $filename_without_suffix;
    2209                         }
    2210                 }
    2211 
    2212                 return $filename;
    2213         }
    2214 
    2215         /**
    2216          * Finalizes an attachment after client-side media processing.
    2217          *
    2218          * Triggers the 'wp_generate_attachment_metadata' filter so that
    2219          * server-side plugins can process the attachment after all client-side
    2220          * operations (upload, thumbnail generation, sideloads) are complete.
    2221          *
    2222          * @since 7.0.0
    2223          *
    2224          * @param WP_REST_Request $request Full details about the request.
    2225          * @return WP_REST_Response|WP_Error Response object on success, WP_Error object on failure.
    2226          */
    2227         public function finalize_item( WP_REST_Request $request ) {
    2228                 $attachment_id = $request['id'];
    2229 
    2230                 $post = $this->get_post( $attachment_id );
    2231                 if ( is_wp_error( $post ) ) {
    2232                         return $post;
    2233                 }
    2234 
    2235                 $metadata = wp_get_attachment_metadata( $attachment_id );
    2236                 if ( ! is_array( $metadata ) ) {
    2237                         $metadata = array();
    2238                 }
    2239 
    2240                 /** This filter is documented in wp-admin/includes/image.php */
    2241                 $metadata = apply_filters( 'wp_generate_attachment_metadata', $metadata, $attachment_id, 'update' );
    2242 
    2243                 wp_update_attachment_metadata( $attachment_id, $metadata );
    2244 
    2245                 $response_request = new WP_REST_Request(
    2246                         WP_REST_Server::READABLE,
    2247                         rest_get_route_for_post( $attachment_id )
    2248                 );
    2249 
    2250                 $response_request['context'] = 'edit';
    2251 
    2252                 if ( isset( $request['_fields'] ) ) {
    2253                         $response_request['_fields'] = $request['_fields'];
    2254                 }
    2255 
    2256                 return $this->prepare_item_for_response( $post, $response_request );
    2257         }
    22581859}
  • trunk/src/wp-includes/script-modules.php

    r62072 r62081  
    191191                }
    192192
    193                 // VIPS files are always minified — the non-minified versions are not
    194                 // shipped because they are ~10MB of inlined WASM with no debugging value.
    195                 if ( str_starts_with( $file_name, 'vips/' ) ) {
    196                         $file_name = str_replace( '.js', '.min.js', $file_name );
    197                 } elseif ( '' !== $suffix ) {
     193                if ( '' !== $suffix ) {
    198194                        $file_name = str_replace( '.js', $suffix . '.js', $file_name );
    199195                }
  • trunk/tests/phpunit/tests/media/wpCrossOriginIsolation.php

    r62048 r62081  
    1 <?php
    2 
    3 /**
    4  * Tests for cross-origin isolation functions.
    5  *
    6  * @group media
    7  * @covers ::wp_set_up_cross_origin_isolation
    8  * @covers ::wp_start_cross_origin_isolation_output_buffer
    9  * @covers ::wp_is_client_side_media_processing_enabled
    10  */
    11 class Tests_Media_wpCrossOriginIsolation extends WP_UnitTestCase {
    12 
    13         /**
    14          * Original HTTP_USER_AGENT value.
    15          */
    16         private ?string $original_user_agent;
    17 
    18         /**
    19          * Original HTTP_HOST value.
    20          */
    21         private ?string $original_http_host;
    22 
    23         /**
    24          * Original HTTPS value.
    25          */
    26         private ?string $original_https;
    27 
    28         /**
    29          * Original $_GET['action'] value.
    30          */
    31         private ?string $original_get_action;
    32 
    33         public function set_up() {
    34                 parent::set_up();
    35                 $this->original_user_agent = $_SERVER['HTTP_USER_AGENT'] ?? null;
    36                 $this->original_http_host  = $_SERVER['HTTP_HOST'] ?? null;
    37                 $this->original_https      = $_SERVER['HTTPS'] ?? null;
    38                 $this->original_get_action = $_GET['action'] ?? null;
    39         }
    40 
    41         public function tear_down() {
    42                 if ( null === $this->original_user_agent ) {
    43                         unset( $_SERVER['HTTP_USER_AGENT'] );
    44                 } else {
    45                         $_SERVER['HTTP_USER_AGENT'] = $this->original_user_agent;
    46                 }
    47 
    48                 if ( null === $this->original_http_host ) {
    49                         unset( $_SERVER['HTTP_HOST'] );
    50                 } else {
    51                         $_SERVER['HTTP_HOST'] = $this->original_http_host;
    52                 }
    53 
    54                 if ( null === $this->original_https ) {
    55                         unset( $_SERVER['HTTPS'] );
    56                 } else {
    57                         $_SERVER['HTTPS'] = $this->original_https;
    58                 }
    59 
    60                 if ( null === $this->original_get_action ) {
    61                         unset( $_GET['action'] );
    62                 } else {
    63                         $_GET['action'] = $this->original_get_action;
    64                 }
    65 
    66                 // Clean up any output buffers started during tests.
    67                 while ( ob_get_level() > 1 ) {
    68                         ob_end_clean();
    69                 }
    70 
    71                 remove_all_filters( 'wp_client_side_media_processing_enabled' );
    72                 parent::tear_down();
    73         }
    74 
    75         /**
    76          * @ticket 64766
    77          */
    78         public function test_returns_early_when_client_side_processing_disabled() {
    79                 add_filter( 'wp_client_side_media_processing_enabled', '__return_false' );
    80 
    81                 // Should not error or start an output buffer.
    82                 $level_before = ob_get_level();
    83                 wp_set_up_cross_origin_isolation();
    84                 $level_after = ob_get_level();
    85 
    86                 $this->assertSame( $level_before, $level_after );
    87         }
    88 
    89         /**
    90          * @ticket 64766
    91          */
    92         public function test_returns_early_when_no_screen() {
    93                 // No screen is set, so it should return early.
    94                 $level_before = ob_get_level();
    95                 wp_set_up_cross_origin_isolation();
    96                 $level_after = ob_get_level();
    97 
    98                 $this->assertSame( $level_before, $level_after );
    99         }
    100 
    101         /**
    102          * This test must run in a separate process because the output buffer
    103          * callback sends HTTP headers via header(), which would fail in the
    104          * main PHPUnit process where output has already started.
    105          *
    106          * @ticket 64766
    107          *
    108          * @runInSeparateProcess
    109          * @preserveGlobalState disabled
    110          */
    111         public function test_starts_output_buffer_for_chrome_137() {
    112                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    113 
    114                 $level_before = ob_get_level();
    115                 wp_start_cross_origin_isolation_output_buffer();
    116                 $level_after = ob_get_level();
    117 
    118                 $this->assertSame( $level_before + 1, $level_after, 'Output buffer should be started for Chrome 137.' );
    119 
    120                 ob_end_clean();
    121         }
    122 
    123         /**
    124          * @ticket 64766
    125          */
    126         public function test_does_not_start_output_buffer_for_chrome_136() {
    127                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36';
    128 
    129                 $level_before = ob_get_level();
    130                 wp_start_cross_origin_isolation_output_buffer();
    131                 $level_after = ob_get_level();
    132 
    133                 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Chrome < 137.' );
    134         }
    135 
    136         /**
    137          * @ticket 64766
    138          */
    139         public function test_does_not_start_output_buffer_for_firefox() {
    140                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0';
    141 
    142                 $level_before = ob_get_level();
    143                 wp_start_cross_origin_isolation_output_buffer();
    144                 $level_after = ob_get_level();
    145 
    146                 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Firefox.' );
    147         }
    148 
    149         /**
    150          * @ticket 64766
    151          */
    152         public function test_does_not_start_output_buffer_for_safari() {
    153                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15';
    154 
    155                 $level_before = ob_get_level();
    156                 wp_start_cross_origin_isolation_output_buffer();
    157                 $level_after = ob_get_level();
    158 
    159                 $this->assertSame( $level_before, $level_after, 'Output buffer should not be started for Safari.' );
    160         }
    161 
    162         /**
    163          * @ticket 64803
    164          */
    165         public function test_client_side_processing_disabled_on_non_secure_origin() {
    166                 $_SERVER['HTTP_HOST'] = 'example.com';
    167                 $_SERVER['HTTPS']     = '';
    168 
    169                 $this->assertFalse(
    170                         wp_is_client_side_media_processing_enabled(),
    171                         'Client-side media processing should be disabled on non-secure, non-localhost origins.'
    172                 );
    173         }
    174 
    175         /**
    176          * @ticket 64803
    177          */
    178         public function test_client_side_processing_enabled_on_localhost() {
    179                 $_SERVER['HTTP_HOST'] = 'localhost';
    180                 $_SERVER['HTTPS']     = '';
    181 
    182                 $this->assertTrue(
    183                         wp_is_client_side_media_processing_enabled(),
    184                         'Client-side media processing should be enabled on localhost.'
    185                 );
    186         }
    187 
    188         /**
    189          * Verifies that cross-origin elements get crossorigin="anonymous" added.
    190          *
    191          * @ticket 64766
    192          *
    193          * @runInSeparateProcess
    194          * @preserveGlobalState disabled
    195          *
    196          * @dataProvider data_elements_that_should_get_crossorigin
    197          *
    198          * @param string $html HTML input to process.
    199          */
    200         public function test_output_buffer_adds_crossorigin( $html ) {
    201                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    202 
    203                 ob_start();
    204 
    205                 wp_start_cross_origin_isolation_output_buffer();
    206                 echo $html;
    207 
    208                 ob_end_flush();
    209                 $output = ob_get_clean();
    210 
    211                 $this->assertStringContainsString( 'crossorigin="anonymous"', $output );
    212         }
    213 
    214         /**
    215          * Data provider for elements that should receive crossorigin="anonymous".
    216          *
    217          * @return array[]
    218          */
    219         public function data_elements_that_should_get_crossorigin() {
    220                 return array(
    221                         'cross-origin script'              => array(
    222                                 '<script src="https://external.example.com/script.js"></script>',
    223                         ),
    224                         'cross-origin audio'               => array(
    225                                 '<audio src="https://external.example.com/audio.mp3"></audio>',
    226                         ),
    227                         'cross-origin video'               => array(
    228                                 '<video src="https://external.example.com/video.mp4"></video>',
    229                         ),
    230                         'cross-origin link stylesheet'     => array(
    231                                 '<link rel="stylesheet" href="https://external.example.com/style.css" />',
    232                         ),
    233                         'cross-origin source inside video' => array(
    234                                 '<video><source src="https://external.example.com/video.mp4" type="video/mp4" /></video>',
    235                         ),
    236                 );
    237         }
    238 
    239         /**
    240          * Verifies that certain elements do not get crossorigin="anonymous" added.
    241          *
    242          * Images are excluded because under Document-Isolation-Policy:
    243          * isolate-and-credentialless, the browser handles cross-origin images
    244          * in credentialless mode without needing explicit CORS headers.
    245          *
    246          * @ticket 64766
    247          *
    248          * @runInSeparateProcess
    249          * @preserveGlobalState disabled
    250          *
    251          * @dataProvider data_elements_that_should_not_get_crossorigin
    252          *
    253          * @param string $html HTML input to process.
    254          */
    255         public function test_output_buffer_does_not_add_crossorigin( $html ) {
    256                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    257 
    258                 ob_start();
    259 
    260                 wp_start_cross_origin_isolation_output_buffer();
    261                 echo $html;
    262 
    263                 ob_end_flush();
    264                 $output = ob_get_clean();
    265 
    266                 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output );
    267         }
    268 
    269         /**
    270          * Data provider for elements that should not receive crossorigin="anonymous".
    271          *
    272          * @return array[]
    273          */
    274         public function data_elements_that_should_not_get_crossorigin() {
    275                 return array(
    276                         'cross-origin img'                        => array(
    277                                 '<img src="https://external.example.com/image.jpg" />',
    278                         ),
    279                         'cross-origin img with srcset'            => array(
    280                                 '<img src="https://external.example.com/image.jpg" srcset="https://external.example.com/image-2x.jpg 2x" />',
    281                         ),
    282                         'link with cross-origin imagesrcset only' => array(
    283                                 '<link rel="preload" as="image" imagesrcset="https://external.example.com/image.jpg 1x" href="/local-fallback.jpg" />',
    284                         ),
    285                         'relative URL script'                     => array(
    286                                 '<script src="/wp-includes/js/wp-embed.min.js"></script>',
    287                         ),
    288                 );
    289         }
    290 
    291         /**
    292          * Same-origin URLs should not get crossorigin="anonymous".
    293          *
    294          * Uses site_url() at runtime since the test domain varies by CI config.
    295          *
    296          * @ticket 64766
    297          *
    298          * @runInSeparateProcess
    299          * @preserveGlobalState disabled
    300          */
    301         public function test_output_buffer_does_not_add_crossorigin_to_same_origin() {
    302                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    303 
    304                 ob_start();
    305 
    306                 wp_start_cross_origin_isolation_output_buffer();
    307                 echo '<script src="' . site_url( '/wp-includes/js/wp-embed.min.js' ) . '"></script>';
    308 
    309                 ob_end_flush();
    310                 $output = ob_get_clean();
    311 
    312                 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output );
    313         }
    314 
    315         /**
    316          * Elements that already have a crossorigin attribute should not be modified.
    317          *
    318          * @ticket 64766
    319          *
    320          * @runInSeparateProcess
    321          * @preserveGlobalState disabled
    322          */
    323         public function test_output_buffer_does_not_override_existing_crossorigin() {
    324                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    325 
    326                 ob_start();
    327 
    328                 wp_start_cross_origin_isolation_output_buffer();
    329                 echo '<script src="https://external.example.com/script.js" crossorigin="use-credentials"></script>';
    330 
    331                 ob_end_flush();
    332                 $output = ob_get_clean();
    333 
    334                 $this->assertStringContainsString( 'crossorigin="use-credentials"', $output, 'Existing crossorigin attribute should not be overridden.' );
    335                 $this->assertStringNotContainsString( 'crossorigin="anonymous"', $output );
    336         }
    337 
    338         /**
    339          * Multiple tags in the same output should each be handled correctly.
    340          *
    341          * @ticket 64766
    342          *
    343          * @runInSeparateProcess
    344          * @preserveGlobalState disabled
    345          */
    346         public function test_output_buffer_handles_mixed_tags() {
    347                 $_SERVER['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36';
    348 
    349                 ob_start();
    350 
    351                 wp_start_cross_origin_isolation_output_buffer();
    352                 echo '<img src="https://external.example.com/image.jpg" />';
    353                 echo '<script src="https://external.example.com/script.js"></script>';
    354                 echo '<audio src="https://external.example.com/audio.mp3"></audio>';
    355 
    356                 ob_end_flush();
    357                 $output = ob_get_clean();
    358 
    359                 // IMG should NOT have crossorigin.
    360                 $this->assertStringContainsString( '<img src="https://external.example.com/image.jpg" />', $output, 'IMG should not be modified.' );
    361 
    362                 // Script and audio should have crossorigin.
    363                 $this->assertSame( 2, substr_count( $output, 'crossorigin="anonymous"' ), 'Script and audio should both get crossorigin, but not img.' );
    364         }
    365 }
  • trunk/tests/phpunit/tests/media/wpGetChromiumMajorVersion.php

    r61844 r62081  
    1 <?php
    2 
    3 /**
    4  * Tests for the `wp_get_chromium_major_version()` function.
    5  *
    6  * @group media
    7  * @covers ::wp_get_chromium_major_version
    8  */
    9 class Tests_Media_wpGetChromiumMajorVersion extends WP_UnitTestCase {
    10 
    11         /**
    12          * Original HTTP_USER_AGENT value.
    13          *
    14          * @var string|null
    15          */
    16         private $original_user_agent;
    17 
    18         public function set_up() {
    19                 parent::set_up();
    20                 $this->original_user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : null;
    21         }
    22 
    23         public function tear_down() {
    24                 if ( null === $this->original_user_agent ) {
    25                         unset( $_SERVER['HTTP_USER_AGENT'] );
    26                 } else {
    27                         $_SERVER['HTTP_USER_AGENT'] = $this->original_user_agent;
    28                 }
    29                 parent::tear_down();
    30         }
    31 
    32         /**
    33          * @ticket 64766
    34          */
    35         public function test_returns_null_when_no_user_agent() {
    36                 unset( $_SERVER['HTTP_USER_AGENT'] );
    37                 $this->assertNull( wp_get_chromium_major_version() );
    38         }
    39 
    40         /**
    41          * @ticket 64766
    42          *
    43          * @dataProvider data_user_agents
    44          *
    45          * @param string   $user_agent The user agent string.
    46          * @param int|null $expected   The expected Chromium major version, or null.
    47          */
    48         public function test_returns_expected_version( $user_agent, $expected ) {
    49                 $_SERVER['HTTP_USER_AGENT'] = $user_agent;
    50                 $this->assertSame( $expected, wp_get_chromium_major_version() );
    51         }
    52 
    53         /**
    54          * Data provider for test_returns_expected_version.
    55          *
    56          * @return array[]
    57          */
    58         public function data_user_agents() {
    59                 return array(
    60                         'empty user agent'   => array( '', null ),
    61                         'Firefox'            => array( 'Mozilla/5.0 (Windows NT 10.0; rv:128.0) Gecko/20100101 Firefox/128.0', null ),
    62                         'Safari'             => array( 'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15', null ),
    63                         'Chrome 137'         => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36', 137 ),
    64                         'Edge 137'           => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0', 137 ),
    65                         'Opera (Chrome 136)' => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 OPR/122.0.0.0', 136 ),
    66                         'Chrome 100'         => array( 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36', 100 ),
    67                 );
    68         }
    69 }
  • trunk/tests/phpunit/tests/rest-api/rest-attachments-controller.php

    r61984 r62081  
    193193
    194194                parent::tear_down();
    195         }
    196 
    197         /**
    198          * Enables client-side media processing and reinitializes the REST server
    199          * so that the sideload and finalize routes are registered.
    200          */
    201         private function enable_client_side_media_processing(): void {
    202                 add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
    203 
    204                 global $wp_rest_server;
    205                 $wp_rest_server = new Spy_REST_Server();
    206                 do_action( 'rest_api_init', $wp_rest_server );
    207195        }
    208196
     
    29432931
    29442932        /**
    2945          * Test that unsupported image type check is skipped when not generating sub-sizes.
    2946          *
    2947          * When the client handles image processing (generate_sub_sizes is false),
    2948          * the server should not check image editor support.
    2949          *
    2950          * Tests the permissions check directly with file params set, since the core
    2951          * check uses get_file_params() which is only populated for multipart uploads.
    2952          *
    2953          * @ticket 64836
    2954          */
    2955         public function test_upload_unsupported_image_type_skipped_when_not_generating_sub_sizes() {
    2956                 wp_set_current_user( self::$author_id );
    2957 
    2958                 add_filter( 'wp_image_editors', '__return_empty_array' );
    2959 
    2960                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    2961                 $request->set_file_params(
    2962                         array(
    2963                                 'file' => array(
    2964                                         'name'     => 'avif-lossy.avif',
    2965                                         'type'     => 'image/avif',
    2966                                         'tmp_name' => self::$test_avif_file,
    2967                                         'error'    => 0,
    2968                                         'size'     => filesize( self::$test_avif_file ),
    2969                                 ),
    2970                         )
    2971                 );
    2972                 $request->set_param( 'generate_sub_sizes', false );
    2973 
    2974                 $controller = new WP_REST_Attachments_Controller( 'attachment' );
    2975                 $result     = $controller->create_item_permissions_check( $request );
    2976 
    2977                 // Should pass because generate_sub_sizes is false (client handles processing).
    2978                 $this->assertTrue( $result );
    2979         }
    2980 
    2981         /**
    29822933         * Test that unsupported image type check is enforced when generating sub-sizes.
    29832934         *
     
    32413192                $this->assertIsArray( $captured_data, 'Data passed to wp_insert_attachment should be an array' );
    32423193        }
    3243 
    3244         /**
    3245          * Tests sideloading a scaled image for an existing attachment.
    3246          *
    3247          * @ticket 64737
    3248          * @requires function imagejpeg
    3249          */
    3250         public function test_sideload_scaled_image() {
    3251                 $this->enable_client_side_media_processing();
    3252 
    3253                 wp_set_current_user( self::$author_id );
    3254 
    3255                 // First, create an attachment.
    3256                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3257                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3258                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3259                 $request->set_body( file_get_contents( self::$test_file ) );
    3260                 $response      = rest_get_server()->dispatch( $request );
    3261                 $data          = $response->get_data();
    3262                 $attachment_id = $data['id'];
    3263 
    3264                 $this->assertSame( 201, $response->get_status() );
    3265 
    3266                 $original_file = get_attached_file( $attachment_id, true );
    3267 
    3268                 // Sideload a "scaled" version of the image.
    3269                 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" );
    3270                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3271                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola-scaled.jpg' );
    3272                 $request->set_param( 'image_size', 'scaled' );
    3273                 $request->set_body( file_get_contents( self::$test_file ) );
    3274                 $response = rest_get_server()->dispatch( $request );
    3275 
    3276                 $this->assertSame( 200, $response->get_status(), 'Sideloading scaled image should succeed.' );
    3277 
    3278                 $metadata = wp_get_attachment_metadata( $attachment_id );
    3279 
    3280                 // The original file should now be recorded as original_image.
    3281                 $this->assertArrayHasKey( 'original_image', $metadata, 'Metadata should contain original_image.' );
    3282                 $this->assertSame( wp_basename( $original_file ), $metadata['original_image'], 'original_image should be the basename of the original attached file.' );
    3283 
    3284                 // The attached file should now point to the scaled version.
    3285                 $new_file = get_attached_file( $attachment_id, true );
    3286                 $this->assertStringContainsString( 'scaled', wp_basename( $new_file ), 'Attached file should now be the scaled version.' );
    3287 
    3288                 // Metadata should have width, height, filesize, and file updated.
    3289                 $this->assertArrayHasKey( 'width', $metadata, 'Metadata should contain width.' );
    3290                 $this->assertArrayHasKey( 'height', $metadata, 'Metadata should contain height.' );
    3291                 $this->assertArrayHasKey( 'filesize', $metadata, 'Metadata should contain filesize.' );
    3292                 $this->assertArrayHasKey( 'file', $metadata, 'Metadata should contain file.' );
    3293                 $this->assertStringContainsString( 'scaled', $metadata['file'], 'Metadata file should reference the scaled version.' );
    3294                 $this->assertGreaterThan( 0, $metadata['width'], 'Width should be positive.' );
    3295                 $this->assertGreaterThan( 0, $metadata['height'], 'Height should be positive.' );
    3296                 $this->assertGreaterThan( 0, $metadata['filesize'], 'Filesize should be positive.' );
    3297         }
    3298 
    3299         /**
    3300          * Tests that sideloading scaled image requires authentication.
    3301          *
    3302          * @ticket 64737
    3303          * @requires function imagejpeg
    3304          */
    3305         public function test_sideload_scaled_image_requires_auth() {
    3306                 $this->enable_client_side_media_processing();
    3307 
    3308                 wp_set_current_user( self::$author_id );
    3309 
    3310                 // Create an attachment.
    3311                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3312                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3313                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3314                 $request->set_body( file_get_contents( self::$test_file ) );
    3315                 $response      = rest_get_server()->dispatch( $request );
    3316                 $attachment_id = $response->get_data()['id'];
    3317 
    3318                 // Try sideloading without authentication.
    3319                 wp_set_current_user( 0 );
    3320 
    3321                 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" );
    3322                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3323                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola-scaled.jpg' );
    3324                 $request->set_param( 'image_size', 'scaled' );
    3325                 $request->set_body( file_get_contents( self::$test_file ) );
    3326                 $response = rest_get_server()->dispatch( $request );
    3327 
    3328                 $this->assertErrorResponse( 'rest_cannot_edit_image', $response, 401 );
    3329         }
    3330 
    3331         /**
    3332          * Tests that the sideload endpoint includes 'scaled' in the image_size enum.
    3333          *
    3334          * @ticket 64737
    3335          */
    3336         public function test_sideload_route_includes_scaled_enum() {
    3337                 $this->enable_client_side_media_processing();
    3338 
    3339                 $server = rest_get_server();
    3340                 $routes = $server->get_routes();
    3341 
    3342                 $endpoint = '/wp/v2/media/(?P<id>[\d]+)/sideload';
    3343                 $this->assertArrayHasKey( $endpoint, $routes, 'Sideload route should exist.' );
    3344 
    3345                 $route    = $routes[ $endpoint ];
    3346                 $endpoint = $route[0];
    3347                 $args     = $endpoint['args'];
    3348 
    3349                 $param_name = 'image_size';
    3350                 $this->assertArrayHasKey( $param_name, $args, 'Route should have image_size arg.' );
    3351                 $this->assertContains( 'scaled', $args[ $param_name ]['enum'], 'image_size enum should include scaled.' );
    3352         }
    3353 
    3354         /**
    3355          * Tests the filter_wp_unique_filename method handles the -scaled suffix.
    3356          *
    3357          * @ticket 64737
    3358          * @requires function imagejpeg
    3359          */
    3360         public function test_sideload_scaled_unique_filename() {
    3361                 $this->enable_client_side_media_processing();
    3362 
    3363                 wp_set_current_user( self::$author_id );
    3364 
    3365                 // Create an attachment.
    3366                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3367                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3368                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3369                 $request->set_body( file_get_contents( self::$test_file ) );
    3370                 $response      = rest_get_server()->dispatch( $request );
    3371                 $attachment_id = $response->get_data()['id'];
    3372 
    3373                 // Sideload with the -scaled suffix.
    3374                 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/sideload" );
    3375                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3376                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola-scaled.jpg' );
    3377                 $request->set_param( 'image_size', 'scaled' );
    3378                 $request->set_body( file_get_contents( self::$test_file ) );
    3379                 $response = rest_get_server()->dispatch( $request );
    3380 
    3381                 $this->assertSame( 200, $response->get_status(), 'Sideloading scaled image should succeed.' );
    3382 
    3383                 // The filename should retain the -scaled suffix without numeric disambiguation.
    3384                 $new_file = get_attached_file( $attachment_id, true );
    3385                 $basename = wp_basename( $new_file );
    3386                 $this->assertMatchesRegularExpression( '/canola-scaled\.jpg$/', $basename, 'Scaled filename should not have numeric suffix appended.' );
    3387         }
    3388 
    3389         /**
    3390          * Tests that sideloading a scaled image for a different attachment retains the numeric suffix
    3391          * when a file with the same name already exists on disk.
    3392          *
    3393          * @ticket 64737
    3394          * @requires function imagejpeg
    3395          */
    3396         public function test_sideload_scaled_unique_filename_conflict() {
    3397                 $this->enable_client_side_media_processing();
    3398 
    3399                 wp_set_current_user( self::$author_id );
    3400 
    3401                 // Create the first attachment.
    3402                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3403                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3404                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3405                 $request->set_body( file_get_contents( self::$test_file ) );
    3406                 $response        = rest_get_server()->dispatch( $request );
    3407                 $attachment_id_a = $response->get_data()['id'];
    3408 
    3409                 // Sideload a scaled image for attachment A, creating canola-scaled.jpg on disk.
    3410                 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id_a}/sideload" );
    3411                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3412                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola-scaled.jpg' );
    3413                 $request->set_param( 'image_size', 'scaled' );
    3414                 $request->set_body( file_get_contents( self::$test_file ) );
    3415                 $response = rest_get_server()->dispatch( $request );
    3416 
    3417                 $this->assertSame( 200, $response->get_status(), 'First sideload should succeed.' );
    3418 
    3419                 // Create a second, different attachment.
    3420                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3421                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3422                 $request->set_header( 'Content-Disposition', 'attachment; filename=other.jpg' );
    3423                 $request->set_body( file_get_contents( self::$test_file ) );
    3424                 $response        = rest_get_server()->dispatch( $request );
    3425                 $attachment_id_b = $response->get_data()['id'];
    3426 
    3427                 // Sideload scaled for attachment B using the same filename that already exists on disk.
    3428                 $request = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id_b}/sideload" );
    3429                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3430                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola-scaled.jpg' );
    3431                 $request->set_param( 'image_size', 'scaled' );
    3432                 $request->set_body( file_get_contents( self::$test_file ) );
    3433                 $response = rest_get_server()->dispatch( $request );
    3434 
    3435                 $this->assertSame( 200, $response->get_status(), 'Second sideload should succeed.' );
    3436 
    3437                 // The filename should have a numeric suffix since the base name does not match this attachment.
    3438                 $new_file = get_attached_file( $attachment_id_b, true );
    3439                 $basename = wp_basename( $new_file );
    3440                 $this->assertMatchesRegularExpression( '/canola-scaled-\d+\.jpg$/', $basename, 'Scaled filename should have numeric suffix when file conflicts with a different attachment.' );
    3441         }
    3442 
    3443         /**
    3444          * Tests that the finalize endpoint triggers wp_generate_attachment_metadata.
    3445          *
    3446          * @ticket 62243
    3447          * @covers WP_REST_Attachments_Controller::finalize_item
    3448          * @requires function imagejpeg
    3449          */
    3450         public function test_finalize_item(): void {
    3451                 $this->enable_client_side_media_processing();
    3452 
    3453                 wp_set_current_user( self::$author_id );
    3454 
    3455                 // Create an attachment.
    3456                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3457                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3458                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3459                 $request->set_body( (string) file_get_contents( self::$test_file ) );
    3460                 $response      = rest_get_server()->dispatch( $request );
    3461                 $attachment_id = $response->get_data()['id'];
    3462 
    3463                 $this->assertSame( 201, $response->get_status() );
    3464 
    3465                 // Track whether wp_generate_attachment_metadata filter fires.
    3466                 $filter_metadata = null;
    3467                 $filter_id       = null;
    3468                 $filter_context  = null;
    3469                 add_filter(
    3470                         'wp_generate_attachment_metadata',
    3471                         function ( array $metadata, int $id, string $context ) use ( &$filter_metadata, &$filter_id, &$filter_context ) {
    3472                                 $filter_metadata = $metadata;
    3473                                 $filter_id       = $id;
    3474                                 $filter_context  = $context;
    3475                                 $metadata['foo'] = 'bar';
    3476                                 return $metadata;
    3477                         },
    3478                         10,
    3479                         3
    3480                 );
    3481 
    3482                 // Call the finalize endpoint.
    3483                 $request  = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/finalize" );
    3484                 $response = rest_get_server()->dispatch( $request );
    3485 
    3486                 $this->assertSame( 200, $response->get_status(), 'Finalize endpoint should return 200.' );
    3487                 $this->assertIsArray( $filter_metadata );
    3488                 $this->assertStringContainsString( 'canola', $filter_metadata['file'], 'Expected the canola image to have been had its metadata updated.' );
    3489                 $this->assertSame( $attachment_id, $filter_id, 'Expected the post ID to be passed to the filter.' );
    3490                 $this->assertSame( 'update', $filter_context, 'Filter context should be "update".' );
    3491                 $resulting_metadata = wp_get_attachment_metadata( $attachment_id );
    3492                 $this->assertIsArray( $resulting_metadata );
    3493                 $this->assertArrayHasKey( 'foo', $resulting_metadata, 'Expected new metadata key to have been added.' );
    3494                 $this->assertSame( 'bar', $resulting_metadata['foo'], 'Expected filtered metadata to be updated.' );
    3495         }
    3496 
    3497         /**
    3498          * Tests that the finalize endpoint requires authentication.
    3499          *
    3500          * @ticket 62243
    3501          * @covers WP_REST_Attachments_Controller::finalize_item
    3502          * @requires function imagejpeg
    3503          */
    3504         public function test_finalize_item_requires_auth(): void {
    3505                 $this->enable_client_side_media_processing();
    3506 
    3507                 wp_set_current_user( self::$author_id );
    3508 
    3509                 // Create an attachment.
    3510                 $request = new WP_REST_Request( 'POST', '/wp/v2/media' );
    3511                 $request->set_header( 'Content-Type', 'image/jpeg' );
    3512                 $request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
    3513                 $request->set_body( (string) file_get_contents( self::$test_file ) );
    3514                 $response      = rest_get_server()->dispatch( $request );
    3515                 $attachment_id = $response->get_data()['id'];
    3516 
    3517                 // Try finalizing without authentication.
    3518                 wp_set_current_user( 0 );
    3519 
    3520                 $request  = new WP_REST_Request( 'POST', "/wp/v2/media/{$attachment_id}/finalize" );
    3521                 $response = rest_get_server()->dispatch( $request );
    3522 
    3523                 $this->assertErrorResponse( 'rest_cannot_edit_image', $response, 401 );
    3524         }
    3525 
    3526         /**
    3527          * Tests that the finalize endpoint returns error for invalid attachment ID.
    3528          *
    3529          * @ticket 62243
    3530          * @covers WP_REST_Attachments_Controller::finalize_item
    3531          */
    3532         public function test_finalize_item_invalid_id(): void {
    3533                 $this->enable_client_side_media_processing();
    3534 
    3535                 wp_set_current_user( self::$author_id );
    3536 
    3537                 $invalid_id = PHP_INT_MAX;
    3538                 $this->assertNull( get_post( $invalid_id ), 'Expected invalid ID to not exist for an existing post.' );
    3539                 $request  = new WP_REST_Request( 'POST', "/wp/v2/media/$invalid_id/finalize" );
    3540                 $response = rest_get_server()->dispatch( $request );
    3541 
    3542                 $this->assertErrorResponse( 'rest_post_invalid_id', $response, 404 );
    3543         }
    35443194}
  • trunk/tests/phpunit/tests/rest-api/rest-schema-setup.php

    r61982 r62081  
    1616        public function set_up() {
    1717                parent::set_up();
    18 
    19                 // Ensure client-side media processing is enabled so the sideload route is registered.
    20                 add_filter( 'wp_client_side_media_processing_enabled', '__return_true' );
    2118
    2219                /** @var WP_REST_Server $wp_rest_server */
     
    113110                        '/wp/v2/media/(?P<id>[\\d]+)/post-process',
    114111                        '/wp/v2/media/(?P<id>[\\d]+)/edit',
    115                         '/wp/v2/media/(?P<id>[\\d]+)/sideload',
    116                         '/wp/v2/media/(?P<id>[\\d]+)/finalize',
    117112                        '/wp/v2/blocks',
    118113                        '/wp/v2/blocks/(?P<id>[\d]+)',
  • trunk/tests/phpunit/tests/script-modules/wpScriptModules.php

    r61794 r62081  
    19051905
    19061906        /**
    1907          * Tests that VIPS script modules always use minified file paths.
    1908          *
    1909          * Non-minified VIPS files are not shipped because they are ~10MB of
    1910          * inlined WASM with no debugging value, so the registration should
    1911          * always point to the .min.js variants.
    1912          *
    1913          * @ticket 64734
     1907         * Tests that VIPS script modules are not registered in Core.
     1908         *
     1909         * The wasm-vips library is plugin-only and should not be included
     1910         * in WordPress Core builds due to its large size (~16MB per file).
     1911         *
     1912         * @ticket 64906
    19141913         *
    19151914         * @covers ::wp_default_script_modules
    19161915         */
    1917         public function test_vips_script_modules_always_use_minified_paths() {
     1916        public function test_vips_script_modules_not_registered_in_core() {
    19181917                wp_default_script_modules();
    19191918                wp_enqueue_script_module( '@wordpress/vips/loader' );
     
    19211920                $actual = get_echo( array( wp_script_modules(), 'print_enqueued_script_modules' ) );
    19221921
    1923                 $this->assertStringContainsString( 'vips/loader.min.js', $actual );
    1924                 $this->assertStringNotContainsString( 'vips/loader.js"', $actual );
     1922                $this->assertStringNotContainsString( 'vips', $actual );
    19251923        }
    19261924
  • trunk/tests/qunit/fixtures/wp-api-generated.js

    r62058 r62081  
    31483148                            "description": "The ID for the associated post of the attachment.",
    31493149                            "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.",
    31623150                            "required": false
    31633151                        }
     
    36773665            ]
    36783666        },
    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                                 "scaled"
    3708                             ],
    3709                             "required": true
    3710                         },
    3711                         "convert_format": {
    3712                             "type": "boolean",
    3713                             "default": true,
    3714                             "description": "Whether to convert image formats.",
    3715                             "required": false
    3716                         }
    3717                     }
    3718                 }
    3719             ]
    3720         },
    3721         "/wp/v2/media/(?P<id>[\\d]+)/finalize": {
    3722             "namespace": "wp/v2",
    3723             "methods": [
    3724                 "POST"
    3725             ],
    3726             "endpoints": [
    3727                 {
    3728                     "methods": [
    3729                         "POST"
    3730                     ],
    3731                     "args": {
    3732                         "id": {
    3733                             "description": "Unique identifier for the attachment.",
    3734                             "type": "integer",
    3735                             "required": false
    3736                         }
    3737                     }
    3738                 }
    3739             ]
    3740         },
    37413667        "/wp/v2/menu-items": {
    37423668            "namespace": "wp/v2",
     
    1277512701        }
    1277612702    },
    12777     "image_sizes": {
    12778         "thumbnail": {
    12779             "width": 150,
    12780             "height": 150,
    12781             "crop": true
    12782         },
    12783         "medium": {
    12784             "width": 300,
    12785             "height": 300,
    12786             "crop": false
    12787         },
    12788         "medium_large": {
    12789             "width": 768,
    12790             "height": 0,
    12791             "crop": false
    12792         },
    12793         "large": {
    12794             "width": 1024,
    12795             "height": 1024,
    12796             "crop": false
    12797         },
    12798         "1536x1536": {
    12799             "width": 1536,
    12800             "height": 1536,
    12801             "crop": false
    12802         },
    12803         "2048x2048": {
    12804             "width": 2048,
    12805             "height": 2048,
    12806             "crop": false
    12807         }
    12808     },
    12809     "image_size_threshold": 2560,
    12810     "image_output_formats": {},
    12811     "jpeg_interlaced": false,
    12812     "png_interlaced": false,
    12813     "gif_interlaced": false,
    1281412703    "site_logo": 0,
    1281512704    "site_icon": 0,
  • trunk/tools/gutenberg/copy.js

    r62073 r62081  
    260260
    261261                        if ( entry.isDirectory() ) {
     262                                // Skip plugin-only packages (e.g., vips/wasm) that should not be in Core.
     263                                if ( entry.name === 'vips' ) {
     264                                        continue;
     265                                }
    262266                                processDirectory( fullPath, baseDir );
    263267                        } else if ( entry.name.endsWith( '.min.asset.php' ) ) {
     
    343347                        if ( ! assetData.dependencies ) {
    344348                                assetData.dependencies = [];
     349                        }
     350
     351                        // Strip plugin-only module dependencies (e.g., vips) that are not in Core.
     352                        if ( Array.isArray( assetData.module_dependencies ) ) {
     353                                assetData.module_dependencies =
     354                                        assetData.module_dependencies.filter(
     355                                                ( dep ) =>
     356                                                        ! ( dep.id || dep ).startsWith(
     357                                                                '@wordpress/vips'
     358                                                        )
     359                                        );
    345360                        }
    346361
Note: See TracChangeset for help on using the changeset viewer.