diff --git a/includes/API/Customers_Controller.php b/includes/API/Customers_Controller.php index d3e62df..a75a055 100644 --- a/includes/API/Customers_Controller.php +++ b/includes/API/Customers_Controller.php @@ -2,9 +2,9 @@ namespace WCPOS\WooCommercePOS\API; -\defined('ABSPATH') || die; +\defined( 'ABSPATH' ) || die; -if ( ! class_exists('WC_REST_Customers_Controller') ) { +if ( ! class_exists( 'WC_REST_Customers_Controller' ) ) { return; } @@ -25,6 +25,7 @@ class Customers_Controller extends WC_REST_Customers_Controller { use Traits\Uuid_Handler; use Traits\WCPOS_REST_API; + use Traits\Query_Helpers; /** * Endpoint namespace. @@ -58,8 +59,8 @@ public function get_item_schema() { $schema = parent::get_item_schema(); // Check and remove email format validation from the billing property - if (isset($schema['properties']['billing']['properties']['email']['format'])) { - unset($schema['properties']['billing']['properties']['email']['format']); + if ( isset( $schema['properties']['billing']['properties']['email']['format'] ) ) { + unset( $schema['properties']['billing']['properties']['email']['format'] ); } return $schema; @@ -73,7 +74,7 @@ public function get_collection_params() { $params = parent::get_collection_params(); // Check if 'orderby' is set and is an array before modifying it - if (isset($params['orderby']) && \is_array($params['orderby']['enum'])) { + if ( isset( $params['orderby'] ) && \is_array( $params['orderby']['enum'] ) ) { // Add new fields to the 'orderby' enum list $new_orderby_options = array( 'first_name', @@ -82,8 +83,8 @@ public function get_collection_params() { 'role', 'username', ); - foreach ($new_orderby_options as $option) { - if ( ! \in_array($option, $params['orderby']['enum'], true)) { + foreach ( $new_orderby_options as $option ) { + if ( ! \in_array( $option, $params['orderby']['enum'], true ) ) { $params['orderby']['enum'][] = $option; } } @@ -137,14 +138,14 @@ public function update_item( $request ) { public function wcpos_validate_billing_email( WP_REST_Request $request ) { // Your custom validation logic for the request data $billing = $request['billing'] ?? null; - $email = \is_array($billing) ? ($billing['email'] ?? null) : null; - - if ( ! \is_null($email) && '' !== $email && ! is_email( $email ) ) { + $email = \is_array( $billing ) ? ( $billing['email'] ?? null ) : null; + + if ( ! \is_null( $email ) && '' !== $email && ! is_email( $email ) ) { return new \WP_Error( 'rest_invalid_param', // translators: Use default WordPress translation __( 'Invalid email address.' ), - array('status' => 400) + array( 'status' => 400 ) ); } @@ -205,18 +206,24 @@ public function wcpos_customer_response( WP_REST_Response $response, WP_User $us $customer = new WC_Customer( $user_data->ID ); $raw_meta_data = $customer->get_meta_data(); - $filtered_meta_data = array_filter($raw_meta_data, function ($meta) { - return '_woocommerce_pos_uuid' === $meta->key; - }); + $filtered_meta_data = array_filter( + $raw_meta_data, + function ( $meta ) { + return '_woocommerce_pos_uuid' === $meta->key; + } + ); // Convert to WC REST API expected format - $data['meta_data'] = array_map(function ($meta) { - return array( - 'id' => $meta->id, - 'key' => $meta->key, - 'value' => $meta->value, - ); - }, array_values($filtered_meta_data)); + $data['meta_data'] = array_map( + function ( $meta ) { + return array( + 'id' => $meta->id, + 'key' => $meta->key, + 'value' => $meta->value, + ); + }, + array_values( $filtered_meta_data ) + ); } catch ( Exception $e ) { Logger::log( 'Error getting customer meta data: ' . $e->getMessage() ); } @@ -277,18 +284,18 @@ public function wcpos_get_all_posts( array $fields = array() ): array { public function wcpos_customer_query( array $prepared_args, WP_REST_Request $request ): array { $query_params = $request->get_query_params(); - // Existing meta query - $existing_meta_query = $prepared_args['meta_query'] ?? array(); - // add modified_after date_modified_gmt if ( isset( $query_params['modified_after'] ) && '' !== $query_params['modified_after'] ) { $timestamp = strtotime( $query_params['modified_after'] ); - $prepared_args['meta_query'] = array( + $prepared_args['meta_query'] = $this->wcpos_combine_meta_queries( array( - 'key' => 'last_update', - 'value' => $timestamp ? (string) $timestamp : '', - 'compare' => '>', + array( + 'key' => 'last_update', + 'value' => $timestamp ? (string) $timestamp : '', + 'compare' => '>', + ), ), + $prepared_args['meta_query'] ); } @@ -340,7 +347,7 @@ public function wcpos_customer_query( array $prepared_args, WP_REST_Request $req unset( $prepared_args['search'] ); $prepared_args['_wcpos_search'] = $search_keyword; // store the search keyword for later use add_action( 'pre_user_query', array( $this, 'wcpos_search_user_table' ) ); - + $search_meta_query = array( 'relation' => 'OR', array( @@ -380,21 +387,10 @@ public function wcpos_customer_query( array $prepared_args, WP_REST_Request $req 'compare' => 'LIKE', ), ); - - // Merge with existing meta_query if any - if ( ! empty($existing_meta_query)) { - $existing_meta_query = array( - 'relation' => 'AND', - $existing_meta_query, - $search_meta_query, - ); - } else { - $existing_meta_query = $search_meta_query; - } - } - // Apply the modified or newly created meta_query - $prepared_args['meta_query'] = $existing_meta_query; + // Combine the search meta_query with the existing meta_query + $prepared_args['meta_query'] = $this->wcpos_combine_meta_queries( $search_meta_query, $prepared_args['meta_query'] ); + } return $prepared_args; } @@ -406,7 +402,7 @@ public function wcpos_customer_query( array $prepared_args, WP_REST_Request $req */ public function wcpos_search_user_table( $query ): void { global $wpdb; - + // Remove the hook remove_action( 'pre_user_query', array( $this, 'wcpos_search_user_table' ) ); @@ -417,7 +413,7 @@ public function wcpos_search_user_table( $query ): void { // Prepare the LIKE statement $like_email = '%' . $wpdb->esc_like( $search_keyword ) . '%'; $like_login = '%' . $wpdb->esc_like( $search_keyword ) . '%'; - + $insertion = $wpdb->prepare( "({$wpdb->users}.user_email LIKE %s) OR ({$wpdb->users}.user_login LIKE %s) OR ", $like_email, @@ -427,10 +423,10 @@ public function wcpos_search_user_table( $query ): void { $pattern = "/\(\s*\w+\.meta_key\s*=\s*'[^']+'\s*AND\s*\w+\.meta_value\s*LIKE\s*'[^']+'\s*\)(\s*OR\s*\(\s*\w+\.meta_key\s*=\s*'[^']+'\s*AND\s*\w+\.meta_value\s*LIKE\s*'[^']+'\s*\))*\s*/"; // Add the search keyword to the query - $modified_where = preg_replace($pattern, "$insertion$0", $query->query_where); + $modified_where = preg_replace( $pattern, "$insertion$0", $query->query_where ); // Check if the replacement was successful and assign it back to query_where - if ($modified_where !== $query->query_where) { + if ( $modified_where !== $query->query_where ) { $query->query_where = $modified_where; } } diff --git a/includes/API/Product_Variations_Controller.php b/includes/API/Product_Variations_Controller.php index 5f765f4..a89dd3e 100644 --- a/includes/API/Product_Variations_Controller.php +++ b/includes/API/Product_Variations_Controller.php @@ -27,6 +27,7 @@ class Product_Variations_Controller extends WC_REST_Product_Variations_Controlle use Traits\Product_Helpers; use Traits\Uuid_Handler; use Traits\WCPOS_REST_API; + use Traits\Query_Helpers; /** * Endpoint namespace. @@ -288,7 +289,7 @@ protected function prepare_objects_query( $request ) { // Add online_only check if ( $this->wcpos_pos_only_products_enabled() ) { - $default_meta_query = array( + $meta_query = array( 'relation' => 'OR', array( 'key' => '_pos_visibility', @@ -301,56 +302,91 @@ protected function prepare_objects_query( $request ) { ), ); - if ( isset( $args['meta_query'] ) ) { - if ( ! isset( $args['meta_query']['relation'] ) ) { - $args['meta_query']['relation'] = 'AND'; - } - $args['meta_query'] = array_merge_recursive( $args['meta_query'], $default_meta_query ); - } else { - $args['meta_query'] = $default_meta_query; - } + // Combine meta queries + $args['meta_query'] = $this->wcpos_combine_meta_queries( $args['meta_query'], $meta_query ); + }; return $args; } /** - * Endpoint for searching all product variations. + * Endpoint for getting all product variations, eg: search for sku or barcode. * * @param WP_REST_Request $request Full details about the request. */ public function wcpos_get_all_items( $request ) { - // Prepare arguments for the product query - $args = array( - 'post_type' => 'product_variation', - 'posts_per_page' => 10, // Limit to 10 items per page - 'orderby' => 'ID', // Default ordering - 'order' => 'ASC', - 'paged' => ! empty( $request['page'] ) ? $request['page'] : 1, // Handle pagination - ); + $query_args = $this->prepare_objects_query( $request ); + if ( is_wp_error( current( $query_args ) ) ) { + return current( $query_args ); + } // Check if 'search' param is set for SKU search - if ( ! empty( $request['search'] ) ) { - $args['meta_query'] = array( - array( - 'key' => '_sku', - 'value' => $request['search'], - 'compare' => 'LIKE', - ), + if ( ! empty( $query_args['s'] ) ) { + $barcode_field = $this->wcpos_get_barcode_field(); + $meta_query = array( + 'key' => '_sku', + 'value' => $query_args['s'], + 'compare' => 'LIKE', ); + + if ( $barcode_field && '_sku' !== $barcode_field ) { + $meta_query = array( + 'relation' => 'OR', + $meta_query, + array( + 'key' => $barcode_field, + 'value' => $query_args['s'], + 'compare' => 'LIKE', + ), + ); + } + + // Combine meta queries + $query_args['meta_query'] = $this->wcpos_combine_meta_queries( $query_args['meta_query'], $meta_query ); + + unset( $query_args['s'] ); + } + + $query_results = $this->get_objects( $query_args ); + + $objects = array(); + foreach ( $query_results['objects'] as $object ) { + if ( ! \wc_rest_check_post_permissions( $this->post_type, 'read', $object->get_id() ) ) { + continue; + } + + $data = $this->prepare_object_for_response( $object, $request ); + $objects[] = $this->prepare_response_for_collection( $data ); } - // Get product variations - $query = new WP_Query( $args ); - $variations = array(); + $page = (int) $query_args['paged']; + $max_pages = $query_results['pages']; - foreach ( $query->posts as $variation ) { - $object = new WC_Product_Variation( $variation->ID ); - $response = $this->prepare_object_for_response( $object, $request ); - $variations[] = $this->prepare_response_for_collection( $response ); + $response = rest_ensure_response( $objects ); + $response->header( 'X-WP-Total', $query_results['total'] ); + $response->header( 'X-WP-TotalPages', (int) $max_pages ); + + /** + * Note: custom endpoint for getting all product variations + */ + $base = 'products/variations'; + $base = add_query_arg( $request->get_query_params(), rest_url( sprintf( '/%s/%s', $this->namespace, $base ) ) ); + + 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 the response - return new WP_REST_Response( $variations, 200 ); + return $response; } } diff --git a/includes/API/Products_Controller.php b/includes/API/Products_Controller.php index 7119849..4bdf15d 100644 --- a/includes/API/Products_Controller.php +++ b/includes/API/Products_Controller.php @@ -2,9 +2,9 @@ namespace WCPOS\WooCommercePOS\API; -\defined('ABSPATH') || die; +\defined( 'ABSPATH' ) || die; -if ( ! class_exists('WC_REST_Products_Controller') ) { +if ( ! class_exists( 'WC_REST_Products_Controller' ) ) { return; } @@ -26,6 +26,7 @@ class Products_Controller extends WC_REST_Products_Controller { use Traits\Product_Helpers; use Traits\Uuid_Handler; use Traits\WCPOS_REST_API; + use Traits\Query_Helpers; /** * Endpoint namespace. @@ -57,21 +58,21 @@ public function __construct() { */ public function get_item_schema() { $schema = parent::get_item_schema(); - + // Add the 'barcode' property if 'properties' exists and is an array - if (isset($schema['properties']) && \is_array($schema['properties'])) { + if ( isset( $schema['properties'] ) && \is_array( $schema['properties'] ) ) { $schema['properties']['barcode'] = array( - 'description' => __('Barcode', 'woocommerce-pos'), + 'description' => __( 'Barcode', 'woocommerce-pos' ), 'type' => 'string', - 'context' => array('view', 'edit'), + 'context' => array( 'view', 'edit' ), 'readonly' => false, ); } // Check for 'stock_quantity' and allow decimal - if ($this->wcpos_allow_decimal_quantities() && - isset($schema['properties']['stock_quantity']) && - \is_array($schema['properties']['stock_quantity'])) { + if ( $this->wcpos_allow_decimal_quantities() && + isset( $schema['properties']['stock_quantity'] ) && + \is_array( $schema['properties']['stock_quantity'] ) ) { $schema['properties']['stock_quantity']['type'] = 'string'; } @@ -84,14 +85,14 @@ public function get_item_schema() { */ public function get_collection_params() { $params = parent::get_collection_params(); - + // Check if 'per_page' parameter exists and has a 'minimum' key before modifying - if (isset($params['per_page']) && \is_array($params['per_page'])) { + if ( isset( $params['per_page'] ) && \is_array( $params['per_page'] ) ) { $params['per_page']['minimum'] = -1; } // Ensure 'orderby' is set and is an array before attempting to modify it - if (isset($params['orderby']['enum']) && \is_array($params['orderby']['enum'])) { + if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) { // Define new sorting options $new_sort_options = array( 'sku', @@ -100,9 +101,9 @@ public function get_collection_params() { 'stock_status', ); // Merge new options, avoiding duplicates - $params['orderby']['enum'] = array_unique(array_merge($params['orderby']['enum'], $new_sort_options)); + $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], $new_sort_options ) ); } - + return $params; } @@ -250,11 +251,11 @@ public function wcpos_posts_search( string $search, WP_Query $wp_query ) { $fields_to_search[] = $barcode_field; } - foreach ((array) $q['search_terms'] as $term) { - $term = $n . $wpdb->esc_like($term) . $n; + foreach ( (array) $q['search_terms'] as $term ) { + $term = $n . $wpdb->esc_like( $term ) . $n; foreach ( $fields_to_search as $field ) { - if ( \in_array( $field, array('post_title'), true ) ) { + if ( \in_array( $field, array( 'post_title' ), true ) ) { $search .= $wpdb->prepare( "{$searchand}($wpdb->posts.$field LIKE %s)", $term ); } else { $search .= $wpdb->prepare( "{$searchand}(pm1.meta_value LIKE %s AND pm1.meta_key = '$field')", $term ); @@ -265,7 +266,7 @@ public function wcpos_posts_search( string $search, WP_Query $wp_query ) { if ( ! empty( $search ) ) { $search = " AND ({$search}) "; - if ( ! is_user_logged_in()) { + if ( ! is_user_logged_in() ) { $search .= " AND ($wpdb->posts.post_password = '') "; } } @@ -333,7 +334,7 @@ public function wcpos_product_query( array $args, WP_REST_Request $request ): ar public function wcpos_posts_join_to_products_search( string $join, WP_Query $wp_query ) { global $wpdb; - if ( ! empty($wp_query->query_vars['s']) && false === strpos($join, 'pm1')) { + if ( ! empty( $wp_query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) { $join .= " LEFT JOIN {$wpdb->postmeta} pm1 ON {$wpdb->posts}.ID = pm1.post_id "; } @@ -445,7 +446,7 @@ protected function prepare_objects_query( $request ) { // Add online_only check if ( $this->wcpos_pos_only_products_enabled() ) { - $default_meta_query = array( + $meta_query = array( 'relation' => 'OR', array( 'key' => '_pos_visibility', @@ -457,15 +458,8 @@ protected function prepare_objects_query( $request ) { 'compare' => '!=', ), ); - - if (isset($args['meta_query'])) { - if ( ! isset($args['meta_query']['relation'])) { - $args['meta_query']['relation'] = 'AND'; - } - $args['meta_query'] = array_merge_recursive($args['meta_query'], $default_meta_query); - } else { - $args['meta_query'] = $default_meta_query; - } + + $args['meta_query'] = $this->wcpos_combine_meta_queries( $meta_query, $args['meta_query'] ); }; return $args; diff --git a/includes/API/Traits/Product_Helpers.php b/includes/API/Traits/Product_Helpers.php index e2890fa..146a242 100644 --- a/includes/API/Traits/Product_Helpers.php +++ b/includes/API/Traits/Product_Helpers.php @@ -13,8 +13,8 @@ trait Product_Helpers { * * @TODO - should we return a wc_placeholder_img_src if no image is available? * - * @param array|false $image { - * Array of image data, or boolean false if no image is available. + * @param array|false $image { + * Array of image data, or boolean false if no image is available. * * @var string $0 Image source URL. * @var int $1 Image width in pixels. @@ -72,7 +72,7 @@ public function wcpos_get_barcode_field() { return ''; } - + // Check for non-string values if ( ! \is_string( $barcode_field ) ) { Logger::log( 'Unexpected data type for barcode_field. Expected string, got: ' . \gettype( $barcode_field ) ); diff --git a/includes/API/Traits/Query_Helpers.php b/includes/API/Traits/Query_Helpers.php new file mode 100644 index 0000000..4113c29 --- /dev/null +++ b/includes/API/Traits/Query_Helpers.php @@ -0,0 +1,37 @@ + 'AND' ) ); + return $combined; + } + + // If both meta_queries are not empty and do not both have 'AND', combine them with 'AND' relation. + return array( + 'relation' => 'AND', + $meta_query1, + $meta_query2, + ); + } +} diff --git a/includes/Services/Settings.php b/includes/Services/Settings.php index 4822cbb..e18a007 100644 --- a/includes/Services/Settings.php +++ b/includes/Services/Settings.php @@ -115,16 +115,16 @@ public function get_settings( string $id, $key = null ) { return $settings; } - if ( ! isset( $settings[$key] ) ) { + if ( ! isset( $settings[ $key ] ) ) { return new WP_Error( 'woocommerce_pos_settings_error', // translators: 1. %s: Settings group id, 2. %s: Settings key - sprintf( __( 'Settings with id %s and key %s not found', 'woocommerce-pos' ), $id, $key ), + sprintf( __( 'Settings with id %1$s and key %2$s not found', 'woocommerce-pos' ), $id, $key ), array( 'status' => 400 ) ); } - return $settings[$key]; + return $settings[ $key ]; } return new WP_Error( @@ -213,7 +213,7 @@ public function get_checkout_settings(): array { return apply_filters( 'woocommerce_pos_checkout_settings', $settings ); } - + public function get_access_settings(): array { global $wp_roles; $role_caps = array(); @@ -277,7 +277,7 @@ public function get_tools_settings(): array { return apply_filters( 'woocommerce_pos_tools_settings', $settings ); } - + public function get_license_settings() { /* * Filters the license settings. @@ -329,7 +329,7 @@ public function get_order_statuses(): array { return array_map( 'wc_get_order_status_name', $order_statuses ); } - + public function get_payment_gateways_settings() { // Note: I need to re-init the gateways here to pass the tests, but it seems to work fine in the app. WC_Payment_Gateways::instance()->init(); @@ -382,7 +382,7 @@ public function get_payment_gateways_settings() { * @return bool|WP_Error */ public static function delete_settings( $id ) { - if ( ! is_super_admin() && ! current_user_can('manage_woocommerce_pos') ) { + if ( ! is_super_admin() && ! current_user_can( 'manage_woocommerce_pos' ) ) { return new WP_Error( 'unauthorized', 'You do not have permission to delete this option.' ); } @@ -395,10 +395,10 @@ public static function delete_settings( $id ) { * @return bool|WP_Error */ public static function delete_all_settings() { - if ( ! is_super_admin() && ! current_user_can('manage_woocommerce_pos') ) { + if ( ! is_super_admin() && ! current_user_can( 'manage_woocommerce_pos' ) ) { return new WP_Error( 'unauthorized', 'You do not have permission to delete this option.' ); } - + foreach ( self::$default_settings as $id => $settings ) { delete_option( self::$db_prefix . $id ); } diff --git a/tests/includes/API/Test_Product_Variations_Controller.php b/tests/includes/API/Test_Product_Variations_Controller.php index fe79365..cf85572 100644 --- a/tests/includes/API/Test_Product_Variations_Controller.php +++ b/tests/includes/API/Test_Product_Variations_Controller.php @@ -5,6 +5,7 @@ use Automattic\WooCommerce\RestApi\UnitTests\Helpers\ProductHelper; use Ramsey\Uuid\Uuid; use WCPOS\WooCommercePOS\API\Product_Variations_Controller; +use WCPOS\WooCommercePOS\Products; /** * @internal @@ -42,6 +43,7 @@ public function test_register_routes(): void { $this->assertArrayHasKey( '/wcpos/v1/products/(?P[\d]+)/variations/(?P[\d]+)', $routes ); $this->assertArrayHasKey( '/wcpos/v1/products/(?P[\d]+)/variations/batch', $routes ); $this->assertArrayHasKey( '/wcpos/v1/products/(?P[\d]+)/variations/generate', $routes ); + $this->assertArrayHasKey( '/wcpos/v1/products/variations', $routes ); } /** @@ -563,4 +565,90 @@ function () { // */ // public function test_variation_orderby_menu_order(): void { // } + + /** + * Test search using the products/variations endpoint. + */ + public function test_variation_search(): void { + // enable barcode and pos_only_products + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => '_barcode', + 'pos_only_products' => true, + ); + } + ); + + $random_sku1 = wp_generate_password( 8, false ); + $random_sku2 = wp_generate_password( 8, false ); + $random_barcode1 = wp_generate_password( 10, false ); + $random_barcode2 = wp_generate_password( 10, false ); + + // create two variable products + $product1 = ProductHelper::create_variation_product(); // has two variations + $variation_ids1 = $product1->get_children(); + update_post_meta( $variation_ids1[0], '_sku', $random_sku1 ); + update_post_meta( $variation_ids1[0], '_barcode', $random_barcode1 ); + update_post_meta( $variation_ids1[1], '_sku', $random_sku2 ); + update_post_meta( $variation_ids1[1], '_barcode', $random_barcode2 ); + + $product2 = ProductHelper::create_variation_product(); // has two variations + $variation_ids2 = $product2->get_children(); + update_post_meta( $variation_ids2[0], '_sku', $random_sku1 ); + update_post_meta( $variation_ids2[0], '_barcode', $random_barcode1 ); + update_post_meta( $variation_ids2[1], '_sku', $random_sku2 ); + update_post_meta( $variation_ids2[1], '_barcode', $random_barcode2 ); + + $request = $this->wp_rest_get_request( '/wcpos/v1/products/variations' ); + + // empty search + $request->set_query_params( array( 'search' => '' ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 4, \count( $data ) ); + + // search for sku1 + $request->set_query_params( array( 'search' => $random_sku1 ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[0], $variation_ids2[0] ), wp_list_pluck( $data, 'id' ) ); + + // search for sku2 + $request->set_query_params( array( 'search' => $random_sku2 ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[1], $variation_ids2[1] ), wp_list_pluck( $data, 'id' ) ); + + // search for barcode1 + $request->set_query_params( array( 'search' => $random_barcode1 ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[0], $variation_ids2[0] ), wp_list_pluck( $data, 'id' ) ); + + // search for barcode2 + $request->set_query_params( array( 'search' => $random_barcode2 ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 2, \count( $data ) ); + $this->assertEqualsCanonicalizing( array( $variation_ids1[1], $variation_ids2[1] ), wp_list_pluck( $data, 'id' ) ); + + // make sure online_only variations are not returned + update_post_meta( $variation_ids1[1], '_pos_visibility', 'online_only' ); + $request->set_query_params( array( 'search' => $random_barcode2 ) ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1, \count( $data ) ); + $this->assertEquals( array( $variation_ids2[1] ), wp_list_pluck( $data, 'id' ) ); + } } diff --git a/tests/includes/API/Test_Products_Controller.php b/tests/includes/API/Test_Products_Controller.php index 3d2d195..dcccd2d 100644 --- a/tests/includes/API/Test_Products_Controller.php +++ b/tests/includes/API/Test_Products_Controller.php @@ -22,15 +22,15 @@ public function tearDown(): void { } public function test_namespace_property(): void { - $namespace = $this->get_reflected_property_value('namespace'); + $namespace = $this->get_reflected_property_value( 'namespace' ); - $this->assertEquals('wcpos/v1', $namespace ); + $this->assertEquals( 'wcpos/v1', $namespace ); } public function test_rest_base(): void { - $rest_base = $this->get_reflected_property_value('rest_base'); + $rest_base = $this->get_reflected_property_value( 'rest_base' ); - $this->assertEquals('products', $rest_base); + $this->assertEquals( 'products', $rest_base ); } /** @@ -145,7 +145,7 @@ public function test_product_api_get_all_ids(): void { $product2 = ProductHelper::create_simple_product(); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); $request->set_param( 'posts_per_page', -1 ); - $request->set_param( 'fields', array('id') ); + $request->set_param( 'fields', array( 'id' ) ); $response = $this->server->dispatch( $request ); $this->assertEquals( 200, $response->get_status() ); @@ -162,37 +162,40 @@ public function test_product_api_get_all_ids(): void { public function test_product_response_contains_uuid_meta_data(): void { $product = ProductHelper::create_simple_product(); $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $found = false; $uuid_value = ''; $count = 0; // Look for the _woocommerce_pos_uuid key in meta_data - foreach ($data['meta_data'] as $meta) { - if ('_woocommerce_pos_uuid' === $meta['key']) { + foreach ( $data['meta_data'] as $meta ) { + if ( '_woocommerce_pos_uuid' === $meta['key'] ) { $count++; $uuid_value = $meta['value']; } } - $this->assertEquals(1, $count, 'There should only be one _woocommerce_pos_uuid.'); - $this->assertTrue(Uuid::isValid($uuid_value), 'The UUID value is not valid.'); + $this->assertEquals( 1, $count, 'There should only be one _woocommerce_pos_uuid.' ); + $this->assertTrue( Uuid::isValid( $uuid_value ), 'The UUID value is not valid.' ); } /** * Barcode. */ public function test_product_get_barcode(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'barcode_field' => 'foo', - ); - }); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => 'foo', + ); + } + ); $product = ProductHelper::create_simple_product(); $product->update_meta_data( 'foo', 'bar' ); @@ -201,43 +204,51 @@ public function test_product_get_barcode(): void { public function test_product_response_contains_barcode(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'barcode_field' => '_some_field', - ); - }); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => '_some_field', + ); + } + ); $product = ProductHelper::create_simple_product(); $product->update_meta_data( '_some_field', 'some_string' ); $product->save_meta_data(); $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() ); - $response = $this->server->dispatch($request); - + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); - - $this->assertEquals(200, $response->get_status()); - + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 'some_string', $data['barcode'] ); } public function test_product_update_barcode(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'barcode_field' => 'barcode', - ); - }); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => 'barcode', + ); + } + ); $product = ProductHelper::create_simple_product( array( 'sku' => 'sku-12345' ) ); $request = $this->wp_rest_patch_request( '/wcpos/v1/products/' . $product->get_id() ); - $request->set_body_params( array( - 'barcode' => 'foo-12345', - ) ); - $response = $this->server->dispatch($request); - + $request->set_body_params( + array( + 'barcode' => 'foo-12345', + ) + ); + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); - - $this->assertEquals(200, $response->get_status()); - + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 'foo-12345', $data['barcode'] ); } @@ -250,7 +261,12 @@ public function test_product_orderby_sku(): void { $product3 = ProductHelper::create_simple_product( array( 'sku' => '123456789' ) ); $product4 = ProductHelper::create_simple_product( array( 'sku' => 'alpha' ) ); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $request->set_query_params( array( 'orderby' => 'sku', 'order' => 'asc' ) ); + $request->set_query_params( + array( + 'orderby' => 'sku', + 'order' => 'asc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'sku' ); @@ -258,7 +274,12 @@ public function test_product_orderby_sku(): void { $this->assertEquals( $skus, array( '123456789', '987654321', 'alpha', 'zeta' ) ); // reverse order - $request->set_query_params( array( 'orderby' => 'sku', 'order' => 'desc' ) ); + $request->set_query_params( + array( + 'orderby' => 'sku', + 'order' => 'desc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'sku' ); @@ -267,11 +288,14 @@ public function test_product_orderby_sku(): void { } public function test_product_orderby_barcode(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'barcode_field' => '_barcode', - ); - }); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => '_barcode', + ); + } + ); $product1 = ProductHelper::create_simple_product(); $product1->update_meta_data( '_barcode', 'alpha' ); @@ -282,7 +306,12 @@ public function test_product_orderby_barcode(): void { $product2->save_meta_data(); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $request->set_query_params( array( 'orderby' => 'barcode', 'order' => 'asc' ) ); + $request->set_query_params( + array( + 'orderby' => 'barcode', + 'order' => 'asc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $barcodes = wp_list_pluck( $data, 'barcode' ); @@ -290,7 +319,12 @@ public function test_product_orderby_barcode(): void { $this->assertEquals( $barcodes, array( 'alpha', 'zeta' ) ); // reverse order - $request->set_query_params( array( 'orderby' => 'barcode', 'order' => 'desc' ) ); + $request->set_query_params( + array( + 'orderby' => 'barcode', + 'order' => 'desc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $barcodes = wp_list_pluck( $data, 'barcode' ); @@ -302,7 +336,12 @@ public function test_product_orderby_stock_status(): void { $product1 = ProductHelper::create_simple_product( array( 'stock_status' => 'instock' ) ); $product2 = ProductHelper::create_simple_product( array( 'stock_status' => 'outofstock' ) ); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $request->set_query_params( array( 'orderby' => 'stock_status', 'order' => 'asc' ) ); + $request->set_query_params( + array( + 'orderby' => 'stock_status', + 'order' => 'asc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_status' ); @@ -310,7 +349,12 @@ public function test_product_orderby_stock_status(): void { $this->assertEquals( $skus, array( 'instock', 'outofstock' ) ); // reverse order - $request->set_query_params( array( 'orderby' => 'stock_status', 'order' => 'desc' ) ); + $request->set_query_params( + array( + 'orderby' => 'stock_status', + 'order' => 'desc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_status' ); @@ -319,14 +363,44 @@ public function test_product_orderby_stock_status(): void { } public function test_product_orderby_stock_quantity(): void { - $product1 = ProductHelper::create_simple_product( array( 'stock_quantity' => 1, 'manage_stock' => true ) ); - $product2 = ProductHelper::create_simple_product( array( 'stock_quantity' => 2, 'manage_stock' => true ) ); - $product3 = ProductHelper::create_simple_product( array( 'stock_quantity' => null, 'manage_stock' => true ) ); - $product4 = ProductHelper::create_simple_product( array( 'stock_quantity' => 0, 'manage_stock' => true ) ); - $product5 = ProductHelper::create_simple_product( array( 'stock_quantity' => -1, 'manage_stock' => true ) ); + $product1 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => 1, + 'manage_stock' => true, + ) + ); + $product2 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => 2, + 'manage_stock' => true, + ) + ); + $product3 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => null, + 'manage_stock' => true, + ) + ); + $product4 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => 0, + 'manage_stock' => true, + ) + ); + $product5 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => -1, + 'manage_stock' => true, + ) + ); $product6 = ProductHelper::create_simple_product(); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $request->set_query_params( array( 'orderby' => 'stock_quantity', 'order' => 'asc' ) ); + $request->set_query_params( + array( + 'orderby' => 'stock_quantity', + 'order' => 'asc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_quantity' ); @@ -334,12 +408,17 @@ public function test_product_orderby_stock_quantity(): void { $this->assertEquals( $skus, array( -1, 0, 1, 2, null, null ) ); // reverse order - $request->set_query_params( array( 'orderby' => 'stock_quantity', 'order' => 'desc' ) ); + $request->set_query_params( + array( + 'orderby' => 'stock_quantity', + 'order' => 'desc', + ) + ); $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_quantity' ); - $this->assertEquals( $skus, array( 2, 1, 0 , -1, null, null ) ); + $this->assertEquals( $skus, array( 2, 1, 0, -1, null, null ) ); } /** @@ -366,12 +445,12 @@ public function test_product_response_with_decimal_quantities(): void { $product->save(); $request = $this->wp_rest_get_request( '/wcpos/v1/products/' . $product->get_id() ); - $response = $this->server->dispatch($request); - + $response = $this->server->dispatch( $request ); + $data = $response->get_data(); - - $this->assertEquals(200, $response->get_status()); - + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 1.5, $data['stock_quantity'] ); } @@ -385,14 +464,16 @@ public function test_product_update_decimal_quantities(): void { $product->save(); $request = $this->wp_rest_patch_request( '/wcpos/v1/products/' . $product->get_id() ); - $request->set_body_params( array( - 'stock_quantity' => '3.85', - ) ); - $response = $this->server->dispatch($request); + $request->set_body_params( + array( + 'stock_quantity' => '3.85', + ) + ); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - - $this->assertEquals(200, $response->get_status()); - + + $this->assertEquals( 200, $response->get_status() ); + $this->assertEquals( 3.85, $data['stock_quantity'] ); } @@ -400,20 +481,45 @@ public function test_product_orderby_decimal_stock_quantity(): void { $this->setup_decimal_quantity_tests(); $this->assertTrue( woocommerce_pos_get_settings( 'general', 'decimal_qty' ) ); - $product1 = ProductHelper::create_simple_product( array( 'stock_quantity' => '11.2', 'manage_stock' => true ) ); - $product2 = ProductHelper::create_simple_product( array( 'stock_quantity' => '3.5', 'manage_stock' => true ) ); - $product2 = ProductHelper::create_simple_product( array( 'stock_quantity' => '20.7', 'manage_stock' => true ) ); + $product1 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => '11.2', + 'manage_stock' => true, + ) + ); + $product2 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => '3.5', + 'manage_stock' => true, + ) + ); + $product2 = ProductHelper::create_simple_product( + array( + 'stock_quantity' => '20.7', + 'manage_stock' => true, + ) + ); $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $request->set_query_params( array( 'orderby' => 'stock_quantity', 'order' => 'asc' ) ); - $response = $this->server->dispatch($request); + $request->set_query_params( + array( + 'orderby' => 'stock_quantity', + 'order' => 'asc', + ) + ); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_quantity' ); $this->assertEquals( $skus, array( 3.5, 11.2, 20.7 ) ); // reverse order - $request->set_query_params( array( 'orderby' => 'stock_quantity', 'order' => 'desc' ) ); - $response = $this->server->dispatch($request); + $request->set_query_params( + array( + 'orderby' => 'stock_quantity', + 'order' => 'desc', + ) + ); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); $skus = wp_list_pluck( $data, 'stock_quantity' ); @@ -421,20 +527,33 @@ public function test_product_orderby_decimal_stock_quantity(): void { } public function test_product_search(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'barcode_field' => '_barcode', - ); - }); - - $random_title = wp_generate_password(12, false); - $random_sku = wp_generate_password(8, false); - $random_barcode = wp_generate_password(10, false); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'barcode_field' => '_barcode', + ); + } + ); + + $random_title = wp_generate_password( 12, false ); + $random_sku = wp_generate_password( 8, false ); + $random_barcode = wp_generate_password( 10, false ); $random_description = 'A string containing ' . $random_title . ' and ' . $random_sku . ' and ' . $random_barcode; $product1 = ProductHelper::create_simple_product( array( 'description' => $random_description ) ); - $product2 = ProductHelper::create_simple_product( array( 'description' => $random_description, 'name' => 'Foo ' . $random_title . ' bar' ) ); - $product3 = ProductHelper::create_simple_product( array( 'description' => $random_description, 'sku' => 'foo-' . $random_sku . '-bar' ) ); + $product2 = ProductHelper::create_simple_product( + array( + 'description' => $random_description, + 'name' => 'Foo ' . $random_title . ' bar', + ) + ); + $product3 = ProductHelper::create_simple_product( + array( + 'description' => $random_description, + 'sku' => 'foo-' . $random_sku . '-bar', + ) + ); $product4 = ProductHelper::create_simple_product( array( 'description' => $random_description ) ); $product4->update_meta_data( '_barcode', 'foo-' . $random_barcode . '-bar' ); $product4->save_meta_data(); @@ -443,32 +562,32 @@ public function test_product_search(): void { // empty search $request->set_query_params( array( 'search' => '' ) ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 4, \count( $data ) ); // search for title $request->set_query_params( array( 'search' => $random_title ) ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 1, \count( $data ) ); $this->assertEquals( $product2->get_id(), $data[0]['id'] ); // search for sku $request->set_query_params( array( 'search' => $random_sku ) ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 1, \count( $data ) ); $this->assertEquals( $product3->get_id(), $data[0]['id'] ); // search for barcode $request->set_query_params( array( 'search' => $random_barcode ) ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 1, \count( $data ) ); $this->assertEquals( $product4->get_id(), $data[0]['id'] ); } @@ -477,17 +596,20 @@ public function test_product_search(): void { * Online Only products. */ public function test_pos_only_products(): void { - add_filter( 'woocommerce_pos_general_settings', function() { - return array( - 'pos_only_products' => true, - ); - }); + add_filter( + 'woocommerce_pos_general_settings', + function () { + return array( + 'pos_only_products' => true, + ); + } + ); // online only $product1 = ProductHelper::create_simple_product(); $product1->update_meta_data( '_pos_visibility', 'online_only' ); $product1->save_meta_data(); - + // both $product2 = ProductHelper::create_simple_product(); $product2->update_meta_data( '_pos_visibility', '' ); @@ -497,17 +619,17 @@ public function test_pos_only_products(): void { $product3 = ProductHelper::create_simple_product(); $product3->update_meta_data( '_pos_visibility', 'pos_only' ); $product3->save_meta_data(); - + // new product - $product4 = ProductHelper::create_simple_product(); + $product4 = ProductHelper::create_simple_product(); // test get all ids $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); $request->set_param( 'posts_per_page', -1 ); - $request->set_param( 'fields', array('id') ); - $response = $this->server->dispatch($request); + $request->set_param( 'fields', array( 'id' ) ); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 3, \count( $data ) ); $ids = wp_list_pluck( $data, 'id' ); @@ -515,9 +637,9 @@ public function test_pos_only_products(): void { // test products response $request = $this->wp_rest_get_request( '/wcpos/v1/products' ); - $response = $this->server->dispatch($request); + $response = $this->server->dispatch( $request ); $data = $response->get_data(); - $this->assertEquals(200, $response->get_status()); + $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( 3, \count( $data ) ); $ids = wp_list_pluck( $data, 'id' );