Make WordPress Core


Ignore:
Timestamp:
03/20/2026 05:09:14 PM (3 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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • 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}
Note: See TracChangeset for help on using the changeset viewer.