| @@ -157,9 +157,9 @@ | ||
| 157 | 157 | * |
| 158 | 158 | * WooCommerce maps `search` onto `s`, which searches post_title/content — useless for a |
| 159 | 159 | * variation, whose title is a generated attribute string. The POS searches what a cashier |
| 160 | 160 | * actually types or scans: the SKU and whichever meta key the store configured as its |
| 161 | - * barcode field (`Barcode_Field::search_keys()`). Every term must match a carrier. | |
| 161 | + * barcode field (`Barcode_Field::search_keys()`). Every term must match at least one carrier. | |
| 162 | 162 | * |
| 163 | 163 | * `sku` is left to WooCommerce: its own exact/comma-list handling is what the |
| 164 | 164 | * sku-beats-search precedence rule relies on. |
| 165 | 165 | */ |
| @@ -171,10 +171,14 @@ | ||
| 171 | 171 | } |
| 172 | 172 | if ( '' !== $search && '' === $sku ) { |
| 173 | 173 | unset( $args['s'] ); |
| 174 | 174 | $args['wcpos_variation_search'] = true; |
| 175 | + $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 176 | + if ( false === $terms ) { | |
| 177 | + $terms = array(); | |
| 178 | + } | |
| 175 | 179 | $carriers = array( 'relation' => 'AND' ); |
| 176 | - foreach ( (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ) as $term ) { | |
| 180 | + foreach ( $terms as $term ) { | |
| 177 | 181 | $term_carriers = array( 'relation' => 'OR' ); |
| 178 | 182 | foreach ( Barcode_Field::search_keys() as $key ) { |
| 179 | 183 | $term_carriers[] = array( |
| 180 | 184 | 'key' => $key, |
| @@ -438,12 +442,20 @@ | ||
| 438 | 442 | return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'sku must not contain more than 100 comma-separated terms', array( 'status' => 400 ) ); |
| 439 | 443 | } |
| 440 | 444 | } else { |
| 441 | 445 | $search = (string) $request->get_param( 'search' ); |
| 442 | - if ( self::MAX_SEARCH_LENGTH < \strlen( $search ) ) { | |
| 443 | - return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not exceed 256 bytes', array( 'status' => 400 ) ); | |
| 446 | + // Unlike mb_strlen(), PCRE is independent of blog_charset and detects malformed UTF-8. | |
| 447 | + $characters = preg_match_all( '/./us', $search ); | |
| 448 | + if ( false === $characters ) { | |
| 449 | + return new WP_Error( 'woocommerce_pos_variations_search_invalid', 'search must be valid UTF-8', array( 'status' => 400 ) ); | |
| 444 | 450 | } |
| 445 | - $terms = (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 451 | + if ( self::MAX_SEARCH_LENGTH < $characters ) { | |
| 452 | + return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not exceed 256 characters', array( 'status' => 400 ) ); | |
| 453 | + } | |
| 454 | + $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 455 | + if ( false === $terms ) { | |
| 456 | + return new WP_Error( 'woocommerce_pos_variations_search_invalid', 'search must be valid UTF-8', array( 'status' => 400 ) ); | |
| 457 | + } | |
| 446 | 458 | if ( self::MAX_SEARCH_TERMS < \count( $terms ) ) { |
| 447 | 459 | return new WP_Error( 'woocommerce_pos_variations_search_limit_exceeded', 'search must not contain more than 10 whitespace-separated terms', array( 'status' => 400 ) ); |
| 448 | 460 | } |
| 449 | 461 | } |
| @@ -490,8 +502,9 @@ | ||
| 490 | 502 | * code. 1.9.x extended the same enum for the same reason. |
| 491 | 503 | */ |
| 492 | 504 | public function get_collection_params() { |
| 493 | 505 | $params = parent::get_collection_params(); |
| 506 | + $params['search']['sanitize_callback'] = 'rest_sanitize_request_arg'; | |
| 494 | 507 | |
| 495 | 508 | if ( isset( $params['orderby']['enum'] ) && \is_array( $params['orderby']['enum'] ) ) { |
| 496 | 509 | $params['orderby']['enum'] = array_values( |
| 497 | 510 | array_unique( |
| @@ -530,10 +543,14 @@ | ||
| 530 | 543 | return true; |
| 531 | 544 | } |
| 532 | 545 | |
| 533 | 546 | $search = (string) ( $request->get_param( 'search' ) ?? '' ); |
| 547 | + $terms = preg_split( '/[\s\p{Z}\p{C}]+/u', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 548 | + if ( false === $terms ) { | |
| 549 | + return false; | |
| 550 | + } | |
| 534 | 551 | |
| 535 | - return array() !== (array) preg_split( '/\s+/', trim( $search ), -1, PREG_SPLIT_NO_EMPTY ); | |
| 552 | + return array() !== $terms; | |
| 536 | 553 | } |
| 537 | 554 | |
| 538 | 555 | /** |
| 539 | 556 | * One page of the POS-servable variation collection, with its total. |