PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.1
WooCommerce v10.9.1
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / src / Internal / DataStores / Orders / OrdersTableSearchQuery.php
woocommerce / src / Internal / DataStores / Orders Last commit date
CustomOrdersTableController.php 3 months ago DataSynchronizer.php 7 months ago LegacyDataCleanup.php 1 year ago LegacyDataHandler.php 5 months ago OrdersTableDataStore.php 1 month ago OrdersTableDataStoreMeta.php 1 year ago OrdersTableFieldQuery.php 2 years ago OrdersTableMetaQuery.php 1 year ago OrdersTableQuery.php 1 month ago OrdersTableRefundDataStore.php 1 month ago OrdersTableSearchQuery.php 11 months ago
OrdersTableSearchQuery.php
460 lines
1 <?php
2
3 namespace Automattic\WooCommerce\Internal\DataStores\Orders;
4
5 use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
6 use Exception;
7
8 /**
9 * Creates the join and where clauses needed to perform an order search using Custom Order Tables.
10 *
11 * @internal
12 */
13 class OrdersTableSearchQuery {
14 /**
15 * Holds the Orders Table Query object.
16 *
17 * @var OrdersTableQuery
18 */
19 private $query;
20
21 /**
22 * Holds the search term to be used in the WHERE clauses.
23 *
24 * @var string
25 */
26 private $search_term;
27
28 /**
29 * Limits the search to a specific field.
30 *
31 * @var string[]
32 */
33 private $search_filters;
34
35 /**
36 * Alias used for the derived table that holds FTS product hits.
37 */
38 private const PRODUCTS_JOIN_ALIAS = 'fts_items';
39
40 /**
41 * Alias used for the derived table that holds FTS customer/address hits.
42 */
43 private const CUSTOMERS_JOIN_ALIAS = 'fts_addresses';
44
45 /**
46 * Creates the JOIN and WHERE clauses needed to execute a search of orders.
47 *
48 * @internal
49 *
50 * @param OrdersTableQuery $query The order query object.
51 */
52 public function __construct( OrdersTableQuery $query ) {
53 $this->query = $query;
54 $this->search_term = $query->get( 's' );
55 $this->search_filters = $this->sanitize_search_filters( $query->get( 'search_filter' ) ?? '' );
56 }
57
58 /**
59 * Sanitize search filter param.
60 *
61 * @param string $search_filter Search filter param.
62 *
63 * @return array Array of search filters.
64 */
65 private function sanitize_search_filters( string $search_filter ): array {
66 $core_filters = array(
67 'order_id',
68 'transaction_id',
69 'customer_email',
70 'customers', // customers also searches in meta.
71 'products',
72 );
73
74 if ( 'all' === $search_filter || '' === $search_filter ) {
75 return $core_filters;
76 } else {
77 return array( $search_filter );
78 }
79 }
80
81 /**
82 * Supplies an array of clauses to be used in an order query.
83 *
84 * @internal
85 * @throws Exception If unable to generate either the JOIN or WHERE SQL fragments.
86 *
87 * @return array {
88 * @type string $join JOIN clause.
89 * @type string $where WHERE clause.
90 * }
91 */
92 public function get_sql_clauses(): array {
93 return array(
94 'join' => array( $this->generate_join() ),
95 'where' => array( $this->generate_where() ),
96 );
97 }
98
99 /**
100 * Generates the necessary JOIN clauses for the order search to be performed.
101 *
102 * @throws Exception May be triggered if a table name cannot be determined.
103 *
104 * @return string
105 */
106 private function generate_join(): string {
107 $join = array();
108
109 foreach ( $this->search_filters as $search_filter ) {
110 $join[] = $this->generate_join_for_search_filter( $search_filter );
111 }
112
113 return implode( ' ', $join );
114 }
115
116 /**
117 * Generate JOIN clause for a given search filter.
118 * Right now we only have the products filter that actually does a JOIN, but in the future we may add more -- for example, custom order fields, payment tokens, and so on. This function makes it easier to add more filters in the future.
119 *
120 * If a search filter needs a JOIN, it will also need a WHERE clause.
121 *
122 * @param string $search_filter Name of the search filter.
123 *
124 * @return string JOIN clause.
125 */
126 private function generate_join_for_search_filter( $search_filter ): string {
127 $join = '';
128
129 if ( 'products' === $search_filter ) {
130 $join = $this->maybe_get_join_for_products();
131 }
132
133 if ( 'customers' === $search_filter ) {
134 $join = $this->maybe_get_join_for_customers();
135 }
136 /**
137 * Filter to support adding a custom order search filter.
138 * Provide a JOIN clause for a new search filter. This should be used along with `woocommerce_hpos_admin_search_filters`
139 * to declare a new custom filter, and `woocommerce_hpos_generate_where_for_search_filter` to generate the WHERE
140 * clause.
141 *
142 * Hardcoded JOINS (products) cannot be modified using this filter for consistency.
143 *
144 * @since 8.9.0
145 *
146 * @param string $join The JOIN clause.
147 * @param string $search_term The search term.
148 * @param string $search_filter The search filter. Use this to bail early if this is not filter you are interested in.
149 * @param OrdersTableQuery $query The order query object.
150 */
151 return apply_filters(
152 'woocommerce_hpos_generate_join_for_search_filter',
153 $join,
154 $this->search_term,
155 $search_filter,
156 $this->query
157 );
158 }
159
160 /**
161 * Returns a prepared JOIN fragment for products when FTS is enabled.
162 *
163 * @since 10.1.0
164 * @return string JOIN clause or empty string if FTS disabled.
165 */
166 private function maybe_get_join_for_products(): string {
167 global $wpdb;
168
169 $db_util = wc_get_container()->get( DatabaseUtil::class );
170 $items_table = $this->query->get_table_name( 'items' );
171 $orders_table = $this->query->get_table_name( 'orders' );
172
173 $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes'
174 && get_option( CustomOrdersTableController::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION ) === 'yes';
175
176 if ( ! $fts_enabled ) {
177 return '';
178 }
179
180 $search_pattern = $wpdb->esc_like( $db_util->sanitise_boolean_fts_search_term( $this->search_term ) );
181
182 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
183 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
184 return $wpdb->prepare(
185 "LEFT JOIN (
186 SELECT DISTINCT order_id
187 FROM $items_table
188 WHERE MATCH ( order_item_name ) AGAINST ( %s IN BOOLEAN MODE )
189 ) AS " . self::PRODUCTS_JOIN_ALIAS . ' ON ' . self::PRODUCTS_JOIN_ALIAS . ".order_id = $orders_table.id",
190 $search_pattern
191 );
192 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
193 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
194 }
195
196 /**
197 * Returns a prepared JOIN fragment for customers/addresses when FTS is enabled.
198 *
199 * @since 10.1.0
200 * @return string JOIN clause or empty string if FTS disabled.
201 */
202 private function maybe_get_join_for_customers(): string {
203 global $wpdb;
204
205 $db_util = wc_get_container()->get( DatabaseUtil::class );
206 $address_table = $this->query->get_table_name( 'addresses' );
207 $orders_table = $this->query->get_table_name( 'orders' );
208
209 $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes'
210 && get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes';
211
212 if ( ! $fts_enabled ) {
213 return '';
214 }
215
216 $search_pattern = $wpdb->esc_like( $db_util->sanitise_boolean_fts_search_term( $this->search_term ) );
217
218 // Support for phone was added in 9.4.
219 $maybe_phone_field = '';
220 if ( version_compare( get_option( 'woocommerce_db_version' ), '9.4.0', '>=' ) ) {
221 $maybe_phone_field = ', phone';
222 }
223
224 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
225 // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
226 return $wpdb->prepare(
227 "LEFT JOIN (
228 SELECT DISTINCT order_id
229 FROM $address_table
230 WHERE MATCH (
231 first_name, last_name, company,
232 address_1, address_2, city, state,
233 postcode, country, email $maybe_phone_field
234 ) AGAINST ( %s IN BOOLEAN MODE )
235 ) AS " . self::CUSTOMERS_JOIN_ALIAS . ' ON ' . self::CUSTOMERS_JOIN_ALIAS . ".order_id = $orders_table.id",
236 $search_pattern
237 );
238 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
239 // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
240 }
241
242 /**
243 * Generates the necessary WHERE clauses for the order search to be performed.
244 *
245 * @throws Exception May be triggered if a table name cannot be determined.
246 *
247 * @return string
248 */
249 private function generate_where(): string {
250 $where = array();
251 $possible_order_id = (string) absint( $this->search_term );
252 $order_table = $this->query->get_table_name( 'orders' );
253
254 // Support the passing of an order ID as the search term.
255 if ( (string) $this->query->get( 's' ) === $possible_order_id ) {
256 $where[] = "`$order_table`.id = $possible_order_id";
257 }
258
259 foreach ( $this->search_filters as $search_filter ) {
260 $search_where = trim( $this->generate_where_for_search_filter( $search_filter ) );
261 if ( strlen( $search_where ) > 0 ) {
262 $where[] = $search_where;
263 }
264 }
265
266 $where_statement = implode( ' OR ', $where );
267
268 return ( strlen( $where_statement ) > 0 ) ? " ( $where_statement ) " : '';
269 }
270
271 /**
272 * Generates WHERE clause for a given search filter. Right now we only have the products and customers filters that actually use WHERE, but in the future we may add more -- for example, custom order fields, payment tokens and so on. This function makes it easier to add more filters in the future.
273 *
274 * @param string $search_filter Name of the search filter.
275 *
276 * @return string WHERE clause.
277 */
278 private function generate_where_for_search_filter( string $search_filter ): string {
279 global $wpdb;
280
281 $order_table = $this->query->get_table_name( 'orders' );
282
283 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $order_table is hardcoded.
284 if ( 'customer_email' === $search_filter ) {
285 return $wpdb->prepare(
286 "`$order_table`.billing_email LIKE %s",
287 $wpdb->esc_like( $this->search_term ) . '%'
288 );
289 }
290
291 if ( 'order_id' === $search_filter && is_numeric( $this->search_term ) ) {
292 return $wpdb->prepare(
293 "`$order_table`.id = %d",
294 absint( $this->search_term )
295 );
296 }
297
298 if ( 'transaction_id' === $search_filter ) {
299 return $wpdb->prepare(
300 "`$order_table`.transaction_id LIKE %s",
301 '%' . $wpdb->esc_like( $this->search_term ) . '%'
302 );
303 }
304 // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
305
306 if ( 'products' === $search_filter ) {
307 return $this->get_where_for_products();
308 }
309
310 if ( 'customers' === $search_filter ) {
311 return $this->get_where_for_customers();
312 }
313
314 /**
315 * Filter to support adding a custom order search filter.
316 * Provide a WHERE clause for a custom search filter via this filter. This should be used with the
317 * `woocommerce_hpos_admin_search_filters` to declare a new custom filter, and optionally also with the
318 * `woocommerce_hpos_generate_join_for_search_filter` filter if a join is also needed.
319 *
320 * Hardcoded filters (products, customers, ID and email) cannot be modified using this filter for consistency.
321 *
322 * @since 8.9.0
323 *
324 * @param string $where WHERE clause to add to the search query.
325 * @param string $search_term The search term.
326 * @param string $search_filter Name of the search filter. Use this to bail early if this is not the filter you are looking for.
327 * @param OrdersTableQuery $query The order query object.
328 */
329 return apply_filters(
330 'woocommerce_hpos_generate_where_for_search_filter',
331 '',
332 $this->search_term,
333 $search_filter,
334 $this->query
335 );
336 }
337
338 /**
339 * Helper function to generate the WHERE clause for products search. Uses FTS when available.
340 *
341 * @return string|null WHERE clause for products search.
342 */
343 private function get_where_for_products() {
344 global $wpdb;
345 $db_util = wc_get_container()->get( DatabaseUtil::class );
346 $items_table = $this->query->get_table_name( 'items' );
347 $orders_table = $this->query->get_table_name( 'orders' );
348 $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ORDER_ITEM_INDEX_CREATED_OPTION ) === 'yes';
349
350 if ( $fts_enabled ) {
351 return self::PRODUCTS_JOIN_ALIAS . '.order_id IS NOT NULL';
352 }
353
354 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orders_table and $items_table are hardcoded.
355 return $wpdb->prepare(
356 "
357 $orders_table.id in (
358 SELECT order_id FROM $items_table search_query_items WHERE
359 search_query_items.order_item_name LIKE %s
360 )
361 ",
362 '%' . $wpdb->esc_like( $this->search_term ) . '%'
363 );
364 // phpcs:enable
365 }
366
367 /**
368 * Helper function to generate the WHERE clause for customers search. Uses FTS when available.
369 *
370 * @return string|null WHERE clause for customers search.
371 */
372 private function get_where_for_customers() {
373 global $wpdb;
374 $order_table = $this->query->get_table_name( 'orders' );
375 $address_table = $this->query->get_table_name( 'addresses' );
376
377 $db_util = wc_get_container()->get( DatabaseUtil::class );
378
379 $fts_enabled = get_option( CustomOrdersTableController::HPOS_FTS_INDEX_OPTION ) === 'yes' && get_option( CustomOrdersTableController::HPOS_FTS_ADDRESS_INDEX_CREATED_OPTION ) === 'yes';
380
381 if ( $fts_enabled ) {
382 return self::CUSTOMERS_JOIN_ALIAS . '.order_id IS NOT NULL';
383 }
384
385 $meta_sub_query = $this->generate_where_for_meta_table();
386 return "`$order_table`.id IN ( $meta_sub_query ) ";
387 }
388
389 /**
390 * Generates where clause for meta table.
391 *
392 * Note we generate the where clause as a subquery to be used by calling function inside the IN clause. This is against the general wisdom for performance, but in this particular case, a subquery is able to use the order_id-meta_key-meta_value index, which is not possible with a join.
393 *
394 * Since it can use the index, which otherwise would not be possible, it is much faster than both LEFT JOIN or SQL_CALC approach that could have been used.
395 *
396 * @return string The where clause for meta table.
397 */
398 private function generate_where_for_meta_table(): string {
399 global $wpdb;
400 $meta_table = $this->query->get_table_name( 'meta' );
401 $meta_fields = $this->get_meta_fields_to_be_searched();
402
403 if ( '' === $meta_fields ) {
404 return '-1';
405 }
406
407 // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_fields is already escaped before imploding, $meta_table is hardcoded.
408 return $wpdb->prepare(
409 "
410 SELECT search_query_meta.order_id
411 FROM $meta_table as search_query_meta
412 WHERE search_query_meta.meta_key IN ( $meta_fields )
413 AND search_query_meta.meta_value LIKE %s
414 GROUP BY search_query_meta.order_id
415 ",
416 '%' . $wpdb->esc_like( $this->search_term ) . '%'
417 );
418 // phpcs:enable
419 }
420
421 /**
422 * Returns the order meta field keys to be searched.
423 *
424 * These will be returned as a single string, where the meta keys have been escaped, quoted and are
425 * comma-separated (ie, "'abc', 'foo'" - ready for inclusion in a SQL IN() clause).
426 *
427 * @return string
428 */
429 private function get_meta_fields_to_be_searched(): string {
430 $meta_fields_to_search = array(
431 '_billing_address_index',
432 '_shipping_address_index',
433 );
434
435 /**
436 * Controls the order meta keys to be included in search queries.
437 *
438 * This hook is used when Custom Order Tables are in use: the corresponding hook when CPT-orders are in use
439 * is 'woocommerce_shop_order_search_fields'.
440 *
441 * @since 7.0.0
442 *
443 * @param array
444 */
445 $meta_keys = apply_filters(
446 'woocommerce_order_table_search_query_meta_keys',
447 $meta_fields_to_search
448 );
449
450 $meta_keys = (array) array_map(
451 function ( string $meta_key ): string {
452 return "'" . esc_sql( wc_clean( $meta_key ) ) . "'";
453 },
454 $meta_keys
455 );
456
457 return implode( ',', $meta_keys );
458 }
459 }
460