Add REST API Products featured image (#37815)
This commit is contained in:
commit
7073fea067
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: enhancement
|
||||
|
||||
Add `featured` field to the images array for Product REST API
|
|
@ -45,9 +45,11 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
protected function get_images( $product ) {
|
||||
$images = array();
|
||||
$attachment_ids = array();
|
||||
$featured_id = null;
|
||||
|
||||
// Add featured image.
|
||||
if ( $product->get_image_id() ) {
|
||||
$featured_id = $product->get_image_id();
|
||||
$attachment_ids[] = $product->get_image_id();
|
||||
}
|
||||
|
||||
|
@ -68,6 +70,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
|
||||
$images[] = array(
|
||||
'id' => (int) $attachment_id,
|
||||
'featured' => (int) $featured_id === (int) $attachment_id,
|
||||
'date_created' => wc_rest_prepare_date_response( $attachment_post->post_date, false ),
|
||||
'date_created_gmt' => wc_rest_prepare_date_response( strtotime( $attachment_post->post_date_gmt ) ),
|
||||
'date_modified' => wc_rest_prepare_date_response( $attachment_post->post_modified, false ),
|
||||
|
@ -305,6 +308,31 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
$images = is_array( $images ) ? array_filter( $images ) : array();
|
||||
|
||||
if ( ! empty( $images ) ) {
|
||||
$featured_image_index = 0;
|
||||
$featured_image_count = 0;
|
||||
|
||||
// Collect featured field usage.
|
||||
foreach ( $images as $index => $image ) {
|
||||
if ( isset( $image['featured'] ) && $image['featured'] ) {
|
||||
$featured_image_index = $index;
|
||||
$featured_image_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle multiple featured images.
|
||||
if ( $featured_image_count > 1 ) {
|
||||
throw new WC_REST_Exception(
|
||||
'woocommerce_rest_product_featured_image_count',
|
||||
__( 'Only one featured image is allowed.', 'woocommerce' ),
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
// If no featured image is set, and the first image explicitly set to false do not set featured at all.
|
||||
if ( 0 === $featured_image_count && isset( $images[0]['featured'] ) && false === $images[0]['featured'] ) {
|
||||
$featured_image_index = null;
|
||||
}
|
||||
|
||||
$gallery = array();
|
||||
|
||||
foreach ( $images as $index => $image ) {
|
||||
|
@ -329,9 +357,7 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
throw new WC_REST_Exception( 'woocommerce_product_invalid_image_id', sprintf( __( '#%s is an invalid image ID.', 'woocommerce' ), $attachment_id ), 400 );
|
||||
}
|
||||
|
||||
$featured_image = $product->get_image_id();
|
||||
|
||||
if ( 0 === $index ) {
|
||||
if ( $featured_image_index === $index ) {
|
||||
$product->set_image_id( $attachment_id );
|
||||
} else {
|
||||
$gallery[] = $attachment_id;
|
||||
|
@ -1253,6 +1279,12 @@ class WC_REST_Products_Controller extends WC_REST_Products_V2_Controller {
|
|||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'featured' => array(
|
||||
'description' => __( 'Featured image.', 'woocommerce' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'date_created' => array(
|
||||
'description' => __( "The date the image was created, in the site's timezone.", 'woocommerce' ),
|
||||
'type' => 'date-time',
|
||||
|
|
|
@ -265,7 +265,7 @@ test.describe('Products API tests: CRUD', () => {
|
|||
expect(responseJSON.type).toEqual('select');
|
||||
expect(responseJSON.order_by).toEqual('name');
|
||||
// the below has_archives test is currently not working as expected
|
||||
// an issue (https://github.com/woocommerce/woocommerce/issues/34991)
|
||||
// an issue (https://github.com/woocommerce/woocommerce/issues/34991)
|
||||
// has been raised and this test can be
|
||||
// updated as appropriate after triage
|
||||
// expect(responseJSON.has_archives).toEqual(true);
|
||||
|
@ -913,7 +913,6 @@ test.describe('Products API tests: CRUD', () => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
test.describe('Product tags tests: CRUD', () => {
|
||||
let productTagId;
|
||||
|
||||
|
@ -1074,6 +1073,235 @@ test.describe('Products API tests: CRUD', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test.describe( 'Product images tests: CRUD', () => {
|
||||
let productId;
|
||||
let images;
|
||||
|
||||
test( 'can add product with an image', async ( { request } ) => {
|
||||
const response = await request.post( 'wp-json/wc/v3/products', {
|
||||
data: {
|
||||
images: [ { src: 'https://cldup.com/6L9h56D9Bw.jpg' } ],
|
||||
},
|
||||
} );
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 201 );
|
||||
expect( responseJSON.images ).toHaveLength( 1 );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
expect( responseJSON.images[ 0 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 0 ].src ).toContain( '6L9h56D9Bw' );
|
||||
|
||||
// Cleanup: Delete the used product
|
||||
await request.delete(
|
||||
`wp-json/wc/v3/products/${ responseJSON.id }`,
|
||||
{
|
||||
data: {
|
||||
force: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'can add product with multiple images (backward compatible)', async ( {
|
||||
request,
|
||||
} ) => {
|
||||
const response = await request.post( 'wp-json/wc/v3/products', {
|
||||
data: {
|
||||
images: [
|
||||
{ src: 'https://cldup.com/6L9h56D9Bw.jpg' },
|
||||
{ src: 'https://cldup.com/Dr1Bczxq4q.png' },
|
||||
],
|
||||
},
|
||||
} );
|
||||
const responseJSON = await response.json();
|
||||
productId = responseJSON.id;
|
||||
images = responseJSON.images.map( ( image ) => image.id );
|
||||
|
||||
expect( response.status() ).toEqual( 201 );
|
||||
expect( responseJSON.images ).toHaveLength( 2 );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
expect( responseJSON.images[ 1 ].name ).toContain( 'Dr1Bczxq4q' );
|
||||
expect( responseJSON.images[ 1 ].featured ).toBeFalsy();
|
||||
} );
|
||||
|
||||
test( 'can add product with multiple images (explicit featured)', async ( {
|
||||
request,
|
||||
} ) => {
|
||||
const response = await request.post( 'wp-json/wc/v3/products', {
|
||||
data: {
|
||||
images: [
|
||||
{
|
||||
src: 'https://cldup.com/6L9h56D9Bw.jpg',
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
src: 'https://cldup.com/Dr1Bczxq4q.png',
|
||||
featured: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
} );
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 201 );
|
||||
expect( responseJSON.images ).toHaveLength( 2 );
|
||||
// When retrieving, the featured image is always the first one.
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( 'Dr1Bczxq4q' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
expect( responseJSON.images[ 1 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 1 ].featured ).toBeFalsy();
|
||||
|
||||
// Cleanup: Delete the used product
|
||||
await request.delete(
|
||||
`wp-json/wc/v3/products/${ responseJSON.id }`,
|
||||
{
|
||||
data: {
|
||||
force: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'can add product with multiple images (no featured)', async ( {
|
||||
request,
|
||||
} ) => {
|
||||
const response = await request.post( 'wp-json/wc/v3/products', {
|
||||
data: {
|
||||
images: [
|
||||
{
|
||||
src: 'https://cldup.com/6L9h56D9Bw.jpg',
|
||||
featured: false,
|
||||
},
|
||||
{
|
||||
src: 'https://cldup.com/Dr1Bczxq4q.png',
|
||||
featured: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
} );
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 201 );
|
||||
expect( responseJSON.images ).toHaveLength( 2 );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 0 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeFalsy();
|
||||
expect( responseJSON.images[ 1 ].name ).toContain( 'Dr1Bczxq4q' );
|
||||
expect( responseJSON.images[ 1 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 1 ].featured ).toBeFalsy();
|
||||
|
||||
// Cleanup: Delete the used product
|
||||
await request.delete(
|
||||
`wp-json/wc/v3/products/${ responseJSON.id }`,
|
||||
{
|
||||
data: {
|
||||
force: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
} );
|
||||
|
||||
test( 'cannot add product with multiple images (all featured)', async ( {
|
||||
request,
|
||||
} ) => {
|
||||
const response = await request.post( 'wp-json/wc/v3/products', {
|
||||
data: {
|
||||
images: [
|
||||
{
|
||||
src: 'https://cldup.com/6L9h56D9Bw.jpg',
|
||||
featured: true,
|
||||
},
|
||||
{
|
||||
src: 'https://cldup.com/Dr1Bczxq4q.png',
|
||||
featured: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
} );
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 400 );
|
||||
expect( responseJSON.code ).toEqual(
|
||||
'woocommerce_rest_product_featured_image_count'
|
||||
);
|
||||
expect( responseJSON.message ).toEqual(
|
||||
'Only one featured image is allowed.'
|
||||
);
|
||||
expect( responseJSON.data ).toEqual( { status: 400 } );
|
||||
} );
|
||||
|
||||
test( 'can retrieve product images', async ( { request } ) => {
|
||||
const response = await request.get(
|
||||
`wp-json/wc/v3/products/${ productId }`
|
||||
);
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 200 );
|
||||
expect( responseJSON.images ).toHaveLength( 2 );
|
||||
expect( responseJSON.images[ 0 ].id ).toEqual( images[ 0 ] );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 0 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
} );
|
||||
|
||||
test( 'can update a product images', async ( { request } ) => {
|
||||
// call API to update a product
|
||||
const response = await request.put(
|
||||
`wp-json/wc/v3/products/${ productId }`,
|
||||
{
|
||||
data: {
|
||||
images: [
|
||||
{ id: images[ 0 ], featured: false },
|
||||
{ id: images[ 1 ], featured: true },
|
||||
],
|
||||
},
|
||||
}
|
||||
);
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 200 );
|
||||
expect( responseJSON.images ).toHaveLength( 2 );
|
||||
// When retrieving, the featured image is always the first one.
|
||||
expect( responseJSON.images[ 0 ].id ).toEqual( images[ 1 ] );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( 'Dr1Bczxq4q' );
|
||||
expect( responseJSON.images[ 0 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
expect( responseJSON.images[ 1 ].id ).toEqual( images[ 0 ] );
|
||||
expect( responseJSON.images[ 1 ].name ).toContain( '6L9h56D9Bw' );
|
||||
expect( responseJSON.images[ 1 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 1 ].featured ).toBeFalsy();
|
||||
} );
|
||||
|
||||
test( 'can remove an image from a product', async ( { request } ) => {
|
||||
// Delete the product attribute.
|
||||
const response = await request.put(
|
||||
`wp-json/wc/v3/products/${ productId }`,
|
||||
{
|
||||
data: {
|
||||
images: [ { id: images[ 1 ] } ],
|
||||
},
|
||||
}
|
||||
);
|
||||
const responseJSON = await response.json();
|
||||
|
||||
expect( response.status() ).toEqual( 200 );
|
||||
expect( responseJSON.images ).toHaveLength( 1 );
|
||||
expect( responseJSON.images[ 0 ].id ).toEqual( images[ 1 ] );
|
||||
expect( responseJSON.images[ 0 ].name ).toContain( 'Dr1Bczxq4q' );
|
||||
expect( responseJSON.images[ 0 ].alt ).toEqual( '' );
|
||||
expect( responseJSON.images[ 0 ].featured ).toBeTruthy();
|
||||
|
||||
// Cleanup: Delete the used product
|
||||
await request.delete( `wp-json/wc/v3/products/${ productId }`, {
|
||||
data: {
|
||||
force: true,
|
||||
},
|
||||
} );
|
||||
} );
|
||||
} );
|
||||
|
||||
test('can add a virtual product', async ({
|
||||
request
|
||||
}) => {
|
||||
|
|
|
@ -204,6 +204,84 @@ class WC_REST_Products_Controller_Tests extends WC_REST_Unit_Test_Case {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that featured field exists under images in product argument.
|
||||
*/
|
||||
public function test_products_args_includes_featured_field() {
|
||||
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/products' );
|
||||
$request->set_param( '_fields', 'endpoints.1.args.images.items.properties' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$image_arg = $response->get_data()['endpoints'][1]['args']['images']['items']['properties'];
|
||||
$this->assertArrayHasKey( 'featured', $image_arg );
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'description' => 'Featured image.',
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'context' =>
|
||||
array(
|
||||
0 => 'view',
|
||||
1 => 'edit',
|
||||
),
|
||||
),
|
||||
$image_arg['featured']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that featured field exists under images in product schema.
|
||||
*/
|
||||
public function test_products_schema_includes_featured_field() {
|
||||
$request = new WP_REST_Request( 'OPTIONS', '/wc/v3/products' );
|
||||
$request->set_param( '_fields', 'schema.properties.images.items.properties' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$image_schema = $response->get_data()['schema']['properties']['images']['items']['properties'];
|
||||
$this->assertArrayHasKey( 'featured', $image_schema );
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'description' => 'Featured image.',
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'context' =>
|
||||
array(
|
||||
0 => 'view',
|
||||
1 => 'edit',
|
||||
),
|
||||
),
|
||||
$image_schema['featured']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that feature field is returned inside images.
|
||||
*/
|
||||
public function test_products_get_images_array_contains_featured() {
|
||||
global $wpdb;
|
||||
|
||||
$product = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper::create_simple_product();
|
||||
$gallery_url = media_sideload_image( 'https://cldup.com/6L9h56D9Bw.jpg', $product->get_id(), 'gallery', 'src' );
|
||||
$this->assertNotWPError( $gallery_url );
|
||||
$gallery_id = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid = %s", $gallery_url ) );
|
||||
|
||||
$featured_url = media_sideload_image( 'https://cldup.com/Dr1Bczxq4q.png', $product->get_id(), 'featured', 'src' );
|
||||
$this->assertNotWPError( $featured_url );
|
||||
$featured_id = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE guid = %s", $featured_url ) );
|
||||
|
||||
$product->set_image_id( $featured_id[0] );
|
||||
$product->set_gallery_image_ids( $gallery_id[0] );
|
||||
$product->save();
|
||||
|
||||
$request = new WP_REST_Request( 'GET', '/wc/v3/products/' . $product->get_id() );
|
||||
$request->set_param( '_fields', 'images' );
|
||||
$response = $this->server->dispatch( $request );
|
||||
$images_array = $response->get_data()['images'];
|
||||
$this->assertEquals( 200, $response->get_status() );
|
||||
$this->assertTrue( $images_array[0]['featured'] );
|
||||
$this->assertEquals( 'featured', $images_array[0]['name'] );
|
||||
$this->assertFalse( $images_array[1]['featured'] );
|
||||
$this->assertEquals( 'gallery', $images_array[1]['name'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the `search` parameter does partial matching in the product name, but not the SKU.
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue