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/V2/Variations_Controller.php +23 -88 1.10.151.10.19 View file →
@@ -8,15 +8,14 @@
8 8 namespace WCPOS\WooCommercePOS\API\V2;
9 9
10 10 use WC_Product_Variation;
11 11 use WC_REST_Product_Variations_Controller;
12 -use WCPOS\WooCommercePOS\Services\Barcode_Field;
13 12 use WCPOS\WooCommercePOS\Sync\Api;
14 13 use WCPOS\WooCommercePOS\Sync\Collection_Rules;
15 14 use WCPOS\WooCommercePOS\Sync\Collection_Rules_Plan;
16 15 use WCPOS\WooCommercePOS\Sync\Digest_Index;
17 16 use WCPOS\WooCommercePOS\Sync\Endpoint_Permissions;
18 -use WCPOS\WooCommercePOS\Sync\Pos_Visibility;
17 +use WCPOS\WooCommercePOS\Sync\Product_Search;
19 18 use WCPOS\WooCommercePOS\Sync\Product_Serializer;
20 19 use WP_Error;
21 20 use WP_Query;
22 21 use WP_REST_Request;
@@ -59,12 +58,13 @@
59 58 */
60 59 private const WCPOS_SORT_PARAM_MAP = array(
61 60 'orderby' => 'orderby',
62 61 'order' => 'order',
62 + 'search' => 'search',
63 63 );
64 64
65 65 /**
66 - * The request whose declared sort `wcpos_posts_clauses()` applies.
66 + * The request whose declared rules wrap the collection query.
67 67 *
68 68 * @var null|WP_REST_Request
69 69 */
70 70 private $wcpos_sort_request = null;
@@ -73,9 +73,8 @@
73 73
74 74 private const MAX_SKU_LENGTH = 4096;
75 75 private const MAX_SKU_TERMS = 100;
76 76 private const MAX_SEARCH_LENGTH = 256;
77 - private const MAX_SEARCH_TERMS = 10;
78 77 private const MAX_PAGE = 1000;
79 78
80 79
81 80 public function register_routes(): void {
@@ -152,49 +151,8 @@
152 151 */
153 152 $args['post_type'] = $this->post_type;
154 153
155 154 /*
156 - * `search` means the barcode CARRIERS here, not the post title.
157 - *
158 - * WooCommerce maps `search` onto `s`, which searches post_title/content — useless for a
159 - * variation, whose title is a generated attribute string. The POS searches what a cashier
160 - * actually types or scans: the SKU and whichever meta key the store configured as its
161 - * barcode field (`Barcode_Field::search_keys()`). Every term must match at least one carrier.
162 - *
163 - * `sku` is left to WooCommerce: its own exact/comma-list handling is what the
164 - * sku-beats-search precedence rule relies on.
165 - */
166 - $search = (string) ( $request->get_param( 'search' ) ?? '' );
167 - if ( '' !== $sku ) {
168 - // SKU is an exact lookup and outranks a fuzzy one; leaving WooCommerce's post-title
169 - // `s` in place would AND the two and return nothing.
170 - unset( $args['s'] );
171 - }
172 - if ( '' !== $search && '' === $sku ) {
173 - unset( $args['s'] );
174 - $args['wcpos_variation_search'] = true;
175 - $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
176 - if ( false === $terms ) {
177 - $terms = array();
178 - }
179 - $carriers = array( 'relation' => 'AND' );
180 - foreach ( $terms as $term ) {
181 - $term_carriers = array( 'relation' => 'OR' );
182 - foreach ( Barcode_Field::search_keys() as $key ) {
183 - $term_carriers[] = array(
184 - 'key' => $key,
185 - 'value' => $term,
186 - 'compare' => 'LIKE',
187 - );
188 - }
189 - $carriers[] = $term_carriers;
190 - }
191 - if ( 1 < \count( $carriers ) ) {
192 - $args['meta_query'] = $this->add_meta_query( $args, $carriers ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
193 - }
194 - }
195 -
196 - /*
197 155 * This route only ever offers what the store owner has for sale — on EVERY lane, including
198 156 * `include`.
199 157 *
200 158 * WooCommerce's Enabled checkbox on the variation metabox writes `post_status = private`
@@ -213,38 +171,25 @@
213 171 * Set after `parent::prepare_objects_query()` so an explicit `status` param cannot widen it.
214 172 */
215 173 $args['post_status'] = 'publish';
216 174
217 - /*
218 - * Leg-3 (ADR 0014 WP-M5): POS-hidden (`online_only`) variations are never served. As a
219 - * query exclusion rather than a post-hoc filter of the result, so paging and totals count
220 - * the same set the client is allowed to see.
221 - *
222 - * Through the helper, NOT a raw `post__not_in` merge: `parent::prepare_objects_query()`
223 - * maps `include` to `post__in`, and WP_Query IGNORES `post__not_in` when `post__in` is
224 - * present — so `?search=X&include=<hidden id>` would have served a hidden variation.
225 - * `apply_to_wp_query_args()` already owns that trap: it intersects `post__in` with the
226 - * hidden set and pins an empty intersection to `array( 0 )`.
227 - */
228 - $args = ( new Pos_Visibility() )->apply_to_wp_query_args( $args, Pos_Visibility::VARIATIONS );
229 -
230 - /*
231 - * The POS sorts on fields WooCommerce does not offer as orderby values. They are
232 - * declared in Sync\Collection_Rules and projected into get_collection_params()
233 - * below — without that, `orderby=sku` is rejected by REST argument validation
234 - * before anything here runs.
235 - *
236 - * They are applied as SQL clauses, NOT as `meta_key` + `orderby => meta_value`:
237 - * that pair INNER JOINs postmeta and drops every variation with no value for the
238 - * key, so the sort silently filtered. `wcpos_posts_clauses()` LEFT JOINs instead
239 - * and orders the meta-less rows last.
240 - */
241 175 $this->wcpos_sort_request = $request;
242 - add_filter( 'posts_clauses', array( $this, 'wcpos_posts_clauses' ), 10, 2 );
176 + $plan = Collection_Rules::for_request( 'variations', $request, self::WCPOS_SORT_PARAM_MAP );
177 + $args = $plan->filter( Collection_Rules_Plan::HOOK_PREPARE_ARGS, $args );
243 178
244 179 return $args;
245 180 }
246 181
182 + /** Apply the same rule topology to discovery, collection pages, and named includes. */
183 + protected function get_objects( $query_args ) {
184 + $plan = Collection_Rules::for_request( 'variations', $this->wcpos_sort_request, self::WCPOS_SORT_PARAM_MAP );
185 + return $plan->around(
186 + function () use ( $query_args ) {
187 + return parent::get_objects( $query_args );
188 + }
189 + );
190 + }
191 +
247 192 /**
248 193 * GET /variations — the flat collection's three lanes, one response shape.
249 194 *
250 195 * `?sku=`/`?search=` discovers by barcode carrier; a bare request serves one
@@ -450,13 +395,10 @@
450 395 }
451 396 if ( self::MAX_SEARCH_LENGTH < $characters ) {
452 397 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not exceed 256 characters', array( 'status' => 400 ) );
453 398 }
454 - $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
455 - if ( false === $terms ) {
456 - return new WP_Error( 'woocommerce_pos_variations_search_invalid', 'search must be valid UTF-8', array( 'status' => 400 ) );
457 - }
458 - if ( self::MAX_SEARCH_TERMS < \count( $terms ) ) {
399 + $terms = Collection_Rules::search_terms( trim( $search ) );
400 + if ( Collection_Rules::rules( 'variations' )['search']['term_cap'] < \count( $terms ) ) {
459 401 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not contain more than 10 whitespace-separated terms', array( 'status' => 400 ) );
460 402 }
461 403 }
462 404
@@ -476,8 +418,9 @@
476 418 *
477 419 * @param array $clauses Associative array of the clauses for the query.
478 420 * @param WP_Query $wp_query The WP_Query instance.
479 421 *
422 + * @deprecated Collection Rules now installs this behavior; retained for Pro callers.
480 423 * @return array
481 424 */
482 425 public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
483 426 if ( null === $this->wcpos_sort_request ) {
@@ -523,13 +466,13 @@
523 466 * De-duplicate variation searches joined through matching meta rows.
524 467 *
525 468 * @param string $groupby Existing GROUP BY clause.
526 469 * @param WP_Query $query Query being filtered.
470 + *
471 + * @deprecated Collection Rules owns variation grouping.
527 472 */
528 473 public function group_search_results( string $groupby, WP_Query $query ): string {
529 - global $wpdb;
530 -
531 - return ! empty( $query->query_vars['wcpos_variation_search'] ) ? "{$wpdb->posts}.ID" : $groupby;
474 + return Product_Search::variation_groupby( $groupby, $query->query_vars );
532 475 }
533 476
534 477 /**
535 478 * Does this discovery request still carry a term after normalization?
@@ -543,12 +486,9 @@
543 486 return true;
544 487 }
545 488
546 489 $search = (string) ( $request->get_param( 'search' ) ?? '' );
547 - $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
548 - if ( false === $terms ) {
549 - return false;
550 - }
490 + $terms = Collection_Rules::search_terms( trim( $search ) );
551 491
552 492 return array() !== $terms;
553 493 }
554 494
@@ -623,14 +563,9 @@
623 563 ),
624 564 );
625 565 }
626 566
627 - add_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10, 2 );
628 - try {
629 - $results = $this->get_objects( $query_args );
630 - } finally {
631 - remove_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10 );
632 - }
567 + $results = $this->get_objects( $query_args );
633 568
634 569 $ids = array();
635 570 foreach ( $results['objects'] as $object ) {
636 571 if ( $object instanceof WC_Product_Variation ) {