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 +32 -80 1.10.91.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,45 +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 a 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 - $carriers = array( 'relation' => 'AND' );
176 - foreach ( (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ) as $term ) {
177 - $term_carriers = array( 'relation' => 'OR' );
178 - foreach ( Barcode_Field::search_keys() as $key ) {
179 - $term_carriers[] = array(
180 - 'key' => $key,
181 - 'value' => $term,
182 - 'compare' => 'LIKE',
183 - );
184 - }
185 - $carriers[] = $term_carriers;
186 - }
187 - if ( 1 < \count( $carriers ) ) {
188 - $args['meta_query'] = $this->add_meta_query( $args, $carriers ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
189 - }
190 - }
191 -
192 - /*
193 155 * This route only ever offers what the store owner has for sale — on EVERY lane, including
194 156 * `include`.
195 157 *
196 158 * WooCommerce's Enabled checkbox on the variation metabox writes `post_status = private`
@@ -209,38 +171,25 @@
209 171 * Set after `parent::prepare_objects_query()` so an explicit `status` param cannot widen it.
210 172 */
211 173 $args['post_status'] = 'publish';
212 174
213 - /*
214 - * Leg-3 (ADR 0014 WP-M5): POS-hidden (`online_only`) variations are never served. As a
215 - * query exclusion rather than a post-hoc filter of the result, so paging and totals count
216 - * the same set the client is allowed to see.
217 - *
218 - * Through the helper, NOT a raw `post__not_in` merge: `parent::prepare_objects_query()`
219 - * maps `include` to `post__in`, and WP_Query IGNORES `post__not_in` when `post__in` is
220 - * present — so `?search=X&include=<hidden id>` would have served a hidden variation.
221 - * `apply_to_wp_query_args()` already owns that trap: it intersects `post__in` with the
222 - * hidden set and pins an empty intersection to `array( 0 )`.
223 - */
224 - $args = ( new Pos_Visibility() )->apply_to_wp_query_args( $args, Pos_Visibility::VARIATIONS );
225 -
226 - /*
227 - * The POS sorts on fields WooCommerce does not offer as orderby values. They are
228 - * declared in Sync\Collection_Rules and projected into get_collection_params()
229 - * below — without that, `orderby=sku` is rejected by REST argument validation
230 - * before anything here runs.
231 - *
232 - * They are applied as SQL clauses, NOT as `meta_key` + `orderby => meta_value`:
233 - * that pair INNER JOINs postmeta and drops every variation with no value for the
234 - * key, so the sort silently filtered. `wcpos_posts_clauses()` LEFT JOINs instead
235 - * and orders the meta-less rows last.
236 - */
237 175 $this->wcpos_sort_request = $request;
238 - 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 );
239 178
240 179 return $args;
241 180 }
242 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 +
243 192 /**
244 193 * GET /variations — the flat collection's three lanes, one response shape.
245 194 *
246 195 * `?sku=`/`?search=` discovers by barcode carrier; a bare request serves one
@@ -438,13 +387,18 @@
438 387 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'sku must not contain more than 100 comma-separated terms', array( 'status' => 400 ) );
439 388 }
440 389 } else {
441 390 $search = (string) $request->get_param( 'search' );
442 - if ( self::MAX_SEARCH_LENGTH < \strlen( $search ) ) {
443 - return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not exceed 256 bytes', array( 'status' => 400 ) );
391 + // Unlike mb_strlen(), PCRE is independent of blog_charset and detects malformed UTF-8.
392 + $characters = preg_match_all( '/./us', $search );
393 + if ( false === $characters ) {
394 + return new WP_Error( 'woocommerce_pos_variations_search_invalid', 'search must be valid UTF-8', array( 'status' => 400 ) );
444 395 }
445 - $terms = (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
446 - if ( self::MAX_SEARCH_TERMS < \count( $terms ) ) {
396 + if ( self::MAX_SEARCH_LENGTH < $characters ) {
397 + return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not exceed 256 characters', array( 'status' => 400 ) );
398 + }
399 + $terms = Collection_Rules::search_terms( trim( $search ) );
400 + if ( Collection_Rules::rules( 'variations' )['search']['term_cap'] < \count( $terms ) ) {
447 401 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not contain more than 10 whitespace-separated terms', array( 'status' => 400 ) );
448 402 }
449 403 }
450 404
@@ -464,8 +418,9 @@
464 418 *
465 419 * @param array $clauses Associative array of the clauses for the query.
466 420 * @param WP_Query $wp_query The WP_Query instance.
467 421 *
422 + * @deprecated Collection Rules now installs this behavior; retained for Pro callers.
468 423 * @return array
469 424 */
470 425 public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
471 426 if ( null === $this->wcpos_sort_request ) {
@@ -490,8 +445,9 @@
490 445 * code. 1.9.x extended the same enum for the same reason.
491 446 */
492 447 public function get_collection_params() {
493 448 $params = parent::get_collection_params();
449 + $params['search']['sanitize_callback'] = 'rest_sanitize_request_arg';
494 450
495 451 if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) {
496 452 $params['orderby']['enum'] = array_values(
497 453 array_unique(
@@ -510,13 +466,13 @@
510 466 * De-duplicate variation searches joined through matching meta rows.
511 467 *
512 468 * @param string $groupby Existing GROUP BY clause.
513 469 * @param WP_Query $query Query being filtered.
470 + *
471 + * @deprecated Collection Rules owns variation grouping.
514 472 */
515 473 public function group_search_results( string $groupby, WP_Query $query ): string {
516 - global $wpdb;
517 -
518 - return ! empty( $query->query_vars['wcpos_variation_search'] ) ? "{$wpdb->posts}.ID" : $groupby;
474 + return Product_Search::variation_groupby( $groupby, $query->query_vars );
519 475 }
520 476
521 477 /**
522 478 * Does this discovery request still carry a term after normalization?
@@ -530,10 +486,11 @@
530 486 return true;
531 487 }
532 488
533 489 $search = (string) ( $request->get_param( 'search' ) ?? '' );
490 + $terms = Collection_Rules::search_terms( trim( $search ) );
534 491
535 - return array() !== (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
492 + return array() !== $terms;
536 493 }
537 494
538 495 /**
539 496 * One page of the POS-servable variation collection, with its total.
@@ -606,14 +563,9 @@
606 563 ),
607 564 );
608 565 }
609 566
610 - add_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10, 2 );
611 - try {
612 - $results = $this->get_objects( $query_args );
613 - } finally {
614 - remove_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10 );
615 - }
567 + $results = $this->get_objects( $query_args );
616 568
617 569 $ids = array();
618 570 foreach ( $results['objects'] as $object ) {
619 571 if ( $object instanceof WC_Product_Variation ) {