PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 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 All 169 releases
← All changes | includes/API/V1/Product_Variations_Controller.php +65 -38 1.10.3 → 1.10.17 View file →
@@ -17,8 +17,10 @@
17 17 use WC_Data;
18 18 use WC_REST_Product_Variations_Controller;
19 19 use WCPOS\WooCommercePOS\Logger;
20 20 use WCPOS\WooCommercePOS\Services\Barcode_Field;
21 +use WCPOS\WooCommercePOS\Sync\Collection_Rules;
22 +use WCPOS\WooCommercePOS\Sync\Collection_Rules_Plan;
21 23 use WCPOS\WooCommercePOS\Sync\Pos_Visibility;
22 24 use WP_Error;
23 25 use WP_Query;
24 26 use WP_REST_Request;
@@ -45,13 +47,27 @@
45 47
46 48 /**
47 49 * Store the request object for use in lifecycle methods.
48 50 *
49 - * @var WP_REST_Request
51 + * Null until `wcpos_dispatch_request()` runs: the instance exists, and its filters are
52 + * registered, before any request is assigned — which is why the readers guard with
53 + * `isset()`. Matches the same property on `API\V1\Products_Controller`.
54 + *
55 + * @var null|WP_REST_Request
50 56 */
51 57 protected $wcpos_request;
52 58
53 59 /**
60 + * Request keys the variation Collection Rules plan reads on this lane.
61 + *
62 + * @var array
63 + */
64 + private const WCPOS_SORT_PARAM_MAP = array(
65 + 'orderby' => 'orderby',
66 + 'order' => 'order',
67 + );
68 +
69 + /**
54 70 * Dispatch request to parent controller, or override if needed.
55 71 *
56 72 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
57 73 * @param WP_REST_Request $request Request used to generate the response.
@@ -64,8 +80,9 @@
64 80 add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 );
65 81 add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 );
66 82 add_filter( 'woocommerce_rest_product_variation_object_query', array( $this, 'wcpos_product_variation_query' ), 10, 2 );
67 83 add_filter( 'posts_search', array( $this, 'wcpos_posts_search' ), 10, 2 );
84 + add_filter( 'posts_clauses', array( $this, 'wcpos_posts_clauses' ), 10, 2 );
68 85
69 86 /*
70 87 * Check if the request is for all products and if the 'posts_per_page' is set to -1.
71 88 * Optimised query for getting all product IDs.
@@ -170,16 +187,11 @@
170 187 }
171 188
172 189 // Ensure 'orderby' is set and is an array before attempting to modify it.
173 190 if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) {
174 - // Define new sorting options.
175 - $new_sort_options = array(
176 - 'sku',
177 - 'barcode',
178 - 'stock_quantity',
179 - 'stock_status',
180 - );
181 - // Merge new options, avoiding duplicates.
191 + // DECLARED once, in Sync\Collection_Rules, and projected here — so a sort cannot
192 + // be advertised on one lane and rejected on the other.
193 + $new_sort_options = Collection_Rules::orderby_enum( 'variations' );
182 194 $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], $new_sort_options ) );
183 195 }
184 196
185 197 return $params;
@@ -276,8 +288,9 @@
276 288
277 289 // Meta fields to search.
278 290 $meta_fields = Barcode_Field::search_keys();
279 291
292 + $meta_placeholders = implode( ', ', array_fill( 0, \count( $meta_fields ), '%s' ) );
280 293 $search_conditions = array();
281 294
282 295 foreach ( $search_terms as $term ) {
283 296 $term = $n . $wpdb->esc_like( $term ) . $n;
@@ -282,15 +295,20 @@
282 295 foreach ( $search_terms as $term ) {
283 296 $term = $n . $wpdb->esc_like( $term ) . $n;
284 297
285 298 // Search in meta fields.
286 - foreach ( $meta_fields as $field ) {
287 - $search_conditions[] = $wpdb->prepare( '(pm1.meta_value LIKE %s AND pm1.meta_key = %s)', $term, $field );
288 - }
299 + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names come from $wpdb; $meta_placeholders is a generated list of %s placeholders, and the keys themselves are passed to prepare() as arguments.
300 + $search_conditions[] = $wpdb->prepare(
301 + "EXISTS (
302 + SELECT 1 FROM {$wpdb->postmeta} AS wcpos_search_meta WHERE wcpos_search_meta.post_id = {$wpdb->posts}.ID AND wcpos_search_meta.meta_key IN ($meta_placeholders) AND wcpos_search_meta.meta_value LIKE %s
303 + )",
304 + array_merge( $meta_fields, array( $term ) )
305 + );
306 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
289 307 }
290 308
291 309 if ( ! empty( $search_conditions ) ) {
292 - $search = ' AND (' . implode( ' OR ', $search_conditions ) . ') ';
310 + $search = ' AND (' . implode( ' AND ', $search_conditions ) . ') ';
293 311 if ( ! is_user_logged_in() ) {
294 312 $search .= " AND ($wpdb->posts.post_password = '') ";
295 313 }
296 314 }
@@ -460,8 +478,35 @@
460 478 }
461 479
462 480
463 481 /**
482 + * Apply the declared POS variation sorts to the SQL clauses.
483 + *
484 + * `posts_clauses` fires for EVERY WP_Query, so the body is guarded by post type and by
485 + * the plan itself — it contributes nothing unless this request claimed one of the
486 + * declared sorts.
487 + *
488 + * @param array $clauses Associative array of the clauses for the query.
489 + * @param WP_Query $wp_query The WP_Query instance.
490 + *
491 + * @return array
492 + */
493 + public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
494 + if ( ! isset( $this->wcpos_request ) ) {
495 + return $clauses;
496 + }
497 +
498 + $post_type = $wp_query->query_vars['post_type'] ?? null;
499 + if ( 'product_variation' !== $post_type && ( ! \is_array( $post_type ) || ! \in_array( 'product_variation', $post_type, true ) ) ) {
500 + return $clauses;
501 + }
502 +
503 + $plan = Collection_Rules::for_request( 'variations', $this->wcpos_request, self::WCPOS_SORT_PARAM_MAP );
504 +
505 + return $plan->filter( Collection_Rules_Plan::HOOK_POSTS_CLAUSES, $clauses, $wp_query );
506 + }
507 +
508 + /**
464 509 * Prepare objects query.
465 510 *
466 511 * @param WP_REST_Request $request Full details about the request.
467 512 *
@@ -469,33 +514,15 @@
469 514 */
470 515 protected function prepare_objects_query( $request ) {
471 516 $args = parent::prepare_objects_query( $request );
472 517
473 - // Add custom 'orderby' options.
474 - if ( isset( $request['orderby'] ) ) {
475 - switch ( $request['orderby'] ) {
476 - case 'sku':
477 - $args['meta_key'] = '_sku';
478 - $args['orderby'] = 'meta_value';
479 -
480 - break;
481 - case 'barcode':
482 - $args['meta_key'] = Barcode_Field::orderby_key();
483 - $args['orderby'] = 'meta_value';
484 -
485 - break;
486 - case 'stock_quantity':
487 - $args['meta_key'] = '_stock';
488 - $args['orderby'] = 'meta_value_num';
489 -
490 - break;
491 - case 'stock_status':
492 - $args['meta_key'] = '_stock_status';
493 - $args['orderby'] = 'meta_value';
494 -
495 - break;
496 - }
497 - }
518 + /*
519 + * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT mapped
520 + * onto `meta_key` + `orderby => meta_value` here any more. That pair INNER JOINs
521 + * postmeta, so it dropped every variation with no value for the key — a sort acting
522 + * as a filter. `Sync\Collection_Rules` declares them and `wcpos_posts_clauses()`
523 + * applies them as a LEFT JOIN, on this lane and on `wcpos/v2` alike.
524 + */
498 525
499 526 return $args;
500 527 }
501 528 }