Create an API that manage the custom field names (#48504)

* Create WC_REST_Product_Custom_Fields_Controller

* Register WC_REST_Product_Custom_Fields_Controller

* Add pagination to the /product-custom-fields/names request

* Add WC_REST_Product_Custom_Fields_Controller_Tests

* Fix linter errors

* Add changelog file

* Fix linter error

* Change endpoint path to matches the product related endpoints set

* Fix controller file description

* Use more descriptive table alias in the sql query
This commit is contained in:
Maikel Perez 2024-06-21 14:43:00 -04:00 committed by GitHub
parent c77dfe5030
commit 5e997435bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 348 additions and 0 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Add a rest api to manage the product custom fields

View File

@ -0,0 +1,162 @@
<?php
/**
* REST API CustomFields controller
*
* Handles requests to the /products/custom-fields endpoint.
*
* @package WooCommerce\RestApi
* @since 9.2.0
*/
use Automattic\WooCommerce\Utilities\I18nUtil;
defined( 'ABSPATH' ) || exit;
/**
* REST API Product Custom Fields controller class.
*
* @package WooCommerce\RestApi
* @extends WC_REST_Controller
*/
class WC_REST_Product_Custom_Fields_Controller extends WC_REST_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc/v3';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'products/custom-fields';
/**
* Post type.
*
* @var string
*/
protected $post_type = 'product';
/**
* Register the routes for products.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/names',
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item_names' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Get a collection of custom field names.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item_names( $request ) {
global $wpdb;
$search = trim( $request['search'] );
$order = strtoupper( $request['order'] ) === 'DESC' ? 'DESC' : 'ASC';
$page = (int) $request['page'];
$limit = (int) $request['per_page'];
$offset = ( $page - 1 ) * $limit;
$base_query = $wpdb->prepare(
"SELECT DISTINCT post_metas.meta_key
FROM {$wpdb->postmeta} post_metas LEFT JOIN {$wpdb->posts} posts ON post_metas.post_id = posts.id
WHERE posts.post_type = %s AND post_metas.meta_key NOT LIKE %s AND post_metas.meta_key LIKE %s",
$this->post_type,
$wpdb->esc_like( '_' ) . '%',
"%{$search}%"
);
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $base_query has been prepared already and $order is a static value.
$query = $wpdb->prepare(
"$base_query ORDER BY post_metas.meta_key $order LIMIT %d, %d",
$offset,
$limit
);
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $base_query has been prepared already.
$total_query = "SELECT COUNT(1) FROM ($base_query) AS total";
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $query has been prepared already.
$query_result = $wpdb->get_results( $query );
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $total_query has been prepared already.
$total_items = $wpdb->get_var( $total_query );
$custom_field_names = array();
foreach ( $query_result as $custom_field_name ) {
$custom_field_names[] = $custom_field_name->meta_key;
}
$response = rest_ensure_response( $custom_field_names );
$response->header( 'X-WP-Total', (int) $total_items );
$max_pages = ceil( $total_items / $limit );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$base = add_query_arg( $request->get_query_params(), rest_url( '/' . $this->namespace . '/' . $this->rest_base . '/names' ) );
if ( $page > 1 ) {
$prev_page = $page - 1;
if ( $prev_page > $max_pages ) {
$prev_page = $max_pages;
}
$prev_link = add_query_arg( 'page', $prev_page, $base );
$response->link_header( 'prev', $prev_link );
}
if ( $max_pages > $page ) {
$next_page = $page + 1;
$next_link = add_query_arg( 'page', $next_page, $base );
$response->link_header( 'next', $next_link );
}
return $response;
}
/**
* Check if a given request has access to read items.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_post_permissions( $this->post_type, 'read' ) ) {
return new WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Add new options for 'order' to the collection params.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['order'] = array(
'description' => __( 'Order sort items ascending or descending.', 'woocommerce' ),
'type' => 'string',
'default' => 'asc',
'enum' => array( 'asc', 'desc' ),
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
}

View File

@ -157,6 +157,7 @@ class Server {
'product-attribute-terms' => 'WC_REST_Product_Attribute_Terms_Controller',
'product-attributes' => 'WC_REST_Product_Attributes_Controller',
'product-categories' => 'WC_REST_Product_Categories_Controller',
'product-custom-fields' => 'WC_REST_Product_Custom_Fields_Controller',
'product-reviews' => 'WC_REST_Product_Reviews_Controller',
'product-shipping-classes' => 'WC_REST_Product_Shipping_Classes_Controller',
'product-tags' => 'WC_REST_Product_Tags_Controller',

View File

@ -0,0 +1,181 @@
<?php
/**
* class WC_REST_Product_Custom_Fields_Controller_Tests.
* Product Custom Fields Controller tests for V3 REST API.
*/
class WC_REST_Product_Custom_Fields_Controller_Tests extends WC_REST_Unit_Test_Case {
/**
* @var WC_Product_Simple[]
*/
protected static $products = array();
/**
* Create products for tests.
*
* @return void
*/
public static function wpSetUpBeforeClass() {
self::$products[] = WC_Helper_Product::create_simple_product(
true,
array(
'name' => 'Pancake',
'sku' => 'pancake-1',
)
);
self::$products[] = WC_Helper_Product::create_simple_product(
true,
array(
'name' => 'Waffle 1',
'sku' => 'pancake-2',
)
);
self::$products[] = WC_Helper_Product::create_simple_product(
true,
array(
'name' => 'French Toast',
'sku' => 'waffle-2',
)
);
self::$products[] = WC_Helper_Product::create_simple_product(
true,
array(
'name' => 'Waffle 3',
'sku' => 'waffle-3',
)
);
foreach ( self::$products as $product ) {
for ( $i = 0; $i < 20; $i++ ) {
$product->add_meta_data( "Custom field $i", "Value $i", true );
}
$product->save();
}
}
/**
* Clean up products after tests.
*
* @return void
*/
public static function wpTearDownAfterClass() {
foreach ( self::$products as $product ) {
WC_Helper_Product::delete_product( $product->get_id() );
}
}
/**
* Setup our test server, endpoints, and user info.
*/
public function setUp(): void {
parent::setUp();
$this->endpoint = new WC_REST_Product_Custom_Fields_Controller();
$this->user = $this->factory->user->create(
array(
'role' => 'administrator',
)
);
wp_set_current_user( $this->user );
}
/**
* Test the default custom field names endpoint response.
*
* @return void
*/
public function test_custom_fields_without_params() {
$request = new WP_REST_Request( 'GET', '/wc/v3/products/custom-fields/names' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 10, count( $data ) );
$this->assertEquals( 'Custom field 0', $data[0] );
$this->assertEquals( 'Custom field 1', $data[1] );
$this->assertEquals( 'Custom field 10', $data[2] );
$this->assertEquals( 'Custom field 11', $data[3] );
$this->assertEquals( 'Custom field 12', $data[4] );
$this->assertEquals( 'Custom field 13', $data[5] );
$this->assertEquals( 'Custom field 14', $data[6] );
$this->assertEquals( 'Custom field 15', $data[7] );
$this->assertEquals( 'Custom field 16', $data[8] );
$this->assertEquals( 'Custom field 17', $data[9] );
}
/**
* Test the custom field names searching.
*
* @return void
*/
public function test_custom_fields_searching() {
$request = new WP_REST_Request( 'GET', '/wc/v3/products/custom-fields/names' );
$request->set_query_params(
array(
'search' => '0',
)
);
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Custom field 0', $data[0] );
$this->assertEquals( 'Custom field 10', $data[1] );
}
/**
* Test the custom field names ordering.
*
* @return void
*/
public function test_custom_fields_ordering() {
$request = new WP_REST_Request( 'GET', '/wc/v3/products/custom-fields/names' );
$request->set_query_params(
array(
'order' => 'desc',
'search' => 'Custom',
)
);
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 10, count( $data ) );
$this->assertEquals( 'Custom field 9', $data[0] );
$this->assertEquals( 'Custom field 8', $data[1] );
$this->assertEquals( 'Custom field 7', $data[2] );
$this->assertEquals( 'Custom field 6', $data[3] );
$this->assertEquals( 'Custom field 5', $data[4] );
$this->assertEquals( 'Custom field 4', $data[5] );
$this->assertEquals( 'Custom field 3', $data[6] );
$this->assertEquals( 'Custom field 2', $data[7] );
$this->assertEquals( 'Custom field 19', $data[8] );
$this->assertEquals( 'Custom field 18', $data[9] );
}
/**
* Test the custom field names pagination.
*
* @return void
*/
public function test_custom_fields_pagination() {
$request = new WP_REST_Request( 'GET', '/wc/v3/products/custom-fields/names' );
$request->set_query_params(
array(
'per_page' => 3,
'page' => 2,
'search' => 'Custom',
)
);
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 3, count( $data ) );
$this->assertEquals( 'Custom field 11', $data[0] );
$this->assertEquals( 'Custom field 12', $data[1] );
$this->assertEquals( 'Custom field 13', $data[2] );
}
}