Skip to content

Commit 5231afc

Browse files
REST API: Expose output format and progressive flags on attachments.
Add two readonly fields to `WP_REST_Attachments_Controller`, available in the `edit` context alongside the existing `exif_orientation` field, for image attachments only: - `image_output_format` returns the resolved output MIME type when the `image_editor_output_format` filter maps the source MIME to a different one (for example JPEG to WebP). - `image_save_progressive` returns the boolean result of the `image_save_progressive` filter for the attachment's MIME type. See related Gutenberg pull request: WordPress/gutenberg#75793 and issue: WordPress/gutenberg#75784. Props westonruter. Fixes #65367. git-svn-id: https://develop.svn.wordpress.org/trunk@62617 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 80c5bd2 commit 5231afc

5 files changed

Lines changed: 218 additions & 22 deletions

File tree

src/wp-includes/rest-api/class-wp-rest-server.php

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,22 +1378,6 @@ public function get_index( $request ) {
13781378

13791379
/** This filter is documented in wp-admin/includes/image.php */
13801380
$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', 'image/heif' );
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' );
13971381
}
13981382

13991383
$response = new WP_REST_Response( $available );

src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,8 @@ public function prepare_item_for_response( $item, $request ) {
11561156

11571157
$response = parent::prepare_item_for_response( $post, $request );
11581158
$fields = $this->get_fields_for_response( $request );
1159-
$data = $response->get_data();
1159+
/** @var array<string, mixed> $data */
1160+
$data = $response->get_data();
11601161

11611162
if ( in_array( 'description', $fields, true ) ) {
11621163
$data['description'] = array(
@@ -1297,6 +1298,40 @@ public function prepare_item_for_response( $item, $request ) {
12971298
$data['exif_orientation'] = $orientation;
12981299
}
12991300

1301+
if ( wp_attachment_is_image( $post ) ) {
1302+
$mime_type = get_post_mime_type( $post );
1303+
1304+
/*
1305+
* Per-file output format for images, evaluated with the real filename
1306+
* and MIME type so plugins filtering image_editor_output_format can
1307+
* make per-attachment decisions (e.g. JPEG -> WebP). Resolved the same
1308+
* way WP_Image_Editor::set_quality() resolves the output format.
1309+
*/
1310+
if ( in_array( 'image_output_format', $fields, true ) ) {
1311+
$filename = get_attached_file( $post->ID );
1312+
1313+
/** This filter is documented in wp-includes/media.php */
1314+
$output_formats = apply_filters(
1315+
'image_editor_output_format',
1316+
array( $mime_type => $mime_type ),
1317+
$filename ? $filename : '',
1318+
$mime_type
1319+
);
1320+
1321+
$output_mime = $output_formats[ $mime_type ] ?? $mime_type;
1322+
$data['image_output_format'] = ( $output_mime !== $mime_type ) ? $output_mime : null;
1323+
}
1324+
1325+
/*
1326+
* Per-file progressive/interlaced encoding flag for images, evaluated
1327+
* against the attachment's MIME type.
1328+
*/
1329+
if ( in_array( 'image_save_progressive', $fields, true ) ) {
1330+
/** This filter is documented in wp-includes/class-wp-image-editor-gd.php */
1331+
$data['image_save_progressive'] = (bool) apply_filters( 'image_save_progressive', false, $mime_type );
1332+
}
1333+
}
1334+
13001335
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
13011336

13021337
$data = $this->filter_response_by_context( $data, $context );
@@ -1487,6 +1522,20 @@ public function get_item_schema() {
14871522
'readonly' => true,
14881523
);
14891524

1525+
$schema['properties']['image_output_format'] = array(
1526+
'description' => __( 'The output MIME type this image should be converted to, based on the image_editor_output_format filter. Null if no conversion is needed.' ),
1527+
'type' => array( 'string', 'null' ),
1528+
'context' => array( 'edit' ),
1529+
'readonly' => true,
1530+
);
1531+
1532+
$schema['properties']['image_save_progressive'] = array(
1533+
'description' => __( 'Whether to use progressive/interlaced encoding when saving this image.' ),
1534+
'type' => 'boolean',
1535+
'context' => array( 'edit' ),
1536+
'readonly' => true,
1537+
);
1538+
14901539
unset( $schema['properties']['password'] );
14911540

14921541
$this->schema = $schema;

src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2393,6 +2393,13 @@ protected function get_available_actions( $post, $request ) {
23932393
* @since 4.7.0
23942394
*
23952395
* @return array Item schema data.
2396+
*
2397+
* @phpstan-return array{
2398+
* title: non-empty-string,
2399+
* type: non-empty-string,
2400+
* properties: array<string, array<string, mixed>>,
2401+
* ...
2402+
* }
23962403
*/
23972404
public function get_item_schema() {
23982405
if ( $this->schema ) {

tests/phpunit/tests/rest-api/rest-attachments-controller.php

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1952,10 +1952,12 @@ public function test_get_item_schema() {
19521952
$response = rest_get_server()->dispatch( $request );
19531953
$data = $response->get_data();
19541954
$properties = $data['schema']['properties'];
1955-
$this->assertCount( 32, $properties );
1955+
$this->assertCount( 34, $properties );
19561956
$this->assertArrayHasKey( 'author', $properties );
19571957
$this->assertArrayHasKey( 'alt_text', $properties );
19581958
$this->assertArrayHasKey( 'exif_orientation', $properties );
1959+
$this->assertArrayHasKey( 'image_output_format', $properties );
1960+
$this->assertArrayHasKey( 'image_save_progressive', $properties );
19591961
$this->assertArrayHasKey( 'filename', $properties );
19601962
$this->assertArrayHasKey( 'filesize', $properties );
19611963
$this->assertArrayHasKey( 'caption', $properties );
@@ -1993,6 +1995,164 @@ public function test_get_item_schema() {
19931995
$this->assertArrayHasKey( 'class_list', $properties );
19941996
}
19951997

1998+
/**
1999+
* Tests the image_output_format / image_save_progressive schema properties.
2000+
*
2001+
* @ticket 65367
2002+
*
2003+
* @covers WP_REST_Attachments_Controller::get_item_schema
2004+
*/
2005+
public function test_image_output_format_and_progressive_schema(): void {
2006+
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
2007+
$response = rest_get_server()->dispatch( $request );
2008+
$data = $response->get_data();
2009+
$properties = $data['schema']['properties'];
2010+
2011+
$this->assertArrayHasKey( 'image_output_format', $properties );
2012+
$this->assertSame( array( 'string', 'null' ), $properties['image_output_format']['type'] );
2013+
$this->assertSame( array( 'edit' ), $properties['image_output_format']['context'] );
2014+
$this->assertTrue( $properties['image_output_format']['readonly'] );
2015+
2016+
$this->assertArrayHasKey( 'image_save_progressive', $properties );
2017+
$this->assertSame( 'boolean', $properties['image_save_progressive']['type'] );
2018+
$this->assertSame( array( 'edit' ), $properties['image_save_progressive']['context'] );
2019+
$this->assertTrue( $properties['image_save_progressive']['readonly'] );
2020+
}
2021+
2022+
/**
2023+
* Verifies image_output_format is null by default (no conversion needed) and
2024+
* image_save_progressive defaults to false on a freshly uploaded JPEG.
2025+
*
2026+
* @ticket 65367
2027+
*
2028+
* @covers WP_REST_Attachments_Controller::create_item
2029+
* @covers WP_REST_Attachments_Controller::prepare_item_for_response
2030+
*/
2031+
public function test_image_output_format_and_progressive_defaults_in_create_response(): void {
2032+
wp_set_current_user( self::$superadmin_id );
2033+
2034+
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
2035+
$request->set_header( 'Content-Type', 'image/jpeg' );
2036+
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
2037+
$request->set_param( 'context', 'edit' );
2038+
$request->set_param( 'generate_sub_sizes', false );
2039+
$request->set_body( file_get_contents( DIR_TESTDATA . '/images/canola.jpg' ) );
2040+
2041+
$response = rest_get_server()->dispatch( $request );
2042+
$data = $response->get_data();
2043+
2044+
$this->assertSame( 201, $response->get_status() );
2045+
$this->assertArrayHasKey( 'image_output_format', $data );
2046+
$this->assertNull( $data['image_output_format'] );
2047+
$this->assertArrayHasKey( 'image_save_progressive', $data );
2048+
$this->assertFalse( $data['image_save_progressive'] );
2049+
}
2050+
2051+
/**
2052+
* Verifies image_output_format reflects an image_editor_output_format filter
2053+
* that remaps JPEG to WebP, and that the filter sees the real attached
2054+
* filename and MIME type.
2055+
*
2056+
* @ticket 65367
2057+
*
2058+
* @covers WP_REST_Attachments_Controller::prepare_item_for_response
2059+
*/
2060+
public function test_image_output_format_with_custom_filter(): void {
2061+
wp_set_current_user( self::$superadmin_id );
2062+
2063+
$attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg' );
2064+
2065+
$captured = array();
2066+
add_filter(
2067+
'image_editor_output_format',
2068+
static function ( $formats, $filename, $mime_type ) use ( &$captured ) {
2069+
$captured['filename'] = $filename;
2070+
$captured['mime_type'] = $mime_type;
2071+
$formats['image/jpeg'] = 'image/webp';
2072+
return $formats;
2073+
},
2074+
10,
2075+
3
2076+
);
2077+
2078+
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
2079+
$request->set_param( 'context', 'edit' );
2080+
2081+
$response = rest_get_server()->dispatch( $request );
2082+
$data = $response->get_data();
2083+
2084+
$this->assertSame( 200, $response->get_status() );
2085+
$this->assertArrayHasKey( 'image_output_format', $data );
2086+
$this->assertSame( 'image/webp', $data['image_output_format'] );
2087+
2088+
// The filter must be invoked with the real attached filename and MIME type.
2089+
$this->assertStringEndsWith( '.jpg', (string) $captured['filename'] );
2090+
$this->assertSame( 'image/jpeg', $captured['mime_type'] );
2091+
}
2092+
2093+
/**
2094+
* Verifies image_save_progressive surfaces the filter result for the
2095+
* attachment's MIME type.
2096+
*
2097+
* @ticket 65367
2098+
*
2099+
* @covers WP_REST_Attachments_Controller::prepare_item_for_response
2100+
*/
2101+
public function test_image_save_progressive_with_custom_filter(): void {
2102+
wp_set_current_user( self::$superadmin_id );
2103+
2104+
$attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg' );
2105+
2106+
add_filter(
2107+
'image_save_progressive',
2108+
static function ( $progressive, $mime_type ) {
2109+
return 'image/jpeg' === $mime_type;
2110+
},
2111+
10,
2112+
2
2113+
);
2114+
2115+
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
2116+
$request->set_param( 'context', 'edit' );
2117+
2118+
$response = rest_get_server()->dispatch( $request );
2119+
$data = $response->get_data();
2120+
2121+
$this->assertSame( 200, $response->get_status() );
2122+
$this->assertArrayHasKey( 'image_save_progressive', $data );
2123+
$this->assertTrue( $data['image_save_progressive'] );
2124+
}
2125+
2126+
/**
2127+
* Non-image attachments must not surface the image_* fields.
2128+
*
2129+
* @ticket 65367
2130+
*
2131+
* @covers WP_REST_Attachments_Controller::prepare_item_for_response
2132+
*/
2133+
public function test_image_output_format_skipped_for_non_image(): void {
2134+
wp_set_current_user( self::$superadmin_id );
2135+
2136+
$attachment_id = self::factory()->attachment->create_object(
2137+
DIR_TESTDATA . '/uploads/dashicons.woff',
2138+
0,
2139+
array(
2140+
'post_mime_type' => 'application/font-woff',
2141+
'post_type' => 'attachment',
2142+
)
2143+
);
2144+
2145+
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
2146+
$request->set_param( 'context', 'edit' );
2147+
2148+
$response = rest_get_server()->dispatch( $request );
2149+
$data = $response->get_data();
2150+
2151+
$this->assertSame( 200, $response->get_status() );
2152+
$this->assertArrayNotHasKey( 'image_output_format', $data );
2153+
$this->assertArrayNotHasKey( 'image_save_progressive', $data );
2154+
}
2155+
19962156
public function test_get_additional_field_registration() {
19972157

19982158
$schema = array(

tests/qunit/fixtures/wp-api-generated.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12913,10 +12913,6 @@ mockedApiResponse.Schema = {
1291312913
}
1291412914
},
1291512915
"image_size_threshold": 2560,
12916-
"image_output_formats": {},
12917-
"jpeg_interlaced": false,
12918-
"png_interlaced": false,
12919-
"gif_interlaced": false,
1292012916
"site_logo": 0,
1292112917
"site_icon": 0,
1292212918
"site_icon_url": ""

0 commit comments

Comments
 (0)