Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
236d585
REST API: Expose size-aware encode quality on attachment responses.
adamsilverstein May 18, 2026
1c60f7b
Apply suggestions from code review
adamsilverstein May 21, 2026
823596a
Media: Add wp_get_image_encode_quality() helper.
adamsilverstein May 21, 2026
8a1ae1a
REST API: Honor jpeg_quality and bound the image_quality schema.
adamsilverstein May 21, 2026
61b33fd
Tests: Reference Trac ticket #65262 for image_quality coverage.
adamsilverstein May 21, 2026
824ee07
Update src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-c…
adamsilverstein May 28, 2026
393e27c
Add types and address PHPStan issues with wp_get_image_encode_quality()
westonruter May 28, 2026
b09105c
REST API: Reset out-of-range encode quality to the per-format default.
adamsilverstein Jun 27, 2026
2436b94
Merge remote-tracking branch 'origin/trunk' into add/rest-image-quali…
adamsilverstein Jul 1, 2026
8a2aca7
Fix param type for $default_quality
westonruter Jul 1, 2026
ae86911
Add return type info for wp_get_registered_image_subsizes()
westonruter Jul 1, 2026
7e0501e
Ensure non-negative-int values are used in dimensions passed to wp_ge…
westonruter Jul 1, 2026
f20e7df
Fix assign.byRefForeachExpr
westonruter Jul 1, 2026
5bc92b3
Cast mime_type to string since guaranteed to be so
westonruter Jul 1, 2026
48706d1
Merge remote-tracking branch 'origin/trunk' into add/rest-image-quali…
adamsilverstein Jul 2, 2026
a847c9c
Merge branch 'trunk' into add/rest-image-quality-field
adamsilverstein Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,63 @@ public function prepare_item_for_response( $item, $request ) {
$data['exif_orientation'] = $orientation;
}

if ( in_array( 'image_quality', $fields, true ) && wp_attachment_is_image( $post ) ) {
$mime_type = get_post_mime_type( $post );
$filename = get_attached_file( $post->ID );

/** This filter is documented in wp-includes/class-wp-image-editor.php */
$output_formats = apply_filters(
'image_editor_output_format',
array( $mime_type => $mime_type ),
$filename ? $filename : '',
$mime_type
);
$output_mime = isset( $output_formats[ $mime_type ] ) ? $output_formats[ $mime_type ] : $mime_type;
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated

// Mirror WP_Image_Editor::get_default_quality(): WebP defaults to 86, everything else to 82.
$default_quality = ( 'image/webp' === $output_mime ) ? 86 : 82;

$metadata = wp_get_attachment_metadata( $post->ID, true );
$full_width = ( is_array( $metadata ) && isset( $metadata['width'] ) ) ? (int) $metadata['width'] : 0;
$full_height = ( is_array( $metadata ) && isset( $metadata['height'] ) ) ? (int) $metadata['height'] : 0;

/** This filter is documented in wp-includes/class-wp-image-editor.php */
$full_quality = (int) apply_filters(
'wp_editor_set_quality',
$default_quality,
$output_mime,
array(
'width' => $full_width,
'height' => $full_height,
)
);
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated

$size_quality = array();

foreach ( wp_get_registered_image_subsizes() as $size_name => $size_data ) {
/** This filter is documented in wp-includes/class-wp-image-editor.php */
$quality = (int) apply_filters(
'wp_editor_set_quality',
$default_quality,
$output_mime,
array(
'width' => (int) $size_data['width'],
'height' => (int) $size_data['height'],
)
);

// Only report sizes whose quality diverges from the full-size value.
if ( $quality !== $full_quality ) {
$size_quality[ $size_name ] = $quality;
}
}

$data['image_quality'] = array(
'default' => $full_quality,
'sizes' => $size_quality,
);
}

$context = ! empty( $request['context'] ) ? $request['context'] : 'view';

$data = $this->filter_response_by_context( $data, $context );
Expand Down Expand Up @@ -1242,6 +1299,24 @@ public function get_item_schema() {
'readonly' => true,
);

$schema['properties']['image_quality'] = array(
'description' => __( 'Encode quality (1-100) from the wp_editor_set_quality filter, resolved against the output MIME type. The "default" value applies to the full-size image; "sizes" lists per-registered-size overrides where the filtered value differs from "default".' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
'properties' => array(
'default' => array(
'type' => 'integer',
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated
),
'sizes' => array(
'type' => 'object',
'additionalProperties' => array(
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated
'type' => 'integer',
Comment thread
adamsilverstein marked this conversation as resolved.
Outdated
),
),
),
);

unset( $schema['properties']['password'] );

$this->schema = $schema;
Expand Down
73 changes: 72 additions & 1 deletion tests/phpunit/tests/rest-api/rest-attachments-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -1939,10 +1939,11 @@ public function test_get_item_schema() {
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertCount( 32, $properties );
$this->assertCount( 33, $properties );
$this->assertArrayHasKey( 'author', $properties );
$this->assertArrayHasKey( 'alt_text', $properties );
$this->assertArrayHasKey( 'exif_orientation', $properties );
$this->assertArrayHasKey( 'image_quality', $properties );
$this->assertArrayHasKey( 'filename', $properties );
$this->assertArrayHasKey( 'filesize', $properties );
$this->assertArrayHasKey( 'caption', $properties );
Expand Down Expand Up @@ -1980,6 +1981,76 @@ public function test_get_item_schema() {
$this->assertArrayHasKey( 'class_list', $properties );
}

/**
* @ticket 64906
*/
public function test_image_quality_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
$response = rest_get_server()->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];

$this->assertArrayHasKey( 'image_quality', $properties );
$this->assertSame( 'object', $properties['image_quality']['type'] );
$this->assertContains( 'edit', $properties['image_quality']['context'] );
$this->assertTrue( $properties['image_quality']['readonly'] );
$this->assertSame( 'integer', $properties['image_quality']['properties']['default']['type'] );
$this->assertSame( 'object', $properties['image_quality']['properties']['sizes']['type'] );
}

/**
* @ticket 64906
* @requires function imagejpeg
*/
public function test_image_quality_default_in_response() {
wp_set_current_user( self::$editor_id );
$attachment = self::factory()->attachment->create_upload_object( self::$test_file );

$request = new WP_REST_Request( 'GET', "/wp/v2/media/{$attachment}" );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$data = $response->get_data();

$this->assertSame( 200, $response->get_status() );
$this->assertArrayHasKey( 'image_quality', $data );
// JPEG default quality is 82; no filter, so no per-size overrides.
$this->assertSame( 82, $data['image_quality']['default'] );
$this->assertSame( array(), $data['image_quality']['sizes'] );
}

/**
* @ticket 64906
* @requires function imagejpeg
*/
public function test_image_quality_with_size_aware_filter() {
wp_set_current_user( self::$editor_id );
$attachment = self::factory()->attachment->create_upload_object( self::$test_file );

// Lower the quality for small images (e.g. thumbnails) only.
$filter = static function ( $quality, $mime_type, $size ) {
if ( is_array( $size ) && ! empty( $size['width'] ) && $size['width'] <= 300 ) {
return 60;
}
return $quality;
};
add_filter( 'wp_editor_set_quality', $filter, 10, 3 );

$request = new WP_REST_Request( 'GET', "/wp/v2/media/{$attachment}" );
$request->set_param( 'context', 'edit' );
$response = rest_do_request( $request );
$data = $response->get_data();

remove_filter( 'wp_editor_set_quality', $filter, 10 );

$this->assertSame( 200, $response->get_status() );
$this->assertArrayHasKey( 'image_quality', $data );
// The full-size image (> 300px wide) keeps the default quality.
$this->assertSame( 82, $data['image_quality']['default'] );
// The thumbnail size (150x150) is <= 300px and diverges to 60.
$this->assertArrayHasKey( 'thumbnail', $data['image_quality']['sizes'] );
$this->assertSame( 60, $data['image_quality']['sizes']['thumbnail'] );
}

public function test_get_additional_field_registration() {

$schema = array(
Expand Down
Loading