PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
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 +33 -71 1.10.141.10.18 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;
@@ -151,39 +151,8 @@
151 151 */
152 152 $args['post_type'] = $this->post_type;
153 153
154 154 /*
155 - * `search` means the barcode CARRIERS here, not the post title.
156 - *
157 - * WooCommerce maps `search` onto `s`, which searches post_title/content — useless for a
158 - * variation, whose title is a generated attribute string. The POS searches what a cashier
159 - * actually types or scans: the SKU and whichever meta key the store configured as its
160 - * barcode field (`Barcode_Field::search_keys()`). The complete phrase must match one carrier.
161 - *
162 - * `sku` is left to WooCommerce: its own exact/comma-list handling is what the
163 - * sku-beats-search precedence rule relies on.
164 - */
165 - $search = (string) ( $request->get_param( 'search' ) ?? '' );
166 - if ( '' !== $sku ) {
167 - // SKU is an exact lookup and outranks a fuzzy one; leaving WooCommerce's post-title
168 - // `s` in place would AND the two and return nothing.
169 - unset( $args['s'] );
170 - }
171 - if ( '' !== $search && '' === $sku ) {
172 - unset( $args['s'] );
173 - $args['wcpos_variation_search'] = true;
174 - $carriers = array( 'relation' => 'OR' );
175 - foreach ( Barcode_Field::search_keys() as $key ) {
176 - $carriers[] = array(
177 - 'key' => $key,
178 - 'value' => trim( $search ),
179 - 'compare' => 'LIKE',
180 - );
181 - }
182 - $args['meta_query'] = $this->add_meta_query( $args, $carriers ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
183 - }
184 -
185 - /*
186 155 * This route only ever offers what the store owner has for sale — on EVERY lane, including
187 156 * `include`.
188 157 *
189 158 * WooCommerce's Enabled checkbox on the variation metabox writes `post_status = private`
@@ -202,38 +171,25 @@
202 171 * Set after `parent::prepare_objects_query()` so an explicit `status` param cannot widen it.
203 172 */
204 173 $args['post_status'] = 'publish';
205 174
206 - /*
207 - * Leg-3 (ADR 0014 WP-M5): POS-hidden (`online_only`) variations are never served. As a
208 - * query exclusion rather than a post-hoc filter of the result, so paging and totals count
209 - * the same set the client is allowed to see.
210 - *
211 - * Through the helper, NOT a raw `post__not_in` merge: `parent::prepare_objects_query()`
212 - * maps `include` to `post__in`, and WP_Query IGNORES `post__not_in` when `post__in` is
213 - * present — so `?search=X&include=<hidden id>` would have served a hidden variation.
214 - * `apply_to_wp_query_args()` already owns that trap: it intersects `post__in` with the
215 - * hidden set and pins an empty intersection to `array( 0 )`.
216 - */
217 - $args = ( new Pos_Visibility() )->apply_to_wp_query_args( $args, Pos_Visibility::VARIATIONS );
218 -
219 - /*
220 - * The POS sorts on fields WooCommerce does not offer as orderby values. They are
221 - * declared in Sync\Collection_Rules and projected into get_collection_params()
222 - * below — without that, `orderby=sku` is rejected by REST argument validation
223 - * before anything here runs.
224 - *
225 - * They are applied as SQL clauses, NOT as `meta_key` + `orderby => meta_value`:
226 - * that pair INNER JOINs postmeta and drops every variation with no value for the
227 - * key, so the sort silently filtered. `wcpos_posts_clauses()` LEFT JOINs instead
228 - * and orders the meta-less rows last.
229 - */
230 175 $this->wcpos_sort_request = $request;
231 - 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 );
232 178
233 179 return $args;
234 180 }
235 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 +
236 192 /**
237 193 * GET /variations — the flat collection's three lanes, one response shape.
238 194 *
239 195 * `?sku=`/`?search=` discovers by barcode carrier; a bare request serves one
@@ -431,11 +387,20 @@
431 387 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'sku must not contain more than 100 comma-separated terms', array( 'status' => 400 ) );
432 388 }
433 389 } else {
434 390 $search = (string) $request->get_param( 'search' );
435 - if ( self::MAX_SEARCH_LENGTH < \strlen( $search ) ) {
436 - 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 ) );
437 395 }
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 ) ) {
401 + return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not contain more than 10 whitespace-separated terms', array( 'status' => 400 ) );
402 + }
438 403 }
439 404
440 405 if ( self::MAX_PAGE < (int) $request->get_param( 'page' ) ) {
441 406 return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'page must not exceed 1000', array( 'status' => 400 ) );
@@ -453,8 +418,9 @@
453 418 *
454 419 * @param array $clauses Associative array of the clauses for the query.
455 420 * @param WP_Query $wp_query The WP_Query instance.
456 421 *
422 + * @deprecated Collection Rules now installs this behavior; retained for Pro callers.
457 423 * @return array
458 424 */
459 425 public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
460 426 if ( null === $this->wcpos_sort_request ) {
@@ -500,13 +466,13 @@
500 466 * De-duplicate variation searches joined through matching meta rows.
501 467 *
502 468 * @param string $groupby Existing GROUP BY clause.
503 469 * @param WP_Query $query Query being filtered.
470 + *
471 + * @deprecated Collection Rules owns variation grouping.
504 472 */
505 473 public function group_search_results( string $groupby, WP_Query $query ): string {
506 - global $wpdb;
507 -
508 - return ! empty( $query->query_vars['wcpos_variation_search'] ) ? "{$wpdb->posts}.ID" : $groupby;
474 + return Product_Search::variation_groupby( $groupby, $query->query_vars );
509 475 }
510 476
511 477 /**
512 478 * Does this discovery request still carry a term after normalization?
@@ -520,10 +486,11 @@
520 486 return true;
521 487 }
522 488
523 489 $search = (string) ( $request->get_param( 'search' ) ?? '' );
490 + $terms = Collection_Rules::search_terms( trim( $search ) );
524 491
525 - return array() !== (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY );
492 + return array() !== $terms;
526 493 }
527 494
528 495 /**
529 496 * One page of the POS-servable variation collection, with its total.
@@ -596,14 +563,9 @@
596 563 ),
597 564 );
598 565 }
599 566
600 - add_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10, 2 );
601 - try {
602 - $results = $this->get_objects( $query_args );
603 - } finally {
604 - remove_filter( 'posts_groupby', array( $this, 'group_search_results' ), 10 );
605 - }
567 + $results = $this->get_objects( $query_args );
606 568
607 569 $ids = array();
608 570 foreach ( $results['objects'] as $object ) {
609 571 if ( $object instanceof WC_Product_Variation ) {