← All changes
|
includes/API/V1/Product_Variations_Controller.php
+65
-38
1.10.3
→
1.10.17
View file →
| @@ -17,8 +17,10 @@ | ||
| 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; |
| 22 | 24 | use WP_Error; |
| 23 | 25 | use WP_Query; |
| 24 | 26 | use WP_REST_Request; |
| @@ -45,13 +47,27 @@ | ||
| 45 | 47 | |
| 46 | 48 | /** |
| 47 | 49 | * Store the request object for use in lifecycle methods. |
| 48 | 50 | * |
| 49 | - * @var WP_REST_Request | |
| 51 | + * Null until `wcpos_dispatch_request()` runs: the instance exists, and its filters are | |
| 52 | + * registered, before any request is assigned — which is why the readers guard with | |
| 53 | + * `isset()`. Matches the same property on `API\V1\Products_Controller`. | |
| 54 | + * | |
| 55 | + * @var null|WP_REST_Request | |
| 50 | 56 | */ |
| 51 | 57 | protected $wcpos_request; |
| 52 | 58 | |
| 53 | 59 | /** |
| 60 | + * Request keys the variation Collection Rules plan reads on this lane. | |
| 61 | + * | |
| 62 | + * @var array | |
| 63 | + */ | |
| 64 | + private const WCPOS_SORT_PARAM_MAP = array( | |
| 65 | + 'orderby' => 'orderby', | |
| 66 | + 'order' => 'order', | |
| 67 | + ); | |
| 68 | + | |
| 69 | + /** | |
| 54 | 70 | * Dispatch request to parent controller, or override if needed. |
| 55 | 71 | * |
| 56 | 72 | * @param mixed $dispatch_result Dispatch result, will be used if not empty. |
| 57 | 73 | * @param WP_REST_Request $request Request used to generate the response. |
| @@ -64,8 +80,9 @@ | ||
| 64 | 80 | add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 ); |
| 65 | 81 | add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 ); |
| 66 | 82 | add_filter( 'woocommerce_rest_product_variation_object_query', array( $this, 'wcpos_product_variation_query' ), 10, 2 ); |
| 67 | 83 | add_filter( 'posts_search', array( $this, 'wcpos_posts_search' ), 10, 2 ); |
| 84 | + add_filter( 'posts_clauses', array( $this, 'wcpos_posts_clauses' ), 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. |
| @@ -170,16 +187,11 @@ | ||
| 170 | 187 | } |
| 171 | 188 | |
| 172 | 189 | // Ensure 'orderby' is set and is an array before attempting to modify it. |
| 173 | 190 | 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. | |
| 191 | + // DECLARED once, in Sync\Collection_Rules, and projected here — so a sort cannot | |
| 192 | + // be advertised on one lane and rejected on the other. | |
| 193 | + $new_sort_options = Collection_Rules::orderby_enum( 'variations' ); | |
| 182 | 194 | $params['orderby']['enum'] = array_unique( array_merge( $params['orderby']['enum'], $new_sort_options ) ); |
| 183 | 195 | } |
| 184 | 196 | |
| 185 | 197 | return $params; |
| @@ -276,8 +288,9 @@ | ||
| 276 | 288 | |
| 277 | 289 | // Meta fields to search. |
| 278 | 290 | $meta_fields = Barcode_Field::search_keys(); |
| 279 | 291 | |
| 292 | + $meta_placeholders = implode( ', ', array_fill( 0, \count( $meta_fields ), '%s' ) ); | |
| 280 | 293 | $search_conditions = array(); |
| 281 | 294 | |
| 282 | 295 | foreach ( $search_terms as $term ) { |
| 283 | 296 | $term = $n . $wpdb->esc_like( $term ) . $n; |
| @@ -282,15 +295,20 @@ | ||
| 282 | 295 | foreach ( $search_terms as $term ) { |
| 283 | 296 | $term = $n . $wpdb->esc_like( $term ) . $n; |
| 284 | 297 | |
| 285 | 298 | // 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 | - } | |
| 299 | + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table names come from $wpdb; $meta_placeholders is a generated list of %s placeholders, and the keys themselves are passed to prepare() as arguments. | |
| 300 | + $search_conditions[] = $wpdb->prepare( | |
| 301 | + "EXISTS ( | |
| 302 | + SELECT 1 FROM {$wpdb->postmeta} AS wcpos_search_meta WHERE wcpos_search_meta.post_id = {$wpdb->posts}.ID AND wcpos_search_meta.meta_key IN ($meta_placeholders) AND wcpos_search_meta.meta_value LIKE %s | |
| 303 | + )", | |
| 304 | + array_merge( $meta_fields, array( $term ) ) | |
| 305 | + ); | |
| 306 | + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 289 | 307 | } |
| 290 | 308 | |
| 291 | 309 | if ( ! empty( $search_conditions ) ) { |
| 292 | - $search = ' AND (' . implode( ' OR ', $search_conditions ) . ') '; | |
| 310 | + $search = ' AND (' . implode( ' AND ', $search_conditions ) . ') '; | |
| 293 | 311 | if ( ! is_user_logged_in() ) { |
| 294 | 312 | $search .= " AND ($wpdb->posts.post_password = '') "; |
| 295 | 313 | } |
| 296 | 314 | } |
| @@ -460,8 +478,35 @@ | ||
| 460 | 478 | } |
| 461 | 479 | |
| 462 | 480 | |
| 463 | 481 | /** |
| 482 | + * Apply the declared POS variation sorts to the SQL clauses. | |
| 483 | + * | |
| 484 | + * `posts_clauses` fires for EVERY WP_Query, so the body is guarded by post type and by | |
| 485 | + * the plan itself — it contributes nothing unless this request claimed one of the | |
| 486 | + * declared sorts. | |
| 487 | + * | |
| 488 | + * @param array $clauses Associative array of the clauses for the query. | |
| 489 | + * @param WP_Query $wp_query The WP_Query instance. | |
| 490 | + * | |
| 491 | + * @return array | |
| 492 | + */ | |
| 493 | + public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array { | |
| 494 | + if ( ! isset( $this->wcpos_request ) ) { | |
| 495 | + return $clauses; | |
| 496 | + } | |
| 497 | + | |
| 498 | + $post_type = $wp_query->query_vars['post_type'] ?? null; | |
| 499 | + if ( 'product_variation' !== $post_type && ( ! \is_array( $post_type ) || ! \in_array( 'product_variation', $post_type, true ) ) ) { | |
| 500 | + return $clauses; | |
| 501 | + } | |
| 502 | + | |
| 503 | + $plan = Collection_Rules::for_request( 'variations', $this->wcpos_request, self::WCPOS_SORT_PARAM_MAP ); | |
| 504 | + | |
| 505 | + return $plan->filter( Collection_Rules_Plan::HOOK_POSTS_CLAUSES, $clauses, $wp_query ); | |
| 506 | + } | |
| 507 | + | |
| 508 | + /** | |
| 464 | 509 | * Prepare objects query. |
| 465 | 510 | * |
| 466 | 511 | * @param WP_REST_Request $request Full details about the request. |
| 467 | 512 | * |
| @@ -469,33 +514,15 @@ | ||
| 469 | 514 | */ |
| 470 | 515 | protected function prepare_objects_query( $request ) { |
| 471 | 516 | $args = parent::prepare_objects_query( $request ); |
| 472 | 517 | |
| 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'; | |
| 479 | - | |
| 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 | - } | |
| 518 | + /* | |
| 519 | + * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT mapped | |
| 520 | + * onto `meta_key` + `orderby => meta_value` here any more. That pair INNER JOINs | |
| 521 | + * postmeta, so it dropped every variation with no value for the key — a sort acting | |
| 522 | + * as a filter. `Sync\Collection_Rules` declares them and `wcpos_posts_clauses()` | |
| 523 | + * applies them as a LEFT JOIN, on this lane and on `wcpos/v2` alike. | |
| 524 | + */ | |
| 498 | 525 | |
| 499 | 526 | return $args; |
| 500 | 527 | } |
| 501 | 528 | } |