From a9093bc309ac3a12ba86653e6cdb134dec4daaed Mon Sep 17 00:00:00 2001 From: Paul Kilmurray Date: Fri, 5 Jan 2024 00:07:07 +0100 Subject: [PATCH] use wcpos include/exclude --- includes/API.php | 77 ++++--- includes/API/Customers_Controller.php | 39 ++++ includes/API/Orders.php | 56 +++-- includes/API/Orders_Controller.php | 46 ++++ .../API/Product_Variations_Controller.php | 55 +++++ includes/API/Products_Controller.php | 50 +++- .../API/Test_Customers_Controller.php | 49 ++++ tests/includes/API/Test_Orders_Controller.php | 164 +++++++++++++ .../Test_Product_Variations_Controller.php | 216 +++++++++++++++++- .../includes/API/Test_Products_Controller.php | 142 ++++++++++++ 10 files changed, 844 insertions(+), 50 deletions(-) diff --git a/includes/API.php b/includes/API.php index 52cc6d2..78a40c9 100644 --- a/includes/API.php +++ b/includes/API.php @@ -219,40 +219,65 @@ public function rest_index( WP_REST_Response $response ): WP_REST_Response { * @return mixed */ public function rest_pre_dispatch( $result, $server, $request ) { - // Get 'include' parameter from request - $include = $request->get_param( 'include' ); + $max_length = 10000; + // Process 'include' parameter + $include = $request->get_param( 'include' ); if ( $include ) { - // Convert to array if it's not - $include_array = \is_array( $include ) ? $include : explode( ',', $include ); - $include_string = implode( ',', $include_array ); - - // If the length of the 'include' string exceeds 10,000 characters, create a new array - if ( \strlen( $include_string ) > 10000 ) { - shuffle( $include_array ); // Shuffle the IDs to randomize - - // Construct a random array of no more than 10,000 characters - $max_include_length = 10000; - $new_include_string = ''; - $random_include_array = array(); - - foreach ( $include_array as $id ) { - if ( \strlen( $new_include_string . $id ) < $max_include_length ) { - $new_include_string .= $id . ','; - $random_include_array[] = $id; - } else { - break; // Stop when we reach the maximum length - } - } + $processed_include = $this->shorten_param_array( $include, $max_length ); + $request->set_param( 'wcpos_include', $processed_include ); + unset( $request['include'] ); + } - // Set modified 'include' parameter back to request - $request->set_param( 'include', $random_include_array ); - } + // Process 'exclude' parameter + $exclude = $request->get_param( 'exclude' ); + if ( $exclude ) { + $processed_exclude = $this->shorten_param_array( $exclude, $max_length ); + $request->set_param( 'wcpos_exclude', $processed_exclude ); + unset( $request['exclude'] ); } return $result; } + /** + * Some servers have a limit on the number of include/exclude we can use in a request. + * Worst thing is there is often no error message, the request returns an empty response. + * + * For example, WP Engine has a limit of 1024 characters? + * https://wpengine.com/support/using-dev-tools/#Long_Queries_in_wp_db + * + * @TODO - For long queries, I should find a better solution than this. + * + * @param string|array $param_value + * @param int $max_length + * @return array + */ + private function shorten_param_array( $param_value, $max_length ) { + $param_array = is_array( $param_value ) ? $param_value : explode( ',', $param_value ); + $param_string = implode( ',', $param_array ); + + if ( strlen( $param_string ) > $max_length ) { + shuffle( $param_array ); // Shuffle to randomize + + $new_param_string = ''; + $random_param_array = array(); + + foreach ( $param_array as $id ) { + if ( strlen( $new_param_string . $id ) < $max_length ) { + $new_param_string .= $id . ','; + $random_param_array[] = $id; + } else { + break; // Stop when maximum length is reached + } + } + + return $random_param_array; + } + + return $param_array; + } + /** * Filters the REST API dispatch request result. * diff --git a/includes/API/Customers_Controller.php b/includes/API/Customers_Controller.php index a75a055..3ba1497 100644 --- a/includes/API/Customers_Controller.php +++ b/includes/API/Customers_Controller.php @@ -41,6 +41,13 @@ class Customers_Controller extends WC_REST_Customers_Controller { */ protected $wcpos_user_search_results = array(); + /** + * Store the request object for use in lifecycle methods. + * + * @var WP_REST_Request + */ + protected $wcpos_request; + /** * Constructor. */ @@ -161,6 +168,7 @@ public function wcpos_validate_billing_email( WP_REST_Request $request ) { * @param array $handler Route handler used for the request. */ public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $request, $route, $handler ) { + $this->wcpos_request = $request; $this->wcpos_register_wc_rest_api_hooks(); $params = $request->get_params(); @@ -392,6 +400,11 @@ public function wcpos_customer_query( array $prepared_args, WP_REST_Request $req $prepared_args['meta_query'] = $this->wcpos_combine_meta_queries( $search_meta_query, $prepared_args['meta_query'] ); } + // Handle include/exclude + if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { + add_action( 'pre_user_query', array( $this, 'wcpos_include_exclude_users_by_id' ) ); + } + return $prepared_args; } @@ -430,4 +443,30 @@ public function wcpos_search_user_table( $query ): void { $query->query_where = $modified_where; } } + + /** + * Include or exclude users by ID. + * + * @param WP_User_Query $query + */ + public function wcpos_include_exclude_users_by_id( $query ) { + global $wpdb; + + // Remove the hook + remove_action( 'pre_user_query', array( $this, 'wcpos_include_exclude_users_by_id' ) ); + + // Handle 'wcpos_include' + if ( ! empty( $this->wcpos_request['wcpos_include'] ) ) { + $include_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_include'] ); + $ids_format = implode( ',', array_fill( 0, count( $include_ids ), '%d' ) ); + $query->query_where .= $wpdb->prepare( " AND {$wpdb->users}.ID IN ($ids_format) ", $include_ids ); + } + + // Handle 'wcpos_exclude' + if ( ! empty( $this->wcpos_request['wcpos_exclude'] ) ) { + $exclude_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_exclude'] ); + $ids_format = implode( ',', array_fill( 0, count( $exclude_ids ), '%d' ) ); + $query->query_where .= $wpdb->prepare( " AND {$wpdb->users}.ID NOT IN ($ids_format) ", $exclude_ids ); + } + } } diff --git a/includes/API/Orders.php b/includes/API/Orders.php index f268e04..fc5945b 100644 --- a/includes/API/Orders.php +++ b/includes/API/Orders.php @@ -36,18 +36,28 @@ public function __construct( WP_REST_Request $request ) { add_filter( 'rest_request_before_callbacks', array( $this, 'rest_request_before_callbacks' ), 10, 3 ); add_filter( 'woocommerce_rest_shop_order_object_query', array( $this, 'order_query' ), 10, 2 ); - add_filter('woocommerce_rest_pre_insert_shop_order_object', array( - $this, - 'pre_insert_shop_order_object', - ), 10, 3); + add_filter( + 'woocommerce_rest_pre_insert_shop_order_object', + array( + $this, + 'pre_insert_shop_order_object', + ), + 10, + 3 + ); add_filter( 'woocommerce_rest_prepare_shop_order_object', array( $this, 'order_response' ), 10, 3 ); add_filter( 'woocommerce_order_get_items', array( $this, 'order_get_items' ), 10, 3 ); add_action( 'woocommerce_rest_set_order_item', array( $this, 'rest_set_order_item' ), 10, 2 ); - add_filter('woocommerce_product_variation_get_attributes', array( - $this, - 'product_variation_get_attributes', - ), 10, 2); + add_filter( + 'woocommerce_product_variation_get_attributes', + array( + $this, + 'product_variation_get_attributes', + ), + 10, + 2 + ); add_action( 'woocommerce_before_order_object_save', array( $this, 'before_order_object_save' ), 10, 2 ); add_filter( 'posts_clauses', array( $this, 'orderby_additions' ), 10, 2 ); add_filter( 'option_woocommerce_tax_based_on', array( $this, 'tax_based_on' ), 10, 2 ); @@ -150,13 +160,18 @@ public function incoming_shop_order(): void { * this hack allows guest orders to bypass this validation */ if ( isset( $raw_data['customer_id'] ) && 0 == $raw_data['customer_id'] ) { - add_filter('is_email', function ( $result, $email ) { - if ( ! $email ) { - return true; - } + add_filter( + 'is_email', + function ( $result, $email ) { + if ( ! $email ) { + return true; + } - return $result; - }, 10, 2); + return $result; + }, + 10, + 2 + ); } } @@ -217,10 +232,13 @@ public function order_response( WP_REST_Response $response, WC_Order $order, WP_ $this->maybe_add_post_uuid( $order ); // Add payment link to the order. - $pos_payment_url = add_query_arg(array( - 'pay_for_order' => true, - 'key' => $order->get_order_key(), - ), get_home_url( null, '/wcpos-checkout/order-pay/' . $order->get_id() )); + $pos_payment_url = add_query_arg( + array( + 'pay_for_order' => true, + 'key' => $order->get_order_key(), + ), + get_home_url( null, '/wcpos-checkout/order-pay/' . $order->get_id() ) + ); $response->add_link( 'payment', $pos_payment_url, array( 'foo' => 'bar' ) ); @@ -410,7 +428,7 @@ public function orderby_additions( array $clauses, WP_Query $wp_query ): array { * * @return array|WP_Error */ - public function get_all_posts(array $fields = array() ): array { + public function get_all_posts( array $fields = array() ): array { $args = array( 'limit' => -1, 'return' => 'ids', diff --git a/includes/API/Orders_Controller.php b/includes/API/Orders_Controller.php index bed4f58..b49909a 100644 --- a/includes/API/Orders_Controller.php +++ b/includes/API/Orders_Controller.php @@ -363,6 +363,7 @@ public function wcpos_recipient_email_address( string $recipient, WC_Order $orde * @param array $handler Route handler used for the request. */ public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $request, $route, $handler ) { + $this->wcpos_request = $request; $this->wcpos_register_wc_rest_api_hooks( $request ); $params = $request->get_params(); @@ -430,6 +431,7 @@ public function wcpos_register_wc_rest_api_hooks( WP_REST_Request $request ): vo add_filter( 'woocommerce_rest_prepare_shop_order_object', array( $this, 'wcpos_order_response' ), 10, 3 ); add_filter( 'woocommerce_order_get_items', array( $this, 'wcpos_order_get_items' ), 10, 3 ); add_action( 'woocommerce_before_order_object_save', array( $this, 'wcpos_before_order_object_save' ), 10, 2 ); + add_filter( 'woocommerce_rest_shop_order_object_query', array( $this, 'wcpos_shop_order_query' ), 10, 2 ); } /** @@ -512,6 +514,50 @@ public function wcpos_before_order_object_save( WC_Order $order ): void { } } + /** + * Filter the order query. + * + * @param array $args Query arguments. + * @param WP_REST_Request $request Request object. + */ + public function wcpos_shop_order_query( array $args, WP_REST_Request $request ) { + // Check for wcpos_include/wcpos_exclude parameter. + if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { + // Add a custom WHERE clause to the query. + add_filter( 'posts_where', array( $this, 'wcpos_posts_where_order_include_exclude' ), 10, 2 ); + } + + return $args; + } + + /** + * Filter the WHERE clause of the query. + * + * @param string $where WHERE clause of the query. + * @param object $query The WP_Query instance. + * + * @return string + */ + public function wcpos_posts_where_order_include_exclude( string $where, $query ) { + global $wpdb; + + // Handle 'wcpos_include' + if ( ! empty( $this->wcpos_request['wcpos_include'] ) ) { + $include_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_include'] ); + $ids_format = implode( ',', array_fill( 0, count( $include_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID IN ($ids_format) ", $include_ids ); + } + + // Handle 'wcpos_exclude' + if ( ! empty( $this->wcpos_request['wcpos_exclude'] ) ) { + $exclude_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_exclude'] ); + $ids_format = implode( ',', array_fill( 0, count( $exclude_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID NOT IN ($ids_format) ", $exclude_ids ); + } + + return $where; + } + /** * Returns array of all order ids. * diff --git a/includes/API/Product_Variations_Controller.php b/includes/API/Product_Variations_Controller.php index a89dd3e..43cc3c4 100644 --- a/includes/API/Product_Variations_Controller.php +++ b/includes/API/Product_Variations_Controller.php @@ -36,6 +36,13 @@ class Product_Variations_Controller extends WC_REST_Product_Variations_Controlle */ protected $namespace = 'wcpos/v1'; + /** + * Store the request object for use in lifecycle methods. + * + * @var WP_REST_Request + */ + protected $wcpos_request; + /** * Constructor. */ @@ -125,6 +132,7 @@ public function get_collection_params() { * @param array $handler Route handler used for the request. */ public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $request, $route, $handler ) { + $this->wcpos_request = $request; $this->wcpos_register_wc_rest_api_hooks(); $params = $request->get_params(); @@ -143,6 +151,7 @@ public function wcpos_register_wc_rest_api_hooks(): void { add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 ); add_filter( 'wp_get_attachment_image_src', array( $this, 'wcpos_product_image_src' ), 10, 4 ); add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 ); + add_filter( 'woocommerce_rest_product_variation_object_query', array( $this, 'wcpos_product_variation_query' ), 10, 2 ); } /** @@ -200,6 +209,52 @@ public function wcpos_insert_product_variation_object( WC_Data $object, WP_REST_ } } + /** + * Filter the query arguments for a request. + * + * @param array $args Key value array of query var to query value. + * @param WP_REST_Request $request The request used. + * + * @return array $args Key value array of query var to query value. + */ + public function wcpos_product_variation_query( array $args, WP_REST_Request $request ) { + // Check for wcpos_include/wcpos_exclude parameter. + if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { + // Add a custom WHERE clause to the query. + add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_include_exclude' ), 10, 2 ); + } + + return $args; + } + + /** + * Filters the WHERE clause of the query. + * + * @param string $where The WHERE clause of the query. + * @param WP_Query $query The WP_Query instance (passed by reference). + * + * @return string + */ + public function wcpos_posts_where_product_variation_include_exclude( string $where, WP_Query $query ) { + global $wpdb; + + // Handle 'wcpos_include' + if ( ! empty( $this->wcpos_request['wcpos_include'] ) ) { + $include_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_include'] ); + $ids_format = implode( ',', array_fill( 0, count( $include_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID IN ($ids_format) ", $include_ids ); + } + + // Handle 'wcpos_exclude' + if ( ! empty( $this->wcpos_request['wcpos_exclude'] ) ) { + $exclude_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_exclude'] ); + $ids_format = implode( ',', array_fill( 0, count( $exclude_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID NOT IN ($ids_format) ", $exclude_ids ); + } + + return $where; + } + /** * Returns array of all product ids, name. * diff --git a/includes/API/Products_Controller.php b/includes/API/Products_Controller.php index 4bdf15d..0c197e3 100644 --- a/includes/API/Products_Controller.php +++ b/includes/API/Products_Controller.php @@ -42,6 +42,13 @@ class Products_Controller extends WC_REST_Products_Controller { */ protected $allow_decimal_quantities = false; + /** + * Store the current request object. + * + * @var WP_REST_Request + */ + private $wcpos_request; + /** * Constructor. */ @@ -116,6 +123,7 @@ public function get_collection_params() { * @param array $handler Route handler used for the request. */ public function wcpos_dispatch_request( $dispatch_result, WP_REST_Request $request, $route, $handler ) { + $this->wcpos_request = $request; $this->wcpos_register_wc_rest_api_hooks(); $params = $request->get_params(); @@ -313,13 +321,19 @@ public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array * * @return array $args Key value array of query var to query value. */ - public function wcpos_product_query( array $args, WP_REST_Request $request ): array { + public function wcpos_product_query( array $args, WP_REST_Request $request ) { if ( ! empty( $request['search'] ) ) { - // We need to set the query up for a postmeta join + // We need to set the query up for a postmeta join. add_filter( 'posts_join', array( $this, 'wcpos_posts_join_to_products_search' ), 10, 2 ); add_filter( 'posts_groupby', array( $this, 'wcpos_posts_groupby_product_search' ), 10, 2 ); } + // Check for wcpos_include/wcpos_exclude parameter. + if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { + // Add a custom WHERE clause to the query. + add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_include_exclude' ), 10, 2 ); + } + return $args; } @@ -331,10 +345,10 @@ public function wcpos_product_query( array $args, WP_REST_Request $request ): ar * * @return string */ - public function wcpos_posts_join_to_products_search( string $join, WP_Query $wp_query ) { + public function wcpos_posts_join_to_products_search( string $join, WP_Query $query ) { global $wpdb; - if ( ! empty( $wp_query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) { + if ( ! empty( $query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) { $join .= " LEFT JOIN {$wpdb->postmeta} pm1 ON {$wpdb->posts}.ID = pm1.post_id "; } @@ -359,6 +373,34 @@ public function wcpos_posts_groupby_product_search( string $groupby, WP_Query $q return $groupby; } + /** + * Filters the WHERE clause of the query. + * + * @param string $where The WHERE clause of the query. + * @param WP_Query $query The WP_Query instance (passed by reference). + * + * @return string + */ + public function wcpos_posts_where_product_include_exclude( string $where, WP_Query $query ) { + global $wpdb; + + // Handle 'wcpos_include' + if ( ! empty( $this->wcpos_request['wcpos_include'] ) ) { + $include_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_include'] ); + $ids_format = implode( ',', array_fill( 0, count( $include_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID IN ($ids_format) ", $include_ids ); + } + + // Handle 'wcpos_exclude' + if ( ! empty( $this->wcpos_request['wcpos_exclude'] ) ) { + $exclude_ids = array_map( 'intval', (array) $this->wcpos_request['wcpos_exclude'] ); + $ids_format = implode( ',', array_fill( 0, count( $exclude_ids ), '%d' ) ); + $where .= $wpdb->prepare( " AND {$wpdb->posts}.ID NOT IN ($ids_format) ", $exclude_ids ); + } + + return $where; + } + /** * Returns array of all product ids, name. diff --git a/tests/includes/API/Test_Customers_Controller.php b/tests/includes/API/Test_Customers_Controller.php index 21bbef8..eb0c6a2 100644 --- a/tests/includes/API/Test_Customers_Controller.php +++ b/tests/includes/API/Test_Customers_Controller.php @@ -451,6 +451,9 @@ public function test_create_customer(): void { $this->assertEquals( 'Doe', $data['last_name'] ); } + /** + * + */ public function test_update_customer(): void { $customer = CustomerHelper::create_customer( array( @@ -475,4 +478,50 @@ public function test_update_customer(): void { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 'Jane', $data['first_name'] ); } + + /** + * Test customer search with includes + */ + public function test_customer_search_with_includes(): void { + $customer1 = CustomerHelper::create_customer( array( 'first_name' => 'John' ) ); + $customer2 = CustomerHelper::create_customer( array( 'first_name' => 'John' ) ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/customers' ); + $request->set_query_params( + array( + 'role' => 'all', + 'search' => 'John', + 'include' => $customer2->get_id(), + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $customer2->get_id(), $data[0]['id'] ); + } + + /** + * Test customer search with includes + */ + public function test_customer_search_with_excludes(): void { + $customer1 = CustomerHelper::create_customer( array( 'first_name' => 'John' ) ); + $customer2 = CustomerHelper::create_customer( array( 'first_name' => 'John' ) ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/customers' ); + $request->set_query_params( + array( + 'role' => 'all', + 'search' => 'John', + 'exclude' => $customer2->get_id(), + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $customer1->get_id(), $data[0]['id'] ); + } } diff --git a/tests/includes/API/Test_Orders_Controller.php b/tests/includes/API/Test_Orders_Controller.php index b6c848a..657ecfe 100644 --- a/tests/includes/API/Test_Orders_Controller.php +++ b/tests/includes/API/Test_Orders_Controller.php @@ -611,4 +611,168 @@ public function test_get_order_statuses(): void { $this->assertIsString( $status['name'] ); } } + + /** + * + */ + public function test_order_search_by_id() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', (string) $order1->get_id() ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order1->get_id() ), $ids ); + } + + /** + * + */ + public function test_order_search_by_billing_first_name() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + $order2->set_billing_first_name( 'John' ); + $order2->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', 'John' ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order2->get_id() ), $ids ); + } + + /** + * + */ + public function test_order_search_by_billing_last_name() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + $order1->set_billing_last_name( 'Doe' ); + $order1->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', 'Doe' ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order1->get_id() ), $ids ); + } + + /** + * + */ + public function test_order_search_by_billing_email() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + $order1->set_billing_email( 'posuser@example.com' ); + $order1->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', 'posuser' ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order1->get_id() ), $ids ); + } + + /** + * + */ + public function test_order_search_by_id_with_includes() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', (string) $order1->get_id() ); + $request->set_param( 'include', array( $order2->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 0, \count( $data ) ); + } + + /** + * + */ + public function test_order_search_by_id_with_excludes() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', (string) $order1->get_id() ); + $request->set_param( 'exclude', array( $order1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 0, \count( $data ) ); + } + + /** + * + */ + public function test_order_search_by_billing_first_name_with_includes() { + $order1 = OrderHelper::create_order(); + $order1->set_billing_first_name( 'John' ); + $order1->save(); + $order2 = OrderHelper::create_order(); + $order2->set_billing_first_name( 'John' ); + $order2->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', 'John' ); + $request->set_param( 'include', array( $order2->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order2->get_id() ), $ids ); + } + + /** + * + */ + public function test_order_search_by_billing_first_name_with_excludes() { + $order1 = OrderHelper::create_order(); + $order1->set_billing_first_name( 'John' ); + $order1->save(); + $order2 = OrderHelper::create_order(); + $order2->set_billing_first_name( 'John' ); + $order2->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/orders' ); + $request->set_param( 'search', 'John' ); + $request->set_param( 'exclude', array( $order1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 0, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $order2->get_id() ), $ids ); + } } diff --git a/tests/includes/API/Test_Product_Variations_Controller.php b/tests/includes/API/Test_Product_Variations_Controller.php index cf85572..df44c07 100644 --- a/tests/includes/API/Test_Product_Variations_Controller.php +++ b/tests/includes/API/Test_Product_Variations_Controller.php @@ -566,10 +566,127 @@ function () { // public function test_variation_orderby_menu_order(): void { // } + /** + * + */ + public function test_variation_get_with_includes() { + $product = ProductHelper::create_variation_product(); // has two variations + $variation_ids = $product->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() . '/variations' ); + $request->set_param( 'include', array( $variation_ids[0] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $variation_ids[0], $data[0]['id'] ); + } + + /** + * + */ + public function test_variation_get_with_excludes() { + $product = ProductHelper::create_variation_product(); // has two variations + $variation_ids = $product->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() . '/variations' ); + $request->set_param( 'exclude', array( $variation_ids[0] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $variation_ids[1], $data[0]['id'] ); + } + + /** + * + */ + public function test_variation_on_sale_with_includes() { + $product = ProductHelper::create_variation_product(); // has two variations + $variation_ids = $product->get_children(); + + // put variations on_sale + $variation1 = new \WC_Product_Variation( $variation_ids[0] ); + $variation1->set_sale_price( 1 ); + $variation1->save(); + + $variation2 = new \WC_Product_Variation( $variation_ids[1] ); + $variation2->set_sale_price( 1 ); + $variation2->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() . '/variations' ); + $variation_ids = $product->get_children(); + $request->set_param( 'on_sale', true ); + $request->set_param( 'include', array( $variation_ids[0] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $variation_ids[0], $data[0]['id'] ); + } + + /** + * + */ + public function test_variation_on_sale_with_excludes() { + $product = ProductHelper::create_variation_product(); // has two variations + $variation_ids = $product->get_children(); + + // put variations on_sale + $variation1 = new \WC_Product_Variation( $variation_ids[0] ); + $variation1->set_sale_price( 1 ); + $variation1->save(); + + $variation2 = new \WC_Product_Variation( $variation_ids[1] ); + $variation2->set_sale_price( 1 ); + $variation2->save(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() . '/variations' ); + $variation_ids = $product->get_children(); + $request->set_param( 'on_sale', true ); + $request->set_param( 'exclude', array( $variation_ids[0] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $variation_ids[1], $data[0]['id'] ); + } + + /** + * + */ + public function test_all_variation_response_contains_barcode(): void { + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => '_some_field', + ); + } + ); + + $product = ProductHelper::create_variation_product(); + $variation_ids = $product->get_children(); + update_post_meta( $variation_ids[0], '_some_field', 'some_string' ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + $response = $this->server->dispatch( $request ); + + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + + $this->assertEquals( 'some_string', $data[0]['barcode'] ); + } + /** * Test search using the products/variations endpoint. */ - public function test_variation_search(): void { + public function test_all_variation_search(): void { // enable barcode and pos_only_products add_filter( 'woocommerce_pos_general_settings', @@ -651,4 +768,101 @@ function () { $this->assertEquals( 1, \count( $data ) ); $this->assertEquals( array( $variation_ids2[1] ), wp_list_pluck( $data, 'id' ) ); } + + /** + * + */ + public function test_all_variation_with_includes() { + $product1 = ProductHelper::create_variation_product(); // has two variations + $variation_ids1 = $product1->get_children(); + $product2 = ProductHelper::create_variation_product(); // has two variations + $variation_ids2 = $product2->get_children(); + $product3 = ProductHelper::create_variation_product(); // has two variations + $variation_ids3 = $product3->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + $request->set_param( 'include', array( $variation_ids1[0], $variation_ids1[1] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[0], $variation_ids1[1] ), $ids ); + } + + /** + * + */ + public function test_all_variation_with_excludes() { + $product1 = ProductHelper::create_variation_product(); // has two variations + $variation_ids1 = $product1->get_children(); + $product2 = ProductHelper::create_variation_product(); // has two variations + $variation_ids2 = $product2->get_children(); + $product3 = ProductHelper::create_variation_product(); // has two variations + $variation_ids3 = $product3->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + $request->set_param( 'exclude', array( $variation_ids1[0], $variation_ids1[1] ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 4, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[1], $variation_ids2[0], $variation_ids2[1], $variation_ids3[0] ), $ids ); + } + + /** + * + */ + public function test_all_variation_search_with_includes() { + $product1 = ProductHelper::create_variation_product(); // has two variations + $variation_ids1 = $product1->get_children(); + $product2 = ProductHelper::create_variation_product(); // has two variations + $variation_ids2 = $product2->get_children(); + $product3 = ProductHelper::create_variation_product(); // has two variations + $variation_ids3 = $product3->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + $request->set_query_params( + array( + 'include' => array( $variation_ids1[0], $variation_ids1[1] ), + 'search' => 'DUMMY SKU VARIABLE SMALL', + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( $variation_ids1[0], $data[0]['id'] ); + } + + /** + * + */ + public function test_all_variation_search_with_excludes() { + $product1 = ProductHelper::create_variation_product(); // has two variations + $variation_ids1 = $product1->get_children(); + $product2 = ProductHelper::create_variation_product(); // has two variations + $variation_ids2 = $product2->get_children(); + $product3 = ProductHelper::create_variation_product(); // has two variations + $variation_ids3 = $product3->get_children(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + $request->set_query_params( + array( + 'exclude' => array( $variation_ids1[0], $variation_ids1[1] ), + 'search' => 'DUMMY SKU VARIABLE SMALL', + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $ids = wp_list_pluck( $data, 'id' ); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids2[0], $variation_ids3[0] ), $ids ); + } } diff --git a/tests/includes/API/Test_Products_Controller.php b/tests/includes/API/Test_Products_Controller.php index dcccd2d..6395f74 100644 --- a/tests/includes/API/Test_Products_Controller.php +++ b/tests/includes/API/Test_Products_Controller.php @@ -645,4 +645,146 @@ function () { $this->assertEqualsCanonicalizing( array( $product2->get_id(), $product3->get_id(), $product4->get_id() ), $ids ); } + + /** + * + */ + public function test_search_title_with_includes() { + $product1 = ProductHelper::create_simple_product(); + $product2 = ProductHelper::create_simple_product(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'search', 'dummy' ); + $request->set_param( 'include', array( $product1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product1->get_id() ), $ids ); + } + + /** + * + */ + public function test_search_sku_with_includes() { + $product1 = ProductHelper::create_simple_product(); + $product2 = ProductHelper::create_simple_product(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'search', 'DUMMY SKU' ); + $request->set_param( 'include', array( $product2->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product2->get_id() ), $ids ); + } + + /** + * + */ + public function test_filter_on_sale_with_includes() { + $product1 = ProductHelper::create_simple_product( + array( + 'sale_price' => 8, + 'on_sale' => true, + ) + ); + $product2 = ProductHelper::create_simple_product(); + $product3 = ProductHelper::create_simple_product( + array( + 'sale_price' => 6, + 'on_sale' => true, + ) + ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'on_sale', true ); + $request->set_param( 'include', array( $product1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product1->get_id() ), $ids ); + } + + /** + * + */ + public function test_search_title_with_excludes() { + $product1 = ProductHelper::create_simple_product(); + $product2 = ProductHelper::create_simple_product(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'search', 'dummy' ); + $request->set_param( 'exclude', array( $product1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product2->get_id() ), $ids ); + } + + /** + * + */ + public function test_search_sku_with_excludes() { + $product1 = ProductHelper::create_simple_product(); + $product2 = ProductHelper::create_simple_product(); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'search', 'DUMMY SKU' ); + $request->set_param( 'exclude', array( $product2->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product1->get_id() ), $ids ); + } + + /** + * + */ + public function test_filter_on_sale_with_excludes() { + $product1 = ProductHelper::create_simple_product( + array( + 'sale_price' => 8, + 'on_sale' => true, + ) + ); + $product2 = ProductHelper::create_simple_product(); + $product3 = ProductHelper::create_simple_product( + array( + 'sale_price' => 6, + 'on_sale' => true, + ) + ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); + $request->set_param( 'on_sale', true ); + $request->set_param( 'exclude', array( $product1->get_id() ) ); + + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + + $ids = wp_list_pluck( $data, 'id' ); + $this->assertEquals( array( $product3->get_id() ), $ids ); + } }