PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.19
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.19
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
← All changes | includes/API/V1/Products_Controller.php +63 -60 1.10.31.10.19 View file →
@@ -21,8 +21,10 @@
21 21 use WCPOS\WooCommercePOS\API\Product_Search;
22 22 use WCPOS\WooCommercePOS\Logger;
23 23 use WCPOS\WooCommercePOS\Services\Barcode_Field;
24 24 use WCPOS\WooCommercePOS\Services\Variable_Price_Range;
25 +use WCPOS\WooCommercePOS\Sync\Collection_Rules;
26 +use WCPOS\WooCommercePOS\Sync\Collection_Rules_Plan;
25 27 use WCPOS\WooCommercePOS\Sync\Pos_Visibility;
26 28 use WP_Error;
27 29 use WP_Query;
28 30 use WP_REST_Request;
@@ -60,8 +62,19 @@
60 62 */
61 63 protected $wcpos_request;
62 64
63 65 /**
66 + * Request keys the product Collection Rules plan reads on this lane.
67 + *
68 + * @var array
69 + */
70 + private const WCPOS_SORT_PARAM_MAP = array(
71 + 'orderby' => 'orderby',
72 + 'order' => 'order',
73 + 'search' => 'search',
74 + );
75 +
76 + /**
64 77 * Memoized parent collection params.
65 78 *
66 79 * @var array|null
67 80 */
@@ -82,10 +95,8 @@
82 95
83 96 add_filter( 'woocommerce_rest_prepare_product_object', array( $this, 'wcpos_product_response' ), 10, 3 );
84 97 add_action( 'woocommerce_rest_insert_product_object', array( $this, 'wcpos_insert_product_object' ), 10, 3 );
85 98 add_filter( 'woocommerce_rest_product_object_query', array( $this, 'wcpos_product_query' ), 10, 2 );
86 - add_filter( 'posts_search', array( $this, 'wcpos_posts_search' ), 10, 2 );
87 - add_filter( 'posts_clauses', array( $this, 'wcpos_posts_clauses' ), 10, 2 );
88 99
89 100 /*
90 101 * Check if the request is for all products and if the 'posts_per_page' is set to -1.
91 102 * Optimised query for getting all product IDs.
@@ -97,8 +108,23 @@
97 108 return $dispatch_result;
98 109 }
99 110
100 111 /**
112 + * Apply the collection's declared rules for the duration of a direct read.
113 + *
114 + * @param WP_REST_Request $request Full details about the request.
115 + * @return \WP_Error|\WP_REST_Response
116 + */
117 + public function get_items( $request ) {
118 + $plan = Collection_Rules::for_request( 'products', $request, self::WCPOS_SORT_PARAM_MAP );
119 + return $plan->around(
120 + function () use ( $request ) {
121 + return parent::get_items( $request );
122 + }
123 + );
124 + }
125 +
126 + /**
101 127 * Create a single product.
102 128 *
103 129 * @param WP_REST_Request $request Full details about the request.
104 130 *
@@ -179,8 +205,14 @@
179 205 if ( isset( $params['per_page'] ) && \is_array( $params['per_page'] ) ) {
180 206 $params['per_page']['minimum'] = -1;
181 207 }
182 208
209 + // Search text is literal on every lane: `sanitize_text_field` would strip `%30`
210 + // and blank malformed UTF-8 before the declared search rule ever saw them.
211 + if ( isset( $params['search'] ) && \is_array( $params['search'] ) ) {
212 + $params['search']['sanitize_callback'] = 'rest_sanitize_request_arg';
213 + }
214 +
183 215 if ( ! $this->wcpos_parent_collection_supports_param( 'brand' ) ) {
184 216 $params['brand'] = array(
185 217 'description' => /* translators: REST API collection parameter description. */ __( 'Limit result set to products assigned to brand IDs or slugs, separated by commas.', 'woocommerce-pos' ),
186 218 'type' => 'string',
@@ -219,15 +251,12 @@
219 251 }
220 252
221 253 // Ensure 'orderby' is set and is an array before attempting to modify it.
222 254 if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) {
223 - // Define new sorting options.
224 - $new_sort_options = array(
225 - 'sku',
226 - 'barcode',
227 - 'stock_quantity',
228 - 'stock_status',
229 - );
255 + // The POS sorts are DECLARED once, in Sync\Collection_Rules, and projected here.
256 + // The v2 proxy lane claims the same list, so a sort cannot be advertised on one
257 + // Read Lane and rejected on the other (#1779).
258 + $new_sort_options = Collection_Rules::orderby_enum( 'products' );
230 259 // Merge new options, avoiding duplicates.
231 260 $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], $new_sort_options ) );
232 261 }
233 262
@@ -358,8 +387,10 @@
358 387 * @param string $search The search SQL query.
359 388 * @param WP_Query $wp_query The WP_Query instance (passed by reference).
360 389 *
361 390 * @return string
391 + *
392 + * @deprecated Collection Rules now installs this behavior.
362 393 */
363 394 public function wcpos_posts_search( string $search, WP_Query $wp_query ) {
364 395 return Product_Search::posts_search( $search, $wp_query );
365 396 }
@@ -373,26 +404,20 @@
373 404 * @param string[] $clauses Associative array of the clauses for the query.
374 405 * @param WP_Query $wp_query The WP_Query instance (passed by reference).
375 406 */
376 407 public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
377 - global $wpdb;
408 + if ( ! isset( $this->wcpos_request ) ) {
409 + return $clauses;
410 + }
378 411
379 - // Handle NULL values in stock_quantity sorting
380 - // By default, MySQL sorts NULLs first in ASC and last in DESC
381 - // We want NULLs to always be last regardless of sort direction.
382 - if ( isset( $this->wcpos_request ) ) {
383 - $orderby = $this->wcpos_request->get_param( 'orderby' );
384 - $order = strtoupper( $this->wcpos_request->get_param( 'order' ) ?? 'ASC' );
412 + $post_type = $wp_query->query_vars['post_type'] ?? null;
413 + if ( 'product' !== $post_type && ( ! \is_array( $post_type ) || ! \in_array( 'product', $post_type, true ) ) ) {
414 + return $clauses;
415 + }
385 416
386 - if ( 'stock_quantity' === $orderby ) {
387 - // Modify ORDER BY to put NULLs last
388 - // Use CASE to assign a sort priority: non-NULL = 0, NULL = 1
389 - // Then sort by the actual value.
390 - $clauses['orderby'] = "{$wpdb->postmeta}.meta_value IS NULL ASC, {$wpdb->postmeta}.meta_value + 0 {$order}";
391 - }
392 - }
417 + $plan = Collection_Rules::for_request( 'products', $this->wcpos_request, self::WCPOS_SORT_PARAM_MAP );
393 418
394 - return $clauses;
419 + return $plan->filter( Collection_Rules_Plan::HOOK_POSTS_CLAUSES, $clauses, $wp_query );
395 420 }
396 421
397 422 /**
398 423 * Filter the query arguments for a request.
@@ -402,19 +427,8 @@
402 427 *
403 428 * @return array $args Key value array of query var to query value.
404 429 */
405 430 public function wcpos_product_query( array $args, WP_REST_Request $request ) {
406 - if ( ! empty( $request['search'] ) ) {
407 - // We need to set the query up for a postmeta join.
408 - add_filter( 'posts_join', array( $this, 'wcpos_posts_join_to_products_search' ), 10, 2 );
409 - add_filter( 'posts_groupby', array( $this, 'wcpos_posts_groupby_product_search' ), 10, 2 );
410 - }
411 -
412 - // if POS only products are enabled, exclude online-only products.
413 - if ( $this->wcpos_pos_only_products_enabled() ) {
414 - add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_exclude_online_only' ), 10, 2 );
415 - }
416 -
417 431 // Check for wcpos_include/wcpos_exclude parameter.
418 432 if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) {
419 433 add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_include_exclude' ), 20, 2 );
420 434 }
@@ -579,8 +593,10 @@
579 593 * @param string $join The JOIN clause of the query.
580 594 * @param WP_Query $query The WP_Query instance (passed by reference).
581 595 *
582 596 * @return string
597 + *
598 + * @deprecated Collection Rules now installs this behavior.
583 599 */
584 600 public function wcpos_posts_join_to_products_search( string $join, WP_Query $query ) {
585 601 return Product_Search::posts_join( $join, $query );
586 602 }
@@ -591,8 +607,10 @@
591 607 * @param string $groupby The GROUP BY clause of the query.
592 608 * @param WP_Query $query The WP_Query instance (passed by reference).
593 609 *
594 610 * @return string
611 + *
612 + * @deprecated Collection Rules now installs this behavior.
595 613 */
596 614 public function wcpos_posts_groupby_product_search( string $groupby, WP_Query $query ) {
597 615 return Product_Search::posts_groupby( $groupby, $query );
598 616 }
@@ -606,8 +624,10 @@
606 624 * @param string $where The WHERE clause of the query.
607 625 * @param WP_Query $query The WP_Query instance (passed by reference).
608 626 *
609 627 * @return string
628 + *
629 + * @deprecated Collection Rules now installs this behavior.
610 630 */
611 631 public function wcpos_posts_where_product_exclude_online_only( string $where, WP_Query $query ) {
612 632 global $wpdb;
613 633
@@ -689,33 +709,16 @@
689 709 protected function prepare_objects_query( $request ) {
690 710 $args = parent::prepare_objects_query( $request );
691 711 $args = $this->wcpos_apply_store_api_tax_operator_fallbacks( $args, $request );
692 712
693 - // Add custom 'orderby' options.
694 - if ( isset( $request['orderby'] ) ) {
695 - switch ( $request['orderby'] ) {
696 - case 'sku':
697 - $args['meta_key'] = '_sku';
698 - $args['orderby'] = 'meta_value';
699 -
700 - break;
701 - case 'barcode':
702 - $args['meta_key'] = Barcode_Field::orderby_key();
703 - $args['orderby'] = 'meta_value';
704 -
705 - break;
706 - case 'stock_quantity':
707 - $args['meta_key'] = '_stock';
708 - $args['orderby'] = 'meta_value_num';
709 -
710 - break;
711 - case 'stock_status':
712 - $args['meta_key'] = '_stock_status';
713 - $args['orderby'] = 'meta_value';
714 -
715 - break;
716 - }
717 - }
713 + /*
714 + * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT
715 + * mapped onto `meta_key` + `orderby => meta_value` here any more. That pair
716 + * INNER JOINs postmeta, so it dropped every product with no value for the key —
717 + * a sort acting as a filter (#1779 follow-up). `Sync\Collection_Rules` declares
718 + * them and `wcpos_posts_clauses()` applies them as a LEFT JOIN, on this lane and
719 + * on the v2 proxy alike.
720 + */
718 721
719 722 return $args;
720 723 }
721 724 }