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/Sync/Collection_Rules_Plan.php +7 -169 1.10.20 → 1.10.17 View file →
@@ -27,12 +27,13 @@
27 27 * set this plan has taken ownership of.
28 28 *
29 29 * # Two application modes
30 30 *
31 - * `filter()` also serves legacy callbacks (Pro subclasses them). This method never
32 - * touches global filter state; search and visibility use the scoped `around()` path.
31 + * `filter()` is the direct lane: the v1 controller keeps its own `add_filter` topology
32 + * (Pro subclasses those callbacks) and each callback body hands its value here. This
33 + * method never touches global filter state.
33 34 *
34 - * `around()` is the scoped read path and the ONLY path that installs anything. Bindings are
35 + * `around()` is the proxy lane and the ONLY path that installs anything. Bindings are
35 36 * captured as closures — never re-derived tuples — installed, and unwound in reverse
36 37 * inside a `finally`, so a throwing forward leaves `$wp_filter` exactly as it found it.
37 38 *
38 39 * # The WooCommerce-owned sorts
@@ -157,29 +158,8 @@
157 158 */
158 159 private $request_order;
159 160
160 161 /**
161 - * Literal search phrase.
162 - *
163 - * @var string
164 - */
165 - private $search = '';
166 -
167 - /**
168 - * Exact SKU lookup, taking precedence over variation search.
169 - *
170 - * @var string
171 - */
172 - private $sku = '';
173 -
174 - /**
175 - * The lane's declared visibility type.
176 - *
177 - * @var string|null
178 - */
179 - private $visibility_type;
180 -
181 - /**
182 162 * Build a plan. Use `Collection_Rules::for_request()`.
183 163 *
184 164 * @internal
185 165 *
@@ -191,10 +171,8 @@
191 171 */
192 172 public function __construct( string $collection, array $rules, string $storage, WP_REST_Request $request, array $param_map ) {
193 173 $this->collection = $collection;
194 174 $this->rules = $rules;
195 - $lane = 0 === strpos( $request->get_route(), '/wcpos/v1/' ) ? 'direct' : 'proxy';
196 - $this->rules['search'] = array_replace( $rules['search'] ?? array(), $rules['search']['lanes'][ $lane ] ?? array() );
197 175 $this->storage = $storage;
198 176
199 177 $order_key = $this->request_key( $param_map, 'order' );
200 178 $raw_order = null === $order_key ? null : $request->get_param( $order_key );
@@ -201,29 +179,8 @@
201 179 $this->request_order = \is_string( $raw_order ) && '' !== $raw_order ? $raw_order : null;
202 180
203 181 $this->claim_sort( $request, $param_map );
204 182 $this->claim_filters( $request, $param_map );
205 - $key = $this->request_key( $param_map, $rules['search']['param'] ?? 'search' );
206 - $search = null === $key ? null : $request->get_param( $key );
207 - if ( isset( $rules['search'] ) && \is_string( $search ) && '' !== $search ) {
208 - $terms = Collection_Rules::search_terms( $search );
209 - if ( 'orders' !== $collection || array() !== $terms || false === preg_match( '//u', $search ) ) {
210 - $this->search = $search;
211 - if ( 'products' === $collection && false !== preg_match( '//u', $search ) ) {
212 - $parts = Collection_Rules::search_terms( $search, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE );
213 - $last = end( $parts );
214 - $this->search = array() === $parts ? '' : substr( $search, $parts[0][1], $last[1] + strlen( $last[0] ) - $parts[0][1] );
215 - }
216 - $this->claims['search'] = $this->search;
217 - $this->claimed_keys[] = $key;
218 - }
219 - }
220 - $sku_param = $this->rules['search']['exact_sku_param'] ?? null;
221 - if ( null !== $sku_param ) {
222 - $this->sku = trim( (string) ( $request->get_param( $sku_param ) ?? '' ), " \t\n\r\0\x0B," );
223 - }
224 - $type = $rules['visibility']['type'] ?? null;
225 - $this->visibility_type = \is_array( $type ) ? $type[ $lane ] : $type;
226 183 }
227 184
228 185 /**
229 186 * The collection this plan was built for.
@@ -248,9 +205,9 @@
248 205 *
249 206 * @return bool
250 207 */
251 208 public function is_empty(): bool {
252 - return null === $this->sort && array() === $this->claims && null === $this->visibility_type;
209 + return null === $this->sort && array() === $this->claims;
253 210 }
254 211
255 212 /**
256 213 * The canonical sort this plan owns, or null.
@@ -305,15 +262,9 @@
305 262 *
306 263 * @return mixed
307 264 */
308 265 public function filter( string $hook, $value, ...$context ) {
309 - $value = $this->apply_read_rule( $hook, $value, $context[0] ?? null );
310 266 switch ( $hook ) {
311 - case 'posts_search':
312 - case 'posts_join':
313 - case 'posts_groupby':
314 - case 'search_orderby':
315 - return $value;
316 267 case self::HOOK_QUERY_ARGS:
317 268 return \is_array( $value ) ? $this->apply_meta_filters( $value ) : $value;
318 269
319 270 case self::HOOK_PREPARE_ARGS:
@@ -352,9 +303,9 @@
352 303
353 304 /**
354 305 * Install this plan's callbacks, run `$run`, then unwind every binding in reverse.
355 306 *
356 - * The scoped read lanes' ONLY install path. Bindings are closures captured here, so the
307 + * The proxy lane's ONLY install path. Bindings are closures captured here, so the
357 308 * unwind removes the exact callables that were added — never a re-derived tuple that
358 309 * could miss. An exception from `$run` propagates AFTER the unwind.
359 310 *
360 311 * @param callable $run The forward to wrap.
@@ -431,49 +382,8 @@
431 382 if ( $this->is_empty() ) {
432 383 return $bindings;
433 384 }
434 385
435 - if ( null !== $this->visibility_type ) {
436 - $hooks = array(
437 - self::HOOK_POSTS_WHERE => 10,
438 - self::HOOK_POSTS_CLAUSES => 10,
439 - );
440 - if ( '' !== $this->search ) {
441 - $hooks += array(
442 - 'posts_search' => 10,
443 - 'posts_join' => 10,
444 - 'posts_groupby' => 10,
445 - 'search_orderby' => 20,
446 - );
447 - }
448 - foreach ( $hooks as $role => $priority ) {
449 - $hook = 'search_orderby' === $role ? 'posts_clauses' : $role;
450 - $callback = function ( $value, $query ) use ( $role ) {
451 - $type = 'variations' === $this->collection ? 'product_variation' : 'product';
452 - return in_array( $type, (array) ( $query->query_vars['post_type'] ?? null ), true )
453 - ? $this->filter( $role, $value, $query ) : $value;
454 - };
455 - add_filter( $hook, $callback, $priority, 2 );
456 - $bindings[] = array( $hook, $callback, $priority );
457 - }
458 - if ( 'products' === $this->collection ) {
459 - $callback = function ( $args ) {
460 - return $this->filter( self::HOOK_PREPARE_ARGS, $args );
461 - };
462 - add_filter( 'woocommerce_rest_product_object_query', $callback );
463 - $bindings[] = array( 'woocommerce_rest_product_object_query', $callback, 10 );
464 - }
465 - if ( '' !== $this->search ) {
466 - $vars = static function ( $vars ) {
467 - $vars[] = 'wcpos_search_phrase';
468 - return $vars;
469 - };
470 - add_filter( 'woocommerce_rest_query_vars', $vars );
471 - $bindings[] = array( 'woocommerce_rest_query_vars', $vars, 10 );
472 - }
473 - return $bindings;
474 - }
475 -
476 386 // `meta_query` rows are storage-neutral (`wc_get_orders()` honours them on both),
477 387 // and the legacy sort args are a no-op under HPOS, so one binding covers both.
478 388 if ( array() !== $this->claimed_meta_filters() || $this->has_legacy_meta_sort() ) {
479 389 $args_callback = function ( $args ) {
@@ -507,9 +417,9 @@
507 417 add_filter( 'posts_orderby', $orderby_callback, 10, 2 );
508 418 $bindings[] = array( 'posts_orderby', $orderby_callback, 10 );
509 419 }
510 420
511 - if ( $this->claims_id_sets() || '' !== $this->search ) {
421 + if ( $this->claims_id_sets() ) {
512 422 /*
513 423 * `posts_where` fires for EVERY WP_Query, and `wcpos/v1` leaves its callback
514 424 * installed for the remainder of the request without a post-type guard (frozen
515 425 * behaviour, reproduced verbatim in the clause body). The proxy lane scopes the
@@ -531,80 +441,8 @@
531 441 $bindings[] = array( 'posts_where', $where_callback, 10 );
532 442 }
533 443
534 444 return $bindings;
535 - }
536 -
537 - /**
538 - * Apply the declared search and visibility bodies without installing hooks.
539 - *
540 - * @param string $hook Clause role.
541 - * @param mixed $value Value to filter.
542 - * @param mixed $query Query instance.
543 - * @return mixed
544 - */
545 - private function apply_read_rule( string $hook, $value, $query ) {
546 - global $wpdb;
547 - $rule = $this->rules['search'] ?? array();
548 - $q = $query->query_vars ?? array();
549 - if ( self::HOOK_PREPARE_ARGS === $hook && \is_array( $value ) && null !== $this->visibility_type ) {
550 - $value = ( new Pos_Visibility() )->apply_to_wp_query_args( $value, $this->collection, null, $this->visibility_type );
551 - if ( 'variations' === $this->collection && 'meta_query' === $rule['query'] ) {
552 - return Product_Search::variation_args( $value, $this->search, $this->sku, $rule );
553 - }
554 - if ( '' !== $this->search ) {
555 - $value['s'] = $this->search;
556 - // Every product/variation lane uses the same literal splitter.
557 - $value['wcpos_search_phrase'] = $this->search;
558 - }
559 - }
560 - if ( self::HOOK_POSTS_WHERE === $hook && ! empty( $this->rules['visibility']['where_backstop'] ) ) {
561 - $value = ( new Pos_Visibility() )->apply_to_sql_where( $value, "{$wpdb->posts}.ID", $this->visibility_type );
562 - }
563 - if ( '' === $this->search ) {
564 - return $value;
565 - }
566 - if ( 'orders' === $this->collection ) {
567 - if ( self::HOOK_HPOS_FILTERS === $hook ) {
568 - $value['where'] .= ' AND ' . Order_Search::hpos_where(
569 - $this->search,
570 - array(
571 - 'orders' => $query->get_table_name( 'orders' ),
572 - 'addresses' => $query->get_table_name( 'addresses' ),
573 - ),
574 - $rule
575 - );
576 - } elseif ( self::HOOK_POSTS_WHERE === $hook ) {
577 - $value .= ' AND ' . Order_Search::posts_where( $this->search, $rule );
578 - }
579 - } elseif ( 'variations' === $this->collection && 'wp_terms' === $rule['query'] ) {
580 - switch ( $hook ) {
581 - case 'posts_search':
582 - return Product_Search::variation_posts_search( $value, $q, $rule );
583 - case 'posts_join':
584 - return empty( $q['s'] ) ? $value : Product_Search::posts_join( $value, $q );
585 - case 'posts_groupby':
586 - return empty( $q['s'] ) ? $value : Product_Search::posts_groupby( $value, $q );
587 - }
588 - } elseif ( 'variations' === $this->collection ) {
589 - if ( 'posts_groupby' === $hook ) {
590 - $value = Product_Search::variation_groupby( $value, $q );
591 - }
592 - } else {
593 - switch ( $hook ) {
594 - case 'posts_search':
595 - return Product_Search::posts_search( $value, $q, $rule );
596 - case 'posts_join':
597 - return Product_Search::posts_join( $value, $q );
598 - case 'posts_groupby':
599 - return Product_Search::posts_groupby( $value, $q );
600 - case 'search_orderby':
601 - if ( $rule['rank_exact'] ) {
602 - $value['orderby'] = Product_Search::posts_orderby( (string) ( $value['orderby'] ?? '' ), $q, $rule );
603 - }
604 - }
605 - }
606 - return $value;
607 445 }
608 446
609 447 /**
610 448 * Claim the `orderby` param when its value names a sort this collection declares.