← All changes
|
includes/API/V1/Product_Variations_Controller.php
+72
-36
1.10.20
→
1.10.17
View file →
| @@ -20,9 +20,8 @@ | ||
| 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; | |
| 25 | 24 | use WP_Error; |
| 26 | 25 | use WP_Query; |
| 27 | 26 | use WP_REST_Request; |
| 28 | 27 | use WP_REST_Response; |
| @@ -64,9 +63,8 @@ | ||
| 64 | 63 | */ |
| 65 | 64 | private const WCPOS_SORT_PARAM_MAP = array( |
| 66 | 65 | 'orderby' => 'orderby', |
| 67 | 66 | 'order' => 'order', |
| 68 | - 'search' => 'search', | |
| 69 | 67 | ); |
| 70 | 68 | |
| 71 | 69 | /** |
| 72 | 70 | * Dispatch request to parent controller, or override if needed. |
| @@ -81,8 +79,10 @@ | ||
| 81 | 79 | |
| 82 | 80 | add_filter( 'woocommerce_rest_prepare_product_variation_object', array( $this, 'wcpos_variation_response' ), 10, 3 ); |
| 83 | 81 | add_action( 'woocommerce_rest_insert_product_variation_object', array( $this, 'wcpos_insert_product_variation_object' ), 10, 3 ); |
| 84 | 82 | 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,23 +94,8 @@ | ||
| 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 | - /** | |
| 113 | 98 | * Register routes. |
| 114 | 99 | */ |
| 115 | 100 | public function register_routes(): void { |
| 116 | 101 | parent::register_routes(); |
| @@ -200,14 +185,8 @@ | ||
| 200 | 185 | if ( isset( $params['per_page'] ) && \is_array( $params['per_page'] ) ) { |
| 201 | 186 | $params['per_page']['minimum'] = -1; |
| 202 | 187 | } |
| 203 | 188 | |
| 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 | - | |
| 210 | 189 | // Ensure 'orderby' is set and is an array before attempting to modify it. |
| 211 | 190 | if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) { |
| 212 | 191 | // DECLARED once, in Sync\Collection_Rules, and projected here — so a sort cannot |
| 213 | 192 | // be advertised on one lane and rejected on the other. |
| @@ -291,12 +270,51 @@ | ||
| 291 | 270 | * @param string $search Search string. |
| 292 | 271 | * @param WP_Query $wp_query WP_Query object. |
| 293 | 272 | * |
| 294 | 273 | * @return string |
| 295 | - * @deprecated Collection Rules now installs this behavior. | |
| 296 | 274 | */ |
| 297 | 275 | public function wcpos_posts_search( string $search, WP_Query $wp_query ) { |
| 298 | - return Product_Search::variation_posts_search( $search, $wp_query->query_vars, Collection_Rules::rules( 'variations' )['search'] ); | |
| 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 | + $meta_placeholders = implode( ', ', array_fill( 0, \count( $meta_fields ), '%s' ) ); | |
| 293 | + $search_conditions = array(); | |
| 294 | + | |
| 295 | + foreach ( $search_terms as $term ) { | |
| 296 | + $term = $n . $wpdb->esc_like( $term ) . $n; | |
| 297 | + | |
| 298 | + // Search in meta fields. | |
| 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 | |
| 307 | + } | |
| 308 | + | |
| 309 | + if ( ! empty( $search_conditions ) ) { | |
| 310 | + $search = ' AND (' . implode( ' AND ', $search_conditions ) . ') '; | |
| 311 | + if ( ! is_user_logged_in() ) { | |
| 312 | + $search .= " AND ($wpdb->posts.post_password = '') "; | |
| 313 | + } | |
| 314 | + } | |
| 315 | + | |
| 316 | + return $search; | |
| 299 | 317 | } |
| 300 | 318 | |
| 301 | 319 | /** |
| 302 | 320 | * Filters the JOIN clause of the query. |
| @@ -304,12 +322,17 @@ | ||
| 304 | 322 | * @param string $join The JOIN clause of the query. |
| 305 | 323 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 306 | 324 | * |
| 307 | 325 | * @return string |
| 308 | - * @deprecated Collection Rules now installs this behavior. | |
| 309 | 326 | */ |
| 310 | 327 | public function wcpos_posts_join_to_posts_search( string $join, WP_Query $query ) { |
| 311 | - return empty( $query->query_vars['s'] ) ? $join : Product_Search::posts_join( $join, $query->query_vars ); | |
| 328 | + global $wpdb; | |
| 329 | + | |
| 330 | + if ( ! empty( $query->query_vars['s'] ) && false === strpos( $join, 'pm1' ) ) { | |
| 331 | + $join .= " LEFT JOIN {$wpdb->postmeta} pm1 ON {$wpdb->posts}.ID = pm1.post_id "; | |
| 332 | + } | |
| 333 | + | |
| 334 | + return $join; | |
| 312 | 335 | } |
| 313 | 336 | |
| 314 | 337 | /** |
| 315 | 338 | * Filters the GROUP BY clause of the query. |
| @@ -317,12 +340,17 @@ | ||
| 317 | 340 | * @param string $groupby The GROUP BY clause of the query. |
| 318 | 341 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 319 | 342 | * |
| 320 | 343 | * @return string |
| 321 | - * @deprecated Collection Rules now installs this behavior. | |
| 322 | 344 | */ |
| 323 | 345 | public function wcpos_posts_groupby_posts_search( string $groupby, WP_Query $query ) { |
| 324 | - return empty( $query->query_vars['s'] ) ? $groupby : Product_Search::posts_groupby( $groupby, $query->query_vars ); | |
| 346 | + global $wpdb; | |
| 347 | + | |
| 348 | + if ( ! empty( $query->query_vars['s'] ) ) { | |
| 349 | + $groupby = "{$wpdb->posts}.ID"; | |
| 350 | + } | |
| 351 | + | |
| 352 | + return $groupby; | |
| 325 | 353 | } |
| 326 | 354 | |
| 327 | 355 | /** |
| 328 | 356 | * Filter the query arguments for a request. |
| @@ -332,10 +360,21 @@ | ||
| 332 | 360 | * |
| 333 | 361 | * @return array $args Key value array of query var to query value. |
| 334 | 362 | */ |
| 335 | 363 | public function wcpos_product_variation_query( array $args, WP_REST_Request $request ) { |
| 364 | + if ( ! empty( $request['search'] ) ) { | |
| 365 | + // We need to set the query up for a postmeta join. | |
| 366 | + add_filter( 'posts_join', array( $this, 'wcpos_posts_join_to_posts_search' ), 10, 2 ); | |
| 367 | + add_filter( 'posts_groupby', array( $this, 'wcpos_posts_groupby_posts_search' ), 10, 2 ); | |
| 368 | + } | |
| 369 | + | |
| 370 | + // if POS only products are enabled, exclude online-only products. | |
| 371 | + if ( $this->wcpos_pos_only_products_enabled() ) { | |
| 372 | + add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_exclude_online_only' ), 10, 2 ); | |
| 373 | + } | |
| 374 | + | |
| 336 | 375 | // Check for wcpos_include/wcpos_exclude parameter. |
| 337 | - // The Collection Rules visibility backstop runs first, at priority 10. | |
| 376 | + // NOTE: do this after POS visibility filter so that takes precedence. | |
| 338 | 377 | if ( isset( $request['wcpos_include'] ) || isset( $request['wcpos_exclude'] ) ) { |
| 339 | 378 | add_filter( 'posts_where', array( $this, 'wcpos_posts_where_product_variation_include_exclude' ), 20, 2 ); |
| 340 | 379 | } |
| 341 | 380 | |
| @@ -351,9 +390,8 @@ | ||
| 351 | 390 | * @param string $where The WHERE clause of the query. |
| 352 | 391 | * @param WP_Query $query The WP_Query instance (passed by reference). |
| 353 | 392 | * |
| 354 | 393 | * @return string |
| 355 | - * @deprecated Collection Rules now installs this behavior. | |
| 356 | 394 | */ |
| 357 | 395 | public function wcpos_posts_where_product_variation_exclude_online_only( string $where, WP_Query $query ) { |
| 358 | 396 | global $wpdb; |
| 359 | 397 | |
| @@ -435,9 +473,9 @@ | ||
| 435 | 473 | * |
| 436 | 474 | * @param WP_REST_Request $request Full details about the request. |
| 437 | 475 | */ |
| 438 | 476 | public function wcpos_get_all_items( $request ) { |
| 439 | - return $this->get_items( $request ); | |
| 477 | + return parent::get_items( $request ); | |
| 440 | 478 | } |
| 441 | 479 | |
| 442 | 480 | |
| 443 | 481 | /** |
| @@ -450,9 +488,8 @@ | ||
| 450 | 488 | * @param array $clauses Associative array of the clauses for the query. |
| 451 | 489 | * @param WP_Query $wp_query The WP_Query instance. |
| 452 | 490 | * |
| 453 | 491 | * @return array |
| 454 | - * @deprecated Collection Rules now installs this behavior. | |
| 455 | 492 | */ |
| 456 | 493 | public function wcpos_posts_clauses( array $clauses, WP_Query $wp_query ): array { |
| 457 | 494 | if ( ! isset( $this->wcpos_request ) ) { |
| 458 | 495 | return $clauses; |
| @@ -481,12 +518,11 @@ | ||
| 481 | 518 | /* |
| 482 | 519 | * The POS sorts (`sku`, `barcode`, `stock_quantity`, `stock_status`) are NOT mapped |
| 483 | 520 | * onto `meta_key` + `orderby => meta_value` here any more. That pair INNER JOINs |
| 484 | 521 | * 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 | |
| 522 | + * as a filter. `Sync\Collection_Rules` declares them and `wcpos_posts_clauses()` | |
| 486 | 523 | * applies them as a LEFT JOIN, on this lane and on `wcpos/v2` alike. |
| 487 | 524 | */ |
| 488 | 525 | |
| 489 | - $plan = Collection_Rules::for_request( 'variations', $request, self::WCPOS_SORT_PARAM_MAP ); | |
| 490 | - return $plan->filter( Collection_Rules_Plan::HOOK_PREPARE_ARGS, $args ); | |
| 526 | + return $args; | |
| 491 | 527 | } |
| 492 | 528 | } |