PluginProbe
ElasticPress / 4.7.0
ElasticPress v4.7.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Feature / WooCommerce / Orders.php

Orders.php in ElasticPress 4.7.0, at includes/classes/Feature/WooCommerce/Orders.php

462 lines 13.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WooCommerce Orders
4 *
5 * @since 4.7.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Feature\WooCommerce;
10
11 use ElasticPress\Indexables;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit; // Exit if accessed directly.
15 }
16
17 /**
18 * WooCommerce Orders
19 */
20 class Orders {
21 /**
22 * WooCommerce feature object instance
23 *
24 * @var WooCommerce
25 */
26 protected $woocommerce;
27
28 /**
29 * Class constructor
30 *
31 * @param WooCommerce $woocommerce WooCommerce feature object instance
32 */
33 public function __construct( WooCommerce $woocommerce ) {
34 $this->woocommerce = $woocommerce;
35 }
36
37 /**
38 * Setup order related hooks
39 */
40 public function setup() {
41 add_filter( 'ep_sync_insert_permissions_bypass', [ $this, 'bypass_order_permissions_check' ], 10, 2 );
42 add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'allow_meta_keys' ] );
43 add_filter( 'ep_post_sync_args_post_prepare_meta', [ $this, 'add_order_items_search' ], 20, 2 );
44 add_filter( 'ep_pc_skip_post_content_cleanup', [ $this, 'keep_order_fields' ], 20, 2 );
45 add_action( 'parse_query', [ $this, 'maybe_hook_woocommerce_search_fields' ], 1 );
46 add_action( 'parse_query', [ $this, 'search_order' ], 11 );
47 add_action( 'pre_get_posts', [ $this, 'translate_args' ], 11, 1 );
48 }
49
50 /**
51 * Allow order creations on the front end to get synced
52 *
53 * @param bool $override Original order perms check value
54 * @param int $post_id Post ID
55 * @return bool
56 */
57 public function bypass_order_permissions_check( $override, $post_id ) {
58 $searchable_post_types = $this->get_admin_searchable_post_types();
59
60 if ( in_array( get_post_type( $post_id ), $searchable_post_types, true ) ) {
61 return true;
62 }
63
64 return $override;
65 }
66
67 /**
68 * Returns the WooCommerce-oriented post types in admin that EP will search
69 *
70 * @return array
71 */
72 public function get_admin_searchable_post_types() {
73 $searchable_post_types = array( 'shop_order' );
74
75 /**
76 * Filter admin searchable WooCommerce post types
77 *
78 * @hook ep_woocommerce_admin_searchable_post_types
79 * @since 4.4.0
80 * @param {array} $post_types Post types
81 * @return {array} New post types
82 */
83 return apply_filters( 'ep_woocommerce_admin_searchable_post_types', $searchable_post_types );
84 }
85
86 /**
87 * Index WooCommerce orders meta fields
88 *
89 * @param array $meta Existing post meta
90 * @return array
91 */
92 public function allow_meta_keys( $meta ) {
93 return array_unique(
94 array_merge(
95 $meta,
96 array(
97 '_customer_user',
98 '_order_key',
99 '_billing_company',
100 '_billing_address_1',
101 '_billing_address_2',
102 '_billing_city',
103 '_billing_postcode',
104 '_billing_country',
105 '_billing_state',
106 '_billing_email',
107 '_billing_phone',
108 '_shipping_address_1',
109 '_shipping_address_2',
110 '_shipping_city',
111 '_shipping_postcode',
112 '_shipping_country',
113 '_shipping_state',
114 '_billing_last_name',
115 '_billing_first_name',
116 '_shipping_first_name',
117 '_shipping_last_name',
118 '_variations_skus',
119 )
120 )
121 );
122 }
123
124 /**
125 * Add order items as a searchable string.
126 *
127 * This mimics how WooCommerce currently does in the order_itemmeta
128 * table. They combine the titles of the products and put them in a
129 * meta field called "Items".
130 *
131 * @param array $post_args Post arguments
132 * @param string|int $post_id Post id
133 *
134 * @return array
135 */
136 public function add_order_items_search( $post_args, $post_id ) {
137 $searchable_post_types = $this->get_admin_searchable_post_types();
138
139 // Make sure it is only WooCommerce orders we touch.
140 if ( ! in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
141 return $post_args;
142 }
143
144 $post_indexable = Indexables::factory()->get( 'post' );
145
146 // Get order items.
147 $order = wc_get_order( $post_id );
148 $item_meta = [];
149 foreach ( $order->get_items() as $delta => $product_item ) {
150 // WooCommerce 3.x uses WC_Order_Item_Product instance while 2.x an array
151 if ( is_object( $product_item ) && method_exists( $product_item, 'get_name' ) ) {
152 $item_meta['_items'][] = $product_item->get_name( 'edit' );
153 } elseif ( is_array( $product_item ) && isset( $product_item['name'] ) ) {
154 $item_meta['_items'][] = $product_item['name'];
155 }
156 }
157
158 // Prepare order items.
159 $item_meta['_items'] = empty( $item_meta['_items'] ) ? '' : implode( '|', $item_meta['_items'] );
160 $post_args['meta'] = array_merge( $post_args['meta'], $post_indexable->prepare_meta_types( $item_meta ) );
161
162 return $post_args;
163 }
164
165 /**
166 * Prevent order fields from being removed.
167 *
168 * When Protected Content is enabled, all posts with password have their content removed.
169 * This can't happen for orders, as the order key is added in that field.
170 *
171 * @see https://github.com/10up/ElasticPress/issues/2726
172 *
173 * @param bool $skip Whether the password protected content should have their content, and meta removed
174 * @param array $post_args Post arguments
175 * @return bool
176 */
177 public function keep_order_fields( $skip, $post_args ) {
178 $searchable_post_types = $this->get_admin_searchable_post_types();
179
180 if ( in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
181 return true;
182 }
183
184 return $skip;
185 }
186
187 /**
188 * Sets woocommerce meta search fields to an empty array if we are integrating the main query with ElasticSearch
189 *
190 * Woocommerce calls this action as part of its own callback on parse_query. We add this filter only if the query
191 * is integrated with ElasticSearch.
192 * If we were to always return array() on this filter, we'd break admin searches when WooCommerce module is activated
193 * without the Protected Content Module
194 *
195 * @param \WP_Query $query Current query
196 */
197 public function maybe_hook_woocommerce_search_fields( $query ) {
198 global $pagenow, $wp, $wc_list_table;
199
200 if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
201 return;
202 }
203
204 /**
205 * Determines actions to be applied, or removed, if doing a WooCommerce serarch
206 *
207 * @hook ep_woocommerce_hook_search_fields
208 * @since 4.4.0
209 */
210 do_action( 'ep_woocommerce_hook_search_fields' );
211
212 if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
213 return;
214 }
215
216 remove_action( 'parse_query', [ $wc_list_table, 'search_custom_fields' ] );
217 }
218
219 /**
220 * Enhance WooCommerce search order by order id, email, phone number, name, etc..
221 * What this function does:
222 * 1. Reverse the woocommerce shop_order_search_custom_fields query
223 * 2. If the search key is integer and it is an Order Id, just query with post__in
224 * 3. If the search key is integer but not an order id ( might be phone number ), use ES to find it
225 *
226 * @param WP_Query $wp WP Query
227 */
228 public function search_order( $wp ) {
229 global $pagenow;
230
231 if ( ! $this->woocommerce->should_integrate_with_query( $wp ) ) {
232 return;
233 }
234
235 $searchable_post_types = $this->get_admin_searchable_post_types();
236
237 if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['post_type'] ) || ! in_array( $wp->query_vars['post_type'], $searchable_post_types, true ) ||
238 ( empty( $wp->query_vars['s'] ) && empty( $wp->query_vars['shop_order_search'] ) ) ) {
239 return;
240 }
241
242 // phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
243 if ( isset( $_GET['s'] ) ) {
244 $search_key_safe = str_replace( array( 'Order #', '#' ), '', wc_clean( $_GET['s'] ) );
245 unset( $wp->query_vars['post__in'] );
246 $wp->query_vars['s'] = $search_key_safe;
247 }
248 // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
249 }
250
251 /**
252 * Determines whether or not ES should be integrating with the provided query
253 *
254 * @param \WP_Query $query Query we might integrate with
255 * @return bool
256 */
257 public function should_integrate_with_query( \WP_Query $query ) : bool {
258 /**
259 * Check the post type
260 */
261 $supported_post_types = $this->get_supported_post_types( $query );
262 $post_type = $query->get( 'post_type', false );
263 if ( ! empty( $post_type ) &&
264 ( in_array( $post_type, $supported_post_types, true ) ||
265 ( is_array( $post_type ) && ! array_diff( $post_type, $supported_post_types ) ) )
266 ) {
267 return true;
268 }
269
270 return false;
271 }
272
273 /**
274 * Get the supported post types for Order related queries
275 *
276 * @return array
277 */
278 public function get_supported_post_types() : array {
279 $post_types = [ 'shop_order', 'shop_order_refund' ];
280
281 /**
282 * DEPRECATED. Expands or contracts the post_types eligible for indexing.
283 *
284 * @hook ep_woocommerce_default_supported_post_types
285 * @since 4.4.0
286 * @param {array} $post_types Post types
287 * @return {array} New post types
288 */
289 $supported_post_types = apply_filters_deprecated(
290 'ep_woocommerce_default_supported_post_types',
291 [ $post_types ],
292 '4.7.0',
293 'ep_woocommerce_orders_supported_post_types'
294 );
295
296 /**
297 * Expands or contracts the post_types related to orders eligible for indexing.
298 *
299 * @hook ep_woocommerce_orders_supported_post_types
300 * @since 4.7.0
301 * @param {array} $post_types Post types
302 * @return {array} New post types
303 */
304 $supported_post_types = apply_filters( 'ep_woocommerce_orders_supported_post_types', $post_types );
305
306 $supported_post_types = array_intersect(
307 $supported_post_types,
308 Indexables::factory()->get( 'post' )->get_indexable_post_types()
309 );
310
311 return $supported_post_types;
312 }
313
314 /**
315 * If the query has a search term, add the order fields that need to be searched.
316 *
317 * @param \WP_Query $query The WP_Query
318 * @return \WP_Query
319 */
320 protected function maybe_set_search_fields( \WP_Query $query ) {
321 $search_term = $this->woocommerce->get_search_term( $query );
322 if ( empty( $search_term ) ) {
323 return $query;
324 }
325
326 $searchable_post_types = $this->get_admin_searchable_post_types();
327
328 $post_type = $query->get( 'post_type', false );
329 if ( ! in_array( $post_type, $searchable_post_types, true ) ) {
330 return $query;
331 }
332
333 $default_search_fields = array( 'post_title', 'post_content', 'post_excerpt' );
334 if ( ctype_digit( $search_term ) ) {
335 $default_search_fields[] = 'ID';
336 }
337 $search_fields = $query->get( 'search_fields', $default_search_fields );
338
339 $search_fields['meta'] = array_map(
340 'wc_clean',
341 /**
342 * Filter shop order meta fields to search for WooCommerce
343 *
344 * @hook shop_order_search_fields
345 * @param {array} $fields Shop order fields
346 * @return {array} New fields
347 */
348 apply_filters(
349 'shop_order_search_fields',
350 array(
351 '_order_key',
352 '_billing_company',
353 '_billing_address_1',
354 '_billing_address_2',
355 '_billing_city',
356 '_billing_postcode',
357 '_billing_country',
358 '_billing_state',
359 '_billing_email',
360 '_billing_phone',
361 '_shipping_address_1',
362 '_shipping_address_2',
363 '_shipping_city',
364 '_shipping_postcode',
365 '_shipping_country',
366 '_shipping_state',
367 '_billing_last_name',
368 '_billing_first_name',
369 '_shipping_first_name',
370 '_shipping_last_name',
371 '_items',
372 )
373 )
374 );
375
376 $query->set(
377 'search_fields',
378 /**
379 * Filter all the shop order fields to search for WooCommerce
380 *
381 * @hook ep_woocommerce_shop_order_search_fields
382 * @since 4.0.0
383 * @param {array} $fields Shop order fields
384 * @param {WP_Query} $query WP Query
385 * @return {array} New fields
386 */
387 apply_filters( 'ep_woocommerce_shop_order_search_fields', $search_fields, $query )
388 );
389 }
390
391 /**
392 * Translate args to ElasticPress compat format. This is the meat of what the feature does
393 *
394 * @param \WP_Query $query WP Query
395 */
396 public function translate_args( $query ) {
397 if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
398 return;
399 }
400
401 if ( ! $this->should_integrate_with_query( $query ) ) {
402 return;
403 }
404
405 $query->set( 'ep_integrate', true );
406
407 /**
408 * Make sure filters are suppressed
409 */
410 $query->query['suppress_filters'] = false;
411 $query->set( 'suppress_filters', false );
412
413 $this->maybe_set_search_fields( $query );
414 }
415
416 /**
417 * Handle calls to OrdersAutosuggest methods
418 *
419 * @param string $method_name The method name
420 * @param array $arguments Array of arguments
421 */
422 public function __call( $method_name, $arguments ) {
423 $orders_autosuggest_methods = [
424 'after_update_feature',
425 'check_token_permission',
426 'enqueue_admin_assets',
427 'epio_delete_search_template',
428 'epio_get_search_template',
429 'epio_save_search_template',
430 'filter_term_suggest',
431 'get_args_schema',
432 'get_search_endpoint',
433 'get_search_template',
434 'get_template_endpoint',
435 'get_token',
436 'get_token_endpoint',
437 'intercept_search_request',
438 'is_integrated_request',
439 'post_statuses',
440 'post_types',
441 'mapping',
442 'maybe_query_password_protected_posts',
443 'maybe_set_posts_where',
444 'refresh_token',
445 'rest_api_init',
446 'set_search_fields',
447 ];
448
449 if ( in_array( $method_name, $orders_autosuggest_methods, true ) ) {
450 _deprecated_function(
451 "\ElasticPress\Feature\WooCommerce\WooCommerce\Orders::{$method_name}", // phpcs:ignore
452 '4.7.0',
453 "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders_autosuggest->{$method_name}()" // phpcs:ignore
454 );
455
456 if ( $this->woocommerce->is_orders_autosuggest_enabled() && method_exists( $this->woocommerce->orders_autosuggest, $method_name ) ) {
457 call_user_func_array( [ $this->woocommerce->orders_autosuggest, $method_name ], $arguments );
458 }
459 }
460 }
461 }
462