← All changes
|
includes/API/V1/Product_Variations_Controller.php
+36
-66
1.10.6
→
1.10.18
View file →
| @@ -20,8 +20,9 @@ | ||
| 20 | 20 | use WCPOS\WooCommercePOS\Services\Barcode_Field; |
| 21 | 21 | use WCPOS\WooCommercePOS\Sync\Collection_Rules; |
| 22 | 22 | use WCPOS\WooCommercePOS\Sync\Collection_Rules_Plan; |
| 23 | 23 | use WCPOS\WooCommercePOS\Sync\Pos_Visibility; |
| 24 | +use WCPOS\WooCommercePOS\Sync\Product_Search; | |
| 24 | 25 | use WP_Error; |
| 25 | 26 | use WP_Query; |
| 26 | 27 | use WP_REST_Request; |
| 27 | 28 | use WP_REST_Response; |
| @@ -63,8 +64,9 @@ | ||
| 63 | 64 | */ |
| 64 | 65 | private const WCPOS_SORT_PARAM_MAP = array( |
| 65 | 66 | 'orderby' => 'orderby', |
| 66 | 67 | 'order' => 'order', |
| 68 | + 'search' => 'search', | |
| 67 | 69 | ); |
| 68 | 70 | |
| 69 | 71 | /** |
| 70 | 72 | * Dispatch request to parent controller, or override if needed. |
| @@ -79,10 +81,8 @@ | ||
| 79 | 81 | |
| 80 | 82 | add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 ); |
| 81 | 83 | add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 ); |
| 82 | 84 | add_filter( 'woocommerce_rest_product_variation_object_query', array( $this, 'wcpos_product_variation_query' ), 10, 2 ); |
| 83 | - add_filter( 'posts_search', array( $this, 'wcpos_posts_search' ), 10, 2 ); | |
| 84 | - add_filter( 'posts_clauses', array( $this, 'wcpos_posts_clauses' ), 10, 2 ); | |
| 85 | 85 | |
| 86 | 86 | /* |
| 87 | 87 | * Check if the request is for all products and if the 'posts_per_page' is set to -1. |
| 88 | 88 | * Optimised query for getting all product IDs. |
| @@ -94,8 +94,23 @@ | ||
| 94 | 94 | return $dispatch_result; |
| 95 | 95 | } |
| 96 | 96 | |
| 97 | 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 | + /** | |
| 98 | 113 | * Register routes. |
| 99 | 114 | */ |
| 100 | 115 | public function register_routes(): void { |
| 101 | 116 | parent::register_routes(); |
| @@ -185,8 +200,14 @@ | ||
| 185 | 200 | if ( isset( $params['per_page'] ) && \is_array( $params['per_page'] ) ) { |
| 186 | 201 | $params['per_page']['minimum'] = -1; |
| 187 | 202 | } |
| 188 | 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 | + | |
| 189 | 210 | // Ensure 'orderby' is set and is an array before attempting to modify it. |
| 190 | 211 | if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) { |
| 191 | 212 | // DECLARED once, in Sync\Collection_Rules, and projected here — so a sort cannot |
| 192 | 213 | // be advertised on one lane and rejected on the other. |
| @@ -270,45 +291,12 @@ | ||
| 270 | 291 | * @param string $search Search string. |
| 271 | 292 | * @param WP_Query $wp_query WP_Query object. |
| 272 | 293 | * |
| 273 | 294 | * @return string |
| 295 | + * @deprecated Collection Rules now installs this behavior. | |
| 274 | 296 | */ |
| 275 | 297 | public function wcpos_posts_search( string $search, WP_Query $wp_query ) { |
| 276 | - global $wpdb; | |
| 277 | - | |
| 278 | - if ( empty( $search ) ) { | |
| 279 | - return $search; // skip processing - no search term in query. | |
| 280 | - } | |
| 281 | - | |
| 282 | - $q = $wp_query->query_vars; | |
| 283 | - $n = ! empty( $q['exact'] ) ? '' : '%'; | |
| 284 | - $search_terms = (array) $q['search_terms']; | |
| 285 | - | |
| 286 | - // Fields in the main 'posts' table. | |
| 287 | - $post_fields = array(); // nothing at the moment for variations. | |
| 288 | - | |
| 289 | - // Meta fields to search. | |
| 290 | - $meta_fields = Barcode_Field::search_keys(); | |
| 291 | - | |
| 292 | - $search_conditions = array(); | |
| 293 | - | |
| 294 | - foreach ( $search_terms as $term ) { | |
| 295 | - $term = $n . $wpdb->esc_like( $term ) . $n; | |
| 296 | - | |
| 297 | - // Search in meta fields. | |
| 298 | - foreach ( $meta_fields as $field ) { | |
| 299 | - $search_conditions[] = $wpdb->prepare( '(pm1.meta_value LIKE %s AND pm1.meta_key = %s)', $term, $field ); | |
| 300 | - } | |
| 301 | - } | |
| 302 | - | |
| 303 | - if ( ! empty( $search_conditions ) ) { | |
| 304 | - $search = ' AND (' . implode( ' OR ', $search_conditions ) . ') '; | |
| 305 | - if ( ! is_user_logged_in() ) { | |
| 306 | - $search .= " AND ($wpdb->posts.post_password = '') "; | |
| 307 | - } | |
| 308 | - } | |
| 309 | - | |
| 310 | - return $search; | |
| 298 | + return Product_Search::variation_posts_search( $search, $wp_query->query_vars, Collection_Rules::rules( 'variations' )['search'] ); | |
| 311 | 299 | } |
| 312 | 300 | |
| 313 | 301 | /** |
| 314 | 302 | * Filters the JOIN clause of the query. |
| @@ -316,17 +304,12 @@ | ||
| 316 | 304 | * @param string $join The JOIN clause of the query. |
| 317 | 305 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 318 | 306 | * |
| 319 | 307 | * @return string |
| 308 | + * @deprecated Collection Rules now installs this behavior. | |
| 320 | 309 | */ |
| 321 | 310 | public function wcpos_posts_join_to_posts_search( string $join, WP_Query $query ) { |
| 322 | - global $wpdb; | |
| 323 | - | |
| 324 | - if ( ! empty( $query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) { | |
| 325 | - $join .= " LEFT JOIN {$wpdb->postmeta} pm1 ON {$wpdb->posts}.ID = pm1.post_id "; | |
| 326 | - } | |
| 327 | - | |
| 328 | - return $join; | |
| 311 | + return empty( $query->query_vars['s'] ) ? $join : Product_Search::posts_join( $join, $query->query_vars ); | |
| 329 | 312 | } |
| 330 | 313 | |
| 331 | 314 | /** |
| 332 | 315 | * Filters the GROUP BY clause of the query. |
| @@ -334,17 +317,12 @@ | ||
| 334 | 317 | * @param string $groupby The GROUP BY clause of the query. |
| 335 | 318 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 336 | 319 | * |
| 337 | 320 | * @return string |
| 321 | + * @deprecated Collection Rules now installs this behavior. | |
| 338 | 322 | */ |
| 339 | 323 | public function wcpos_posts_groupby_posts_search( string $groupby, WP_Query $query ) { |
| 340 | - global $wpdb; | |
| 341 | - | |
| 342 | - if ( ! empty( $query->query_vars['s'] ) ) { | |
| 343 | - $groupby = "{$wpdb->posts}.ID"; | |
| 344 | - } | |
| 345 | - | |
| 346 | - return $groupby; | |
| 324 | + return empty( $query->query_vars['s'] ) ? $groupby : Product_Search::posts_groupby( $groupby, $query->query_vars ); | |
| 347 | 325 | } |
| 348 | 326 | |
| 349 | 327 | /** |
| 350 | 328 | * Filter the query arguments for a request. |
| @@ -354,21 +332,10 @@ | ||
| 354 | 332 | * |
| 355 | 333 | * @return array $args Key value array of query var to query value. |
| 356 | 334 | */ |
| 357 | 335 | public function wcpos_product_variation_query( array $args, WP_REST_Request $request ) { |
| 358 | - if ( ! empty( $request['search'] ) ) { | |
| 359 | - // We need to set the query up for a postmeta join. | |
| 360 | - add_filter( 'posts_join', array( $this, 'wcpos_posts_join_to_posts_search' ), 10, 2 ); | |
| 361 | - add_filter( 'posts_groupby', array( $this, 'wcpos_posts_groupby_posts_search' ), 10, 2 ); | |
| 362 | - } | |
| 363 | - | |
| 364 | - // if POS only products are enabled, exclude online-only products. | |
| 365 | - if ( $this->wcpos_pos_only_products_enabled() ) { | |
| 366 | - add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_exclude_online_only' ), 10, 2 ); | |
| 367 | - } | |
| 368 | - | |
| 369 | 336 | // Check for wcpos_include/wcpos_exclude parameter. |
| 370 | - // NOTE: do this after POS visibility filter so that takes precedence. | |
| 337 | + // The Collection Rules visibility backstop runs first, at priority 10. | |
| 371 | 338 | if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { |
| 372 | 339 | add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_include_exclude' ), 20, 2 ); |
| 373 | 340 | } |
| 374 | 341 | |
| @@ -384,8 +351,9 @@ | ||
| 384 | 351 | * @param string $where The WHERE clause of the query. |
| 385 | 352 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 386 | 353 | * |
| 387 | 354 | * @return string |
| 355 | + * @deprecated Collection Rules now installs this behavior. | |
| 388 | 356 | */ |
| 389 | 357 | public function wcpos_posts_where_product_variation_exclude_online_only( string $where, WP_Query $query ) { |
| 390 | 358 | global $wpdb; |
| 391 | 359 | |
| @@ -467,9 +435,9 @@ | ||
| 467 | 435 | * |
| 468 | 436 | * @param WP_REST_Request $request Full details about the request. |
| 469 | 437 | */ |
| 470 | 438 | public function wcpos_get_all_items( $request ) { |
| 471 | - return parent::get_items( $request ); | |
| 439 | + return $this->get_items( $request ); | |
| 472 | 440 | } |
| 473 | 441 | |
| 474 | 442 | |
| 475 | 443 | /** |
| @@ -482,8 +450,9 @@ | ||
| 482 | 450 | * @param array $clauses Associative array of the clauses for the query. |
| 483 | 451 | * @param WP_Query $wp_query The WP_Query instance. |
| 484 | 452 | * |
| 485 | 453 | * @return array |
| 454 | + * @deprecated Collection Rules now installs this behavior. | |
| 486 | 455 | */ |
| 487 | 456 | public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array { |
| 488 | 457 | if ( ! isset( $this->wcpos_request ) ) { |
| 489 | 458 | return $clauses; |
| @@ -512,11 +481,12 @@ | ||
| 512 | 481 | /* |
| 513 | 482 | * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT mapped |
| 514 | 483 | * onto `meta_key` + `orderby => meta_value` here any more. That pair INNER JOINs |
| 515 | 484 | * postmeta, so it dropped every variation with no value for the key — a sort acting |
| 516 | - * as a filter. `Sync\Collection_Rules` declares them and `wcpos_posts_clauses()` | |
| 485 | + * as a filter. `Sync\Collection_Rules` declares them and its scoped plan | |
| 517 | 486 | * applies them as a LEFT JOIN, on this lane and on `wcpos/v2` alike. |
| 518 | 487 | */ |
| 519 | 488 | |
| 520 | - 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 ); | |
| 521 | 491 | } |
| 522 | 492 | } |