PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.17
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.17
1.10.25 1.10.24 1.10.23 1.10.22 1.10.21 1.10.20 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 All 169 releases
← All changes | includes/API/Product_Search.php +45 -9 1.10.5 → 1.10.17 View file →
@@ -13,8 +13,10 @@
13 13 /**
14 14 * Keeps v1 and v2 product search fields identical.
15 15 */
16 16 final class Product_Search {
17 + private const MAX_SEARCH_TERMS = 10;
18 +
17 19 /**
18 20 * Search product titles, SKUs, and the configured barcode field.
19 21 *
20 22 * @param string $search Search SQL.
@@ -22,25 +24,41 @@
22 24 * @return string
23 25 */
24 26 public static function posts_search( string $search, WP_Query $wp_query ): string {
25 27 global $wpdb;
26 - if ( empty( $search ) ) {
28 + $q = $wp_query->query_vars;
29 + $phrase = $q['wcpos_search_phrase'] ?? null;
30 + if ( empty( $search ) && null === $phrase ) {
27 31 return $search;
28 32 }
29 - $q = $wp_query->query_vars;
33 + $terms = null !== $phrase
34 + ? preg_split( '/[\s\p{Z}\p{C}]+/u', $phrase, -1, PREG_SPLIT_NO_EMPTY )
35 + : (array) $q['search_terms'];
36 + if ( null !== $phrase && ( false === $terms || array() === $terms ) ) {
37 + return ' AND 1=0 ';
38 + }
39 + // Like WP_Query::parse_search() and WooCommerce's search_products(), collapse over-long lists to the phrase.
40 + if ( null !== $phrase && self::MAX_SEARCH_TERMS < \count( $terms ) ) {
41 + $terms = array( $phrase );
42 + }
30 43 $n = ! empty( $q['exact'] ) ? '' : '%';
31 44 $meta_fields = Barcode_Field::search_keys();
45 + $meta_placeholders = implode( ', ', array_fill( 0, \count( $meta_fields ), '%s' ) );
32 46 $search_conditions = array();
33 - foreach ( (array) $q['search_terms'] as $term ) {
47 + foreach ( $terms as $term ) {
34 48 $term = $n . $wpdb->esc_like( $term ) . $n;
35 - $search_conditions[] = $wpdb->prepare( "({$wpdb->posts}.post_title LIKE %s)", $term ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is safe.
36 - foreach ( $meta_fields as $field ) {
37 - $search_conditions[] = $wpdb->prepare( '(pm1.meta_value LIKE %s AND pm1.meta_key = %s)', $term, $field );
38 - }
49 + // 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.
50 + $search_conditions[] = $wpdb->prepare(
51 + "( {$wpdb->posts}.post_title LIKE %s OR EXISTS (
52 + 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
53 + ) )",
54 + array_merge( array( $term ), $meta_fields, array( $term ) )
55 + );
56 + // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
39 57 }
40 58
41 59 if ( ! empty( $search_conditions ) ) {
42 - $search = ' AND (' . implode( ' OR ', $search_conditions ) . ') ';
60 + $search = ' AND (' . implode( ' AND ', $search_conditions ) . ') ';
43 61 if ( ! is_user_logged_in() ) {
44 62 $search .= " AND ($wpdb->posts.post_password = '') ";
45 63 }
46 64 }
@@ -77,8 +95,26 @@
77 95 return $groupby;
78 96 }
79 97
80 98 /**
99 + * Rank exact SKU or barcode matches ahead of substring matches.
100 + *
101 + * @param string $orderby ORDER BY SQL.
102 + * @param WP_Query $query Query instance.
103 + */
104 + public static function posts_orderby( string $orderby, WP_Query $query ): string {
105 + global $wpdb;
106 + if ( ! self::is_searching( $query ) ) {
107 + return $orderby;
108 + }
109 + $keys = Barcode_Field::search_keys();
110 + return $wpdb->prepare(
111 + 'MIN(CASE WHEN pm1.meta_key IN (' . implode( ', ', array_fill( 0, count( $keys ), '%s' ) ) . ') AND pm1.meta_value = %s THEN 0 ELSE 1 END) ASC',
112 + array_merge( $keys, array( trim( (string) ( $query->query_vars['wcpos_search_phrase'] ?? $query->query_vars['s'] ) ) ) )
113 + ) . ', ' . ( '' === trim( $orderby ) ? "{$wpdb->posts}.ID DESC" : $orderby );
114 + }
115 +
116 + /**
81 117 * Whether the query carries a search term. Not empty(): the literal term "0" is a search.
82 118 *
83 119 * @param WP_Query $query Query instance.
84 120 * @return bool
@@ -83,7 +119,7 @@
83 119 * @param WP_Query $query Query instance.
84 120 * @return bool
85 121 */
86 122 private static function is_searching( WP_Query $query ): bool {
87 - return isset( $query->query_vars['s'] ) && '' !== (string) $query->query_vars['s'];
123 + return '' !== (string) ( $query->query_vars['wcpos_search_phrase'] ?? $query->query_vars['s'] ?? '' );
88 124 }
89 125 }