Make WordPress Core

Changeset 62081


Ignore:
Timestamp:
03/20/2026 05:09:14 PM (8 days 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="https://core.trac.wordpress.org/local-fallback.jpg" />',
    284             ),
    285             'relative URL script'                     => array(
    286                 '<script src="https://core.trac.wordpress.org/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.