PluginProbe
ElasticPress / 4.7.2
ElasticPress v4.7.2
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.2, at includes/classes/Feature/WooCommerce/Orders.php

466 lines 13.3 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 $order = wc_get_order( $post_id );
138 if ( ! $order ) {
139 return $post_args;
140 }
141
142 $searchable_post_types = $this->get_admin_searchable_post_types();
143
144 // Make sure it is only WooCommerce orders we touch.
145 if ( ! in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
146 return $post_args;
147 }
148
149 $post_indexable = Indexables::factory()->get( 'post' );
150
151 // Get order items.
152 $item_meta = [];
153 foreach ( $order->get_items() as $delta => $product_item ) {
154 // WooCommerce 3.x uses WC_Order_Item_Product instance while 2.x an array
155 if ( is_object( $product_item ) && method_exists( $product_item, 'get_name' ) ) {
156 $item_meta['_items'][] = $product_item->get_name( 'edit' );
157 } elseif ( is_array( $product_item ) && isset( $product_item['name'] ) ) {
158 $item_meta['_items'][] = $product_item['name'];
159 }
160 }
161
162 // Prepare order items.
163 $item_meta['_items'] = empty( $item_meta['_items'] ) ? '' : implode( '|', $item_meta['_items'] );
164 $post_args['meta'] = array_merge( $post_args['meta'], $post_indexable->prepare_meta_types( $item_meta ) );
165
166 return $post_args;
167 }
168
169 /**
170 * Prevent order fields from being removed.
171 *
172 * When Protected Content is enabled, all posts with password have their content removed.
173 * This can't happen for orders, as the order key is added in that field.
174 *
175 * @see https://github.com/10up/ElasticPress/issues/2726
176 *
177 * @param bool $skip Whether the password protected content should have their content, and meta removed
178 * @param array $post_args Post arguments
179 * @return bool
180 */
181 public function keep_order_fields( $skip, $post_args ) {
182 $searchable_post_types = $this->get_admin_searchable_post_types();
183
184 if ( in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
185 return true;
186 }
187
188 return $skip;
189 }
190
191 /**
192 * Sets WooCommerce meta search fields to an empty array if we are integrating the main query with ElasticSearch
193 *
194 * WooCommerce calls this action as part of its own callback on parse_query. We add this filter only if the query
195 * is integrated with ElasticSearch.
196 * If we were to always return array() on this filter, we'd break admin searches when WooCommerce module is activated
197 * without the Protected Content Module
198 *
199 * @param \WP_Query $query Current query
200 */
201 public function maybe_hook_woocommerce_search_fields( $query ) {
202 global $pagenow, $wp, $wc_list_table;
203
204 if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
205 return;
206 }
207
208 /**
209 * Determines actions to be applied, or removed, if doing a WooCommerce serarch
210 *
211 * @hook ep_woocommerce_hook_search_fields
212 * @since 4.4.0
213 */
214 do_action( 'ep_woocommerce_hook_search_fields' );
215
216 if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
217 return;
218 }
219
220 remove_action( 'parse_query', [ $wc_list_table, 'search_custom_fields' ] );
221 }
222
223 /**
224 * Enhance WooCommerce search order by order id, email, phone number, name, etc..
225 * What this function does:
226 * 1. Reverse the woocommerce shop_order_search_custom_fields query
227 * 2. If the search key is integer and it is an Order Id, just query with post__in
228 * 3. If the search key is integer but not an order id ( might be phone number ), use ES to find it
229 *
230 * @param WP_Query $wp WP Query
231 */
232 public function search_order( $wp ) {
233 global $pagenow;
234
235 if ( ! $this->woocommerce->should_integrate_with_query( $wp ) ) {
236 return;
237 }
238
239 $searchable_post_types = $this->get_admin_searchable_post_types();
240
241 if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['post_type'] ) || ! in_array( $wp->query_vars['post_type'], $searchable_post_types, true ) ||
242 ( empty( $wp->query_vars['s'] ) && empty( $wp->query_vars['shop_order_search'] ) ) ) {
243 return;
244 }
245
246 // phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
247 if ( isset( $_GET['s'] ) ) {
248 $search_key_safe = str_replace( array( 'Order #', '#' ), '', wc_clean( $_GET['s'] ) );
249 unset( $wp->query_vars['post__in'] );
250 $wp->query_vars['s'] = $search_key_safe;
251 }
252 // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
253 }
254
255 /**
256 * Determines whether or not ES should be integrating with the provided query
257 *
258 * @param \WP_Query $query Query we might integrate with
259 * @return bool
260 */
261 public function should_integrate_with_query( \WP_Query $query ) : bool {
262 /**
263 * Check the post type
264 */
265 $supported_post_types = $this->get_supported_post_types( $query );
266 $post_type = $query->get( 'post_type', false );
267 if ( ! empty( $post_type ) &&
268 ( in_array( $post_type, $supported_post_types, true ) ||
269 ( is_array( $post_type ) && ! array_diff( $post_type, $supported_post_types ) ) )
270 ) {
271 return true;
272 }
273
274 return false;
275 }
276
277 /**
278 * Get the supported post types for Order related queries
279 *
280 * @return array
281 */
282 public function get_supported_post_types() : array {
283 $post_types = [ 'shop_order', 'shop_order_refund' ];
284
285 /**
286 * DEPRECATED. Expands or contracts the post_types eligible for indexing.
287 *
288 * @hook ep_woocommerce_default_supported_post_types
289 * @since 4.4.0
290 * @param {array} $post_types Post types
291 * @return {array} New post types
292 */
293 $supported_post_types = apply_filters_deprecated(
294 'ep_woocommerce_default_supported_post_types',
295 [ $post_types ],
296 '4.7.0',
297 'ep_woocommerce_orders_supported_post_types'
298 );
299
300 /**
301 * Expands or contracts the post_types related to orders eligible for indexing.
302 *
303 * @hook ep_woocommerce_orders_supported_post_types
304 * @since 4.7.0
305 * @param {array} $supported_post_types Post types
306 * @return {array} New post types
307 */
308 $supported_post_types = apply_filters( 'ep_woocommerce_orders_supported_post_types', $supported_post_types );
309
310 $supported_post_types = array_intersect(
311 $supported_post_types,
312 Indexables::factory()->get( 'post' )->get_indexable_post_types()
313 );
314
315 return $supported_post_types;
316 }
317
318 /**
319 * If the query has a search term, add the order fields that need to be searched.
320 *
321 * @param \WP_Query $query The WP_Query
322 * @return \WP_Query
323 */
324 protected function maybe_set_search_fields( \WP_Query $query ) {
325 $search_term = $this->woocommerce->get_search_term( $query );
326 if ( empty( $search_term ) ) {
327 return $query;
328 }
329
330 $searchable_post_types = $this->get_admin_searchable_post_types();
331
332 $post_type = $query->get( 'post_type', false );
333 if ( ! in_array( $post_type, $searchable_post_types, true ) ) {
334 return $query;
335 }
336
337 $default_search_fields = array( 'post_title', 'post_content', 'post_excerpt' );
338 if ( ctype_digit( $search_term ) ) {
339 $default_search_fields[] = 'ID';
340 }
341 $search_fields = $query->get( 'search_fields', $default_search_fields );
342
343 $search_fields['meta'] = array_map(
344 'wc_clean',
345 /**
346 * Filter shop order meta fields to search for WooCommerce
347 *
348 * @hook shop_order_search_fields
349 * @param {array} $fields Shop order fields
350 * @return {array} New fields
351 */
352 apply_filters(
353 'shop_order_search_fields',
354 array(
355 '_order_key',
356 '_billing_company',
357 '_billing_address_1',
358 '_billing_address_2',
359 '_billing_city',
360 '_billing_postcode',
361 '_billing_country',
362 '_billing_state',
363 '_billing_email',
364 '_billing_phone',
365 '_shipping_address_1',
366 '_shipping_address_2',
367 '_shipping_city',
368 '_shipping_postcode',
369 '_shipping_country',
370 '_shipping_state',
371 '_billing_last_name',
372 '_billing_first_name',
373 '_shipping_first_name',
374 '_shipping_last_name',
375 '_items',
376 )
377 )
378 );
379
380 $query->set(
381 'search_fields',
382 /**
383 * Filter all the shop order fields to search for WooCommerce
384 *
385 * @hook ep_woocommerce_shop_order_search_fields
386 * @since 4.0.0
387 * @param {array} $fields Shop order fields
388 * @param {WP_Query} $query WP Query
389 * @return {array} New fields
390 */
391 apply_filters( 'ep_woocommerce_shop_order_search_fields', $search_fields, $query )
392 );
393 }
394
395 /**
396 * Translate args to ElasticPress compat format. This is the meat of what the feature does
397 *
398 * @param \WP_Query $query WP Query
399 */
400 public function translate_args( $query ) {
401 if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
402 return;
403 }
404
405 if ( ! $this->should_integrate_with_query( $query ) ) {
406 return;
407 }
408
409 $query->set( 'ep_integrate', true );
410
411 /**
412 * Make sure filters are suppressed
413 */
414 $query->query['suppress_filters'] = false;
415 $query->set( 'suppress_filters', false );
416
417 $this->maybe_set_search_fields( $query );
418 }
419
420 /**
421 * Handle calls to OrdersAutosuggest methods
422 *
423 * @param string $method_name The method name
424 * @param array $arguments Array of arguments
425 */
426 public function __call( $method_name, $arguments ) {
427 $orders_autosuggest_methods = [
428 'after_update_feature',
429 'check_token_permission',
430 'enqueue_admin_assets',
431 'epio_delete_search_template',
432 'epio_get_search_template',
433 'epio_save_search_template',
434 'filter_term_suggest',
435 'get_args_schema',
436 'get_search_endpoint',
437 'get_search_template',
438 'get_template_endpoint',
439 'get_token',
440 'get_token_endpoint',
441 'intercept_search_request',
442 'is_integrated_request',
443 'post_statuses',
444 'post_types',
445 'mapping',
446 'maybe_query_password_protected_posts',
447 'maybe_set_posts_where',
448 'refresh_token',
449 'rest_api_init',
450 'set_search_fields',
451 ];
452
453 if ( in_array( $method_name, $orders_autosuggest_methods, true ) ) {
454 _deprecated_function(
455 "\ElasticPress\Feature\WooCommerce\WooCommerce\Orders::{$method_name}", // phpcs:ignore
456 '4.7.0',
457 "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders_autosuggest->{$method_name}()" // phpcs:ignore
458 );
459
460 if ( $this->woocommerce->is_orders_autosuggest_enabled() && method_exists( $this->woocommerce->orders_autosuggest, $method_name ) ) {
461 call_user_func_array( [ $this->woocommerce->orders_autosuggest, $method_name ], $arguments );
462 }
463 }
464 }
465 }
466