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/V1/Product_Variations_Controller.php +89 -98 1.10.21.10.18 View file →
@@ -17,9 +17,12 @@
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;
24 +use WCPOS\WooCommercePOS\Sync\Product_Search;
22 25 use WP_Error;
23 26 use WP_Query;
24 27 use WP_REST_Request;
25 28 use WP_REST_Response;
@@ -45,13 +48,28 @@
45 48
46 49 /**
47 50 * Store the request object for use in lifecycle methods.
48 51 *
49 - * @var WP_REST_Request
52 + * Null until `wcpos_dispatch_request()` runs: the instance exists, and its filters are
53 + * registered, before any request is assigned — which is why the readers guard with
54 + * `isset()`. Matches the same property on `API\V1\Products_Controller`.
55 + *
56 + * @var null|WP_REST_Request
50 57 */
51 58 protected $wcpos_request;
52 59
53 60 /**
61 + * Request keys the variation Collection Rules plan reads on this lane.
62 + *
63 + * @var array
64 + */
65 + private const WCPOS_SORT_PARAM_MAP = array(
66 + 'orderby' => 'orderby',
67 + 'order' => 'order',
68 + 'search' => 'search',
69 + );
70 +
71 + /**
54 72 * Dispatch request to parent controller, or override if needed.
55 73 *
56 74 * @param mixed $dispatch_result Dispatch result, will be used if not empty.
57 75 * @param WP_REST_Request $request Request used to generate the response.
@@ -63,9 +81,8 @@
63 81
64 82 add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 );
65 83 add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 );
66 84 add_filter( 'woocommerce_rest_product_variation_object_query', array( $this, 'wcpos_product_variation_query' ), 10, 2 );
67 - add_filter( 'posts_search', array( $this, 'wcpos_posts_search' ), 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.
@@ -77,8 +94,23 @@
77 94 return $dispatch_result;
78 95 }
79 96
80 97 /**
98 + * Apply the collection's declared rules for nested and flat direct reads.
99 + *
100 + * @param WP_REST_Request $request Full details about the request.
101 + * @return \WP_Error|\WP_REST_Response
102 + */
103 + public function get_items( $request ) {
104 + $plan = Collection_Rules::for_request( 'variations', $request, self::WCPOS_SORT_PARAM_MAP );
105 + return $plan->around(
106 + function () use ( $request ) {
107 + return parent::get_items( $request );
108 + }
109 + );
110 + }
111 +
112 + /**
81 113 * Register routes.
82 114 */
83 115 public function register_routes(): void {
84 116 parent::register_routes();
@@ -168,18 +200,19 @@
168 200 if ( isset( $params['per_page'] ) && \is_array( $params['per_page'] ) ) {
169 201 $params['per_page']['minimum'] = -1;
170 202 }
171 203
204 + // Search text is literal on every lane: `sanitize_text_field` would strip `%30`
205 + // and blank malformed UTF-8 before the declared search rule ever saw them.
206 + if ( isset( $params['search'] ) && \is_array( $params['search'] ) ) {
207 + $params['search']['sanitize_callback'] = 'rest_sanitize_request_arg';
208 + }
209 +
172 210 // Ensure 'orderby' is set and is an array before attempting to modify it.
173 211 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.
212 + // DECLARED once, in Sync\Collection_Rules, and projected here — so a sort cannot
213 + // be advertised on one lane and rejected on the other.
214 + $new_sort_options = Collection_Rules::orderby_enum( 'variations' );
182 215 $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], $new_sort_options ) );
183 216 }
184 217
185 218 return $params;
@@ -258,45 +291,12 @@
258 291 * @param string $search Search string.
259 292 * @param WP_Query $wp_query WP_Query object.
260 293 *
261 294 * @return string
295 + * @deprecated Collection Rules now installs this behavior.
262 296 */
263 297 public function wcpos_posts_search( string $search, WP_Query $wp_query ) {
264 - global $wpdb;
265 -
266 - if ( empty( $search ) ) {
267 - return $search; // skip processing - no search term in query.
268 - }
269 -
270 - $q = $wp_query->query_vars;
271 - $n = ! empty( $q['exact'] ) ? '' : '%';
272 - $search_terms = (array) $q['search_terms'];
273 -
274 - // Fields in the main 'posts' table.
275 - $post_fields = array(); // nothing at the moment for variations.
276 -
277 - // Meta fields to search.
278 - $meta_fields = Barcode_Field::search_keys();
279 -
280 - $search_conditions = array();
281 -
282 - foreach ( $search_terms as $term ) {
283 - $term = $n . $wpdb->esc_like( $term ) . $n;
284 -
285 - // 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 - }
289 - }
290 -
291 - if ( ! empty( $search_conditions ) ) {
292 - $search = ' AND (' . implode( ' OR ', $search_conditions ) . ') ';
293 - if ( ! is_user_logged_in() ) {
294 - $search .= " AND ($wpdb->posts.post_password = '') ";
295 - }
296 - }
297 -
298 - return $search;
298 + return Product_Search::variation_posts_search( $search, $wp_query->query_vars, Collection_Rules::rules( 'variations' )['search'] );
299 299 }
300 300
301 301 /**
302 302 * Filters the JOIN clause of the query.
@@ -304,17 +304,12 @@
304 304 * @param string $join The JOIN clause of the query.
305 305 * @param WP_Query $query The WP_Query instance (passed by reference).
306 306 *
307 307 * @return string
308 + * @deprecated Collection Rules now installs this behavior.
308 309 */
309 310 public function wcpos_posts_join_to_posts_search( string $join, WP_Query $query ) {
310 - global $wpdb;
311 -
312 - if ( ! empty( $query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) {
313 - $join .= " LEFT JOIN {$wpdb->postmeta} pm1 ON {$wpdb->posts}.ID = pm1.post_id ";
314 - }
315 -
316 - return $join;
311 + return empty( $query->query_vars['s'] ) ? $join : Product_Search::posts_join( $join, $query->query_vars );
317 312 }
318 313
319 314 /**
320 315 * Filters the GROUP BY clause of the query.
@@ -322,17 +317,12 @@
322 317 * @param string $groupby The GROUP BY clause of the query.
323 318 * @param WP_Query $query The WP_Query instance (passed by reference).
324 319 *
325 320 * @return string
321 + * @deprecated Collection Rules now installs this behavior.
326 322 */
327 323 public function wcpos_posts_groupby_posts_search( string $groupby, WP_Query $query ) {
328 - global $wpdb;
329 -
330 - if ( ! empty( $query->query_vars['s'] ) ) {
331 - $groupby = "{$wpdb->posts}.ID";
332 - }
333 -
334 - return $groupby;
324 + return empty( $query->query_vars['s'] ) ? $groupby : Product_Search::posts_groupby( $groupby, $query->query_vars );
335 325 }
336 326
337 327 /**
338 328 * Filter the query arguments for a request.
@@ -342,21 +332,10 @@
342 332 *
343 333 * @return array $args Key value array of query var to query value.
344 334 */
345 335 public function wcpos_product_variation_query( array $args, WP_REST_Request $request ) {
346 - if ( ! empty( $request['search'] ) ) {
347 - // We need to set the query up for a postmeta join.
348 - add_filter( 'posts_join', array( $this, 'wcpos_posts_join_to_posts_search' ), 10, 2 );
349 - add_filter( 'posts_groupby', array( $this, 'wcpos_posts_groupby_posts_search' ), 10, 2 );
350 - }
351 -
352 - // if POS only products are enabled, exclude online-only products.
353 - if ( $this->wcpos_pos_only_products_enabled() ) {
354 - add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_exclude_online_only' ), 10, 2 );
355 - }
356 -
357 336 // Check for wcpos_include/wcpos_exclude parameter.
358 - // NOTE: do this after POS visibility filter so that takes precedence.
337 + // The Collection Rules visibility backstop runs first, at priority 10.
359 338 if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) {
360 339 add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_include_exclude' ), 20, 2 );
361 340 }
362 341
@@ -372,8 +351,9 @@
372 351 * @param string $where The WHERE clause of the query.
373 352 * @param WP_Query $query The WP_Query instance (passed by reference).
374 353 *
375 354 * @return string
355 + * @deprecated Collection Rules now installs this behavior.
376 356 */
377 357 public function wcpos_posts_where_product_variation_exclude_online_only( string $where, WP_Query $query ) {
378 358 global $wpdb;
379 359
@@ -455,13 +435,41 @@
455 435 *
456 436 * @param WP_REST_Request $request Full details about the request.
457 437 */
458 438 public function wcpos_get_all_items( $request ) {
459 - return parent::get_items( $request );
439 + return $this->get_items( $request );
460 440 }
461 441
462 442
463 443 /**
444 + * Apply the declared POS variation sorts to the SQL clauses.
445 + *
446 + * `posts_clauses` fires for EVERY WP_Query, so the body is guarded by post type and by
447 + * the plan itself — it contributes nothing unless this request claimed one of the
448 + * declared sorts.
449 + *
450 + * @param array $clauses Associative array of the clauses for the query.
451 + * @param WP_Query $wp_query The WP_Query instance.
452 + *
453 + * @return array
454 + * @deprecated Collection Rules now installs this behavior.
455 + */
456 + public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array {
457 + if ( ! isset( $this->wcpos_request ) ) {
458 + return $clauses;
459 + }
460 +
461 + $post_type = $wp_query->query_vars['post_type'] ?? null;
462 + if ( 'product_variation' !== $post_type && ( ! \is_array( $post_type ) || ! \in_array( 'product_variation', $post_type, true ) ) ) {
463 + return $clauses;
464 + }
465 +
466 + $plan = Collection_Rules::for_request( 'variations', $this->wcpos_request, self::WCPOS_SORT_PARAM_MAP );
467 +
468 + return $plan->filter( Collection_Rules_Plan::HOOK_POSTS_CLAUSES, $clauses, $wp_query );
469 + }
470 +
471 + /**
464 472 * Prepare objects query.
465 473 *
466 474 * @param WP_REST_Request $request Full details about the request.
467 475 *
@@ -469,33 +477,16 @@
469 477 */
470 478 protected function prepare_objects_query( $request ) {
471 479 $args = parent::prepare_objects_query( $request );
472 480
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';
481 + /*
482 + * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT mapped
483 + * onto `meta_key` + `orderby => meta_value` here any more. That pair INNER JOINs
484 + * postmeta, so it dropped every variation with no value for the key — a sort acting
485 + * as a filter. `Sync\Collection_Rules` declares them and its scoped plan
486 + * applies them as a LEFT JOIN, on this lane and on `wcpos/v2` alike.
487 + */
479 488
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 - }
498 -
499 - return $args;
489 + $plan = Collection_Rules::for_request( 'variations', $request, self::WCPOS_SORT_PARAM_MAP );
490 + return $plan->filter( Collection_Rules_Plan::HOOK_PREPARE_ARGS, $args );
500 491 }
501 492 }