diff --git a/includes/classes/Feature/WooCommerce/Orders.php b/includes/classes/Feature/WooCommerce/Orders.php
index 9fb7ba10ff..8dd4da7928 100644
--- a/includes/classes/Feature/WooCommerce/Orders.php
+++ b/includes/classes/Feature/WooCommerce/Orders.php
@@ -1,612 +1,461 @@
index = Indexables::factory()->get( 'post' )->get_index_name();
+ public function __construct( WooCommerce $woocommerce ) {
+ $this->woocommerce = $woocommerce;
}
/**
- * Setup feature functionality.
- *
- * @return void
+ * Setup order related hooks
*/
public function setup() {
- add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
- add_filter( 'ep_after_update_feature', [ $this, 'after_update_feature' ], 10, 3 );
- add_filter( 'ep_after_sync_index', [ $this, 'epio_save_search_template' ] );
- add_filter( 'ep_saved_weighting_configuration', [ $this, 'epio_save_search_template' ] );
- add_filter( 'ep_indexable_post_status', [ $this, 'post_statuses' ] );
- add_filter( 'ep_indexable_post_types', [ $this, 'post_types' ] );
- add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
- add_filter( 'ep_post_sync_args', [ $this, 'filter_term_suggest' ], 10 );
- add_filter( 'ep_post_mapping', [ $this, 'mapping' ] );
- add_action( 'ep_woocommerce_shop_order_search_fields', [ $this, 'set_search_fields' ], 10, 2 );
- add_filter( 'ep_index_posts_args', [ $this, 'maybe_query_password_protected_posts' ] );
- add_filter( 'posts_where', [ $this, 'maybe_set_posts_where' ], 10, 2 );
+ add_filter( 'ep_sync_insert_permissions_bypass', [ $this, 'bypass_order_permissions_check' ], 10, 2 );
+ add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'allow_meta_keys' ] );
+ add_filter( 'ep_post_sync_args_post_prepare_meta', [ $this, 'add_order_items_search' ], 20, 2 );
+ add_filter( 'ep_pc_skip_post_content_cleanup', [ $this, 'keep_order_fields' ], 20, 2 );
+ add_action( 'parse_query', [ $this, 'maybe_hook_woocommerce_search_fields' ], 1 );
+ add_action( 'parse_query', [ $this, 'search_order' ], 11 );
+ add_action( 'pre_get_posts', [ $this, 'translate_args' ], 11, 1 );
}
/**
- * Get the endpoint for WooCommerce Orders search.
+ * Allow order creations on the front end to get synced
*
- * @return string WooCommerce orders search endpoint.
+ * @param bool $override Original order perms check value
+ * @param int $post_id Post ID
+ * @return bool
*/
- public function get_search_endpoint() {
- /**
- * Filters the WooCommerce Orders search endpoint.
- *
- * @since 4.5.0
- * @hook ep_woocommerce_order_search_endpoint
- * @param {string} $endpoint Endpoint path.
- * @param {string} $index Elasticsearch index.
- */
- return apply_filters( 'ep_woocommerce_order_search_endpoint', "api/v1/search/orders/{$this->index}", $this->index );
- }
+ public function bypass_order_permissions_check( $override, $post_id ) {
+ $searchable_post_types = $this->get_admin_searchable_post_types();
- /**
- * Get the endpoint for the WooCommerce Orders search template.
- *
- * @return string WooCommerce Orders search template endpoint.
- */
- public function get_template_endpoint() {
- /**
- * Filters the WooCommerce Orders search template API endpoint.
- *
- * @since 4.5.0
- * @hook ep_woocommerce_order_search_template_endpoint
- * @param {string} $endpoint Endpoint path.
- * @param {string} $index Elasticsearch index.
- * @returns {string} Search template API endpoint.
- */
- return apply_filters( 'ep_woocommerce_order_search_template_endpoint', "api/v1/search/orders/{$this->index}/template", $this->index );
+ if ( in_array( get_post_type( $post_id ), $searchable_post_types, true ) ) {
+ return true;
+ }
+
+ return $override;
}
/**
- * Get the endpoint for temporary tokens.
+ * Returns the WooCommerce-oriented post types in admin that EP will search
*
- * @return string Temporary token endpoint.
+ * @return array
*/
- public function get_token_endpoint() {
+ public function get_admin_searchable_post_types() {
+ $searchable_post_types = array( 'shop_order' );
+
/**
- * Filters the temporary token API endpoint.
+ * Filter admin searchable WooCommerce post types
*
- * @since 4.5.0
- * @hook ep_token_endpoint
- * @param {string} $endpoint Endpoint path.
- * @returns {string} Token API endpoint.
+ * @hook ep_woocommerce_admin_searchable_post_types
+ * @since 4.4.0
+ * @param {array} $post_types Post types
+ * @return {array} New post types
*/
- return apply_filters( 'ep_token_endpoint', 'api/v1/token' );
- }
-
- /**
- * Registers the API endpoint to get a token.
- *
- * @return void
- */
- public function rest_api_init() {
- register_rest_route(
- 'elasticpress/v1',
- 'token',
- [
- [
- 'callback' => [ $this, 'get_token' ],
- 'permission_callback' => [ $this, 'check_token_permission' ],
- 'methods' => 'GET',
- ],
- [
- 'callback' => [ $this, 'refresh_token' ],
- 'permission_callback' => [ $this, 'check_token_permission' ],
- 'methods' => 'POST',
- ],
- ]
- );
+ return apply_filters( 'ep_woocommerce_admin_searchable_post_types', $searchable_post_types );
}
/**
- * Enqueue admin assets.
+ * Index WooCommerce orders meta fields
*
- * @param string $hook_suffix The current admin page.
+ * @param array $meta Existing post meta
+ * @return array
*/
- public function enqueue_admin_assets( $hook_suffix ) {
- if ( 'edit.php' !== $hook_suffix ) {
- return;
- }
-
- if ( ! isset( $_GET['post_type'] ) || 'shop_order' !== $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- return;
- }
-
- wp_enqueue_style(
- 'elasticpress-woocommerce-order-search',
- EP_URL . 'dist/css/woocommerce-order-search-styles.css',
- Utils\get_asset_info( 'woocommerce-order-search-styles', 'dependencies' ),
- Utils\get_asset_info( 'woocommerce-order-search-styles', 'version' )
- );
-
- wp_enqueue_script(
- 'elasticpress-woocommerce-order-search',
- EP_URL . 'dist/js/woocommerce-order-search-script.js',
- Utils\get_asset_info( 'woocommerce-order-search-script', 'dependencies' ),
- Utils\get_asset_info( 'woocommerce-order-search-script', 'version' ),
- true
- );
-
- wp_set_script_translations( 'elasticpress-woocommerce-order-search', 'elasticpress' );
-
- $api_endpoint = $this->get_search_endpoint();
- $api_host = Utils\get_host();
-
- wp_localize_script(
- 'elasticpress-woocommerce-order-search',
- 'epWooCommerceOrderSearch',
- array(
- 'adminUrl' => admin_url( 'post.php' ),
- 'apiEndpoint' => $api_endpoint,
- 'apiHost' => ( 0 !== strpos( $api_endpoint, 'http' ) ) ? trailingslashit( esc_url_raw( $api_host ) ) : '',
- 'argsSchema' => $this->get_args_schema(),
- 'credentialsApiUrl' => rest_url( 'elasticpress/v1/token' ),
- 'credentialsNonce' => wp_create_nonce( 'wp_rest' ),
- 'dateFormat' => wc_date_format(),
- 'statusLabels' => wc_get_order_statuses(),
- 'timeFormat' => wc_time_format(),
- 'requestIdBase' => Utils\get_request_id_base(),
+ public function allow_meta_keys( $meta ) {
+ return array_unique(
+ array_merge(
+ $meta,
+ array(
+ '_customer_user',
+ '_order_key',
+ '_billing_company',
+ '_billing_address_1',
+ '_billing_address_2',
+ '_billing_city',
+ '_billing_postcode',
+ '_billing_country',
+ '_billing_state',
+ '_billing_email',
+ '_billing_phone',
+ '_shipping_address_1',
+ '_shipping_address_2',
+ '_shipping_city',
+ '_shipping_postcode',
+ '_shipping_country',
+ '_shipping_state',
+ '_billing_last_name',
+ '_billing_first_name',
+ '_shipping_first_name',
+ '_shipping_last_name',
+ '_variations_skus',
+ )
)
);
}
/**
- * Save or delete the search template on ElasticPress.io based on whether
- * the WooCommerce feature is being activated or deactivated.
- *
- * @param string $feature Feature slug
- * @param array $settings Feature settings
- * @param array $data Feature activation data
+ * Add order items as a searchable string.
*
- * @return void
- */
- public function after_update_feature( $feature, $settings, $data ) {
- if ( 'woocommerce' !== $feature ) {
- return;
- }
-
- if ( true === $data['active'] ) {
- $this->epio_save_search_template();
- } else {
- $this->epio_delete_search_template();
- }
- }
-
- /**
- * Save the search template to ElasticPress.io.
+ * This mimics how WooCommerce currently does in the order_itemmeta
+ * table. They combine the titles of the products and put them in a
+ * meta field called "Items".
*
- * @return void
- */
- public function epio_save_search_template() {
- $endpoint = $this->get_template_endpoint();
- $template = $this->get_search_template();
-
- Elasticsearch::factory()->remote_request(
- $endpoint,
- [
- 'blocking' => false,
- 'body' => $template,
- 'method' => 'PUT',
- ]
- );
-
- /**
- * Fires after the request is sent the search template API endpoint.
- *
- * @since 4.5.0
- * @hook ep_woocommerce_order_search_template_saved
- * @param {string} $template The search template (JSON).
- * @param {string} $index Index name.
- */
- do_action( 'ep_woocommerce_order_search_template_saved', $template, $this->index );
- }
-
- /**
- * Delete the search template from ElasticPress.io.
+ * @param array $post_args Post arguments
+ * @param string|int $post_id Post id
*
- * @return void
+ * @return array
*/
- public function epio_delete_search_template() {
- $endpoint = $this->get_template_endpoint();
-
- Elasticsearch::factory()->remote_request(
- $endpoint,
- [
- 'blocking' => false,
- 'method' => 'DELETE',
- ]
- );
+ public function add_order_items_search( $post_args, $post_id ) {
+ $searchable_post_types = $this->get_admin_searchable_post_types();
- /**
- * Fires after the request is sent the search template API endpoint.
- *
- * @since 4.5.0
- * @hook ep_woocommerce_order_search_template_deleted
- * @param {string} $index Index name.
- */
- do_action( 'ep_woocommerce_order_search_template_deleted', $this->index );
- }
+ // Make sure it is only WooCommerce orders we touch.
+ if ( ! in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
+ return $post_args;
+ }
- /**
- * Get the saved search template from ElasticPress.io.
- *
- * @return string|WP_Error Search template if found, WP_Error on error.
- */
- public function epio_get_search_template() {
- $endpoint = $this->get_template_endpoint();
- $request = Elasticsearch::factory()->remote_request( $endpoint );
+ $post_indexable = Indexables::factory()->get( 'post' );
- if ( is_wp_error( $request ) ) {
- return $request;
+ // Get order items.
+ $order = wc_get_order( $post_id );
+ $item_meta = [];
+ foreach ( $order->get_items() as $delta => $product_item ) {
+ // WooCommerce 3.x uses WC_Order_Item_Product instance while 2.x an array
+ if ( is_object( $product_item ) && method_exists( $product_item, 'get_name' ) ) {
+ $item_meta['_items'][] = $product_item->get_name( 'edit' );
+ } elseif ( is_array( $product_item ) && isset( $product_item['name'] ) ) {
+ $item_meta['_items'][] = $product_item['name'];
+ }
}
- $response = wp_remote_retrieve_body( $request );
+ // Prepare order items.
+ $item_meta['_items'] = empty( $item_meta['_items'] ) ? '' : implode( '|', $item_meta['_items'] );
+ $post_args['meta'] = array_merge( $post_args['meta'], $post_indexable->prepare_meta_types( $item_meta ) );
- return $response;
+ return $post_args;
}
/**
- * Generate a search template.
+ * Prevent order fields from being removed.
*
- * A search template is the JSON for an Elasticsearch query with a
- * placeholder search term. The template is sent to ElasticPress.io where
- * it's used to make Elasticsearch queries using search terms sent from
- * the front end.
- *
- * @return string The search template as JSON.
- */
- public function get_search_template() {
- $order_statuses = wc_get_order_statuses();
-
- add_filter( 'ep_bypass_exclusion_from_search', '__return_true', 10 );
- add_filter( 'ep_intercept_remote_request', '__return_true' );
- add_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10, 4 );
- add_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10, 2 );
-
- $query = new \WP_Query(
- array(
- 'ep_integrate' => true,
- 'ep_order_search_template' => true,
- 'post_status' => array_keys( $order_statuses ),
- 'post_type' => 'shop_order',
- 's' => '{{ep_placeholder}}',
- )
- );
-
- remove_filter( 'ep_bypass_exclusion_from_search', '__return_true', 10 );
- remove_filter( 'ep_intercept_remote_request', '__return_true' );
- remove_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10 );
- remove_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10 );
-
- return $this->search_template;
- }
-
- /**
- * Return true if a given feature is supported by WooCommerce Orders.
+ * When Protected Content is enabled, all posts with password have their content removed.
+ * This can't happen for orders, as the order key is added in that field.
*
- * Applied as a filter on Utils\is_integrated_request() so that features
- * are enabled for the query that is used to generate the search template,
- * regardless of the request type. This avoids the need to send a request
- * to the front end.
+ * @see https://github.com/10up/ElasticPress/issues/2726
*
- * @param bool $is_integrated Whether queries for the request will be
- * integrated.
- * @param string $context Context for the original check. Usually the
- * slug of the feature doing the check.
- * @return bool True if the check is for a feature supported by WooCommerce
- * Order search.
+ * @param bool $skip Whether the password protected content should have their content, and meta removed
+ * @param array $post_args Post arguments
+ * @return bool
*/
- public function is_integrated_request( $is_integrated, $context ) {
- $supported_contexts = [
- 'search',
- 'woocommerce',
- ];
+ public function keep_order_fields( $skip, $post_args ) {
+ $searchable_post_types = $this->get_admin_searchable_post_types();
- return in_array( $context, $supported_contexts, true );
- }
-
- /**
- * Store intercepted request body and return request result.
- *
- * @param object $response Response
- * @param array $query Query
- * @param array $args WP_Query argument array
- * @param int $failures Count of failures in request loop
- * @return object $response Response
- */
- public function intercept_search_request( $response, $query = [], $args = [], $failures = 0 ) {
- $this->search_template = $query['args']['body'];
+ if ( in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
+ return true;
+ }
- return wp_remote_request( $query['url'], $args );
+ return $skip;
}
/**
- * Get schema for search args.
+ * Sets woocommerce meta search fields to an empty array if we are integrating the main query with ElasticSearch
*
- * @return array Search args schema.
- */
- public function get_args_schema() {
- $args = array(
- 'customer' => array(
- 'type' => 'number',
- ),
- 'm' => array(
- 'type' => 'string',
- ),
- 'offset' => array(
- 'type' => 'number',
- 'default' => 0,
- ),
- 'per_page' => array(
- 'type' => 'number',
- 'default' => 6,
- ),
- 'search' => array(
- 'type' => 'string',
- 'default' => '',
- ),
- );
-
- return $args;
- }
-
- /**
- * Get a temporary token.
+ * Woocommerce calls this action as part of its own callback on parse_query. We add this filter only if the query
+ * is integrated with ElasticSearch.
+ * If we were to always return array() on this filter, we'd break admin searches when WooCommerce module is activated
+ * without the Protected Content Module
*
- * @return string|false Authorization header, or false on failure.
+ * @param \WP_Query $query Current query
*/
- public function get_token() {
- $user_id = get_current_user_id();
+ public function maybe_hook_woocommerce_search_fields( $query ) {
+ global $pagenow, $wp, $wc_list_table;
- $credentials = get_user_meta( $user_id, 'ep_token', true );
+ if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
+ return;
+ }
- if ( $credentials ) {
- return $credentials;
+ /**
+ * Determines actions to be applied, or removed, if doing a WooCommerce serarch
+ *
+ * @hook ep_woocommerce_hook_search_fields
+ * @since 4.4.0
+ */
+ do_action( 'ep_woocommerce_hook_search_fields' );
+
+ if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
+ return;
}
- return $this->refresh_token();
+ remove_action( 'parse_query', [ $wc_list_table, 'search_custom_fields' ] );
}
/**
- * Refresh the temporary token.
+ * Enhance WooCommerce search order by order id, email, phone number, name, etc..
+ * What this function does:
+ * 1. Reverse the woocommerce shop_order_search_custom_fields query
+ * 2. If the search key is integer and it is an Order Id, just query with post__in
+ * 3. If the search key is integer but not an order id ( might be phone number ), use ES to find it
*
- * @return string|false Authorization header, or false on failure.
+ * @param WP_Query $wp WP Query
*/
- public function refresh_token() {
- $user_id = get_current_user_id();
+ public function search_order( $wp ) {
+ global $pagenow;
- $endpoint = $this->get_token_endpoint();
- $response = Elasticsearch::factory()->remote_request( $endpoint, [ 'method' => 'POST' ] );
-
- if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
- return false;
+ if ( ! $this->woocommerce->should_integrate_with_query( $wp ) ) {
+ return;
}
- $response = wp_remote_retrieve_body( $response );
- $response = json_decode( $response );
+ $searchable_post_types = $this->get_admin_searchable_post_types();
- $credentials = base64_encode( "$response->username:$response->clear_password" ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
-
- update_user_meta( $user_id, 'ep_token', $credentials );
+ if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['post_type'] ) || ! in_array( $wp->query_vars['post_type'], $searchable_post_types, true ) ||
+ ( empty( $wp->query_vars['s'] ) && empty( $wp->query_vars['shop_order_search'] ) ) ) {
+ return;
+ }
- return $credentials;
+ // phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
+ if ( isset( $_GET['s'] ) ) {
+ $search_key_safe = str_replace( array( 'Order #', '#' ), '', wc_clean( $_GET['s'] ) );
+ unset( $wp->query_vars['post__in'] );
+ $wp->query_vars['s'] = $search_key_safe;
+ }
+ // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
}
/**
- * Checks if the token API can be used.
+ * Determines whether or not ES should be integrating with the provided query
*
- * @return boolean Whether the token API can be used.
+ * @param \WP_Query $query Query we might integrate with
+ * @return bool
*/
- public function check_token_permission() {
+ public function should_integrate_with_query( \WP_Query $query ) : bool {
/**
- * Filters the capability required to use the token API.
- *
- * @since 4.5.0
- * @hook ep_token_capability
- * @param {string} $capability Required capability.
+ * Check the post type
*/
- $capability = apply_filters( 'ep_token_capability', 'edit_others_shop_orders' );
-
- return current_user_can( $capability );
- }
-
- /**
- * Index shop orders.
- *
- * @param array $post_types Indexable post types.
- * @return array Indexable post types.
- */
- public function post_types( $post_types ) {
- $post_types['shop_order'] = 'shop_order';
-
- return $post_types;
- }
-
- /**
- * Index order statuses.
- *
- * @param array $post_statuses Indexable post statuses.
- * @return array Indexable post statuses.
- */
- public function post_statuses( $post_statuses ) {
- $order_statuses = wc_get_order_statuses();
+ $supported_post_types = $this->get_supported_post_types( $query );
+ $post_type = $query->get( 'post_type', false );
+ if ( ! empty( $post_type ) &&
+ ( in_array( $post_type, $supported_post_types, true ) ||
+ ( is_array( $post_type ) && ! array_diff( $post_type, $supported_post_types ) ) )
+ ) {
+ return true;
+ }
- return array_unique( array_merge( $post_statuses, array_keys( $order_statuses ) ) );
+ return false;
}
/**
- * Add term suggestions to be indexed
+ * Get the supported post types for Order related queries
*
- * @param array $post_args Array of ES args.
* @return array
*/
- public function filter_term_suggest( $post_args ) {
- if ( empty( $post_args['post_type'] ) || 'shop_order' !== $post_args['post_type'] ) {
- return $post_args;
- }
-
- if ( empty( $post_args['meta'] ) ) {
- return $post_args;
- }
+ public function get_supported_post_types() : array {
+ $post_types = [ 'shop_order', 'shop_order_refund' ];
/**
- * Add the order number as a meta (text) field, so we can freely search on it.
+ * DEPRECATED. Expands or contracts the post_types eligible for indexing.
+ *
+ * @hook ep_woocommerce_default_supported_post_types
+ * @since 4.4.0
+ * @param {array} $post_types Post types
+ * @return {array} New post types
*/
- $order_id = $post_args['ID'];
- if ( function_exists( 'wc_get_order' ) ) {
- $order = wc_get_order( $post_args['ID'] );
- if ( $order && is_a( $order, 'WC_Order' ) && method_exists( $order, 'get_order_number' ) ) {
- $order_id = $order->get_order_number();
- }
- }
-
- $post_args['meta']['order_number'] = [
- [
- 'raw' => $order_id,
- 'value' => $order_id,
- ],
- ];
-
- $suggest = [];
-
- $fields_to_ngram = [
- '_billing_email',
- '_billing_last_name',
- '_billing_first_name',
- ];
+ $supported_post_types = apply_filters_deprecated(
+ 'ep_woocommerce_default_supported_post_types',
+ [ $post_types ],
+ '4.7.0',
+ 'ep_woocommerce_orders_supported_post_types'
+ );
- foreach ( $fields_to_ngram as $field_to_ngram ) {
- if ( ! empty( $post_args['meta'][ $field_to_ngram ] )
- && ! empty( $post_args['meta'][ $field_to_ngram ][0] )
- && ! empty( $post_args['meta'][ $field_to_ngram ][0]['value'] ) ) {
- $suggest[] = $post_args['meta'][ $field_to_ngram ][0]['value'];
- }
- }
+ /**
+ * Expands or contracts the post_types related to orders eligible for indexing.
+ *
+ * @hook ep_woocommerce_orders_supported_post_types
+ * @since 4.7.0
+ * @param {array} $post_types Post types
+ * @return {array} New post types
+ */
+ $supported_post_types = apply_filters( 'ep_woocommerce_orders_supported_post_types', $post_types );
- if ( ! empty( $suggest ) ) {
- $post_args['term_suggest'] = $suggest;
- }
+ $supported_post_types = array_intersect(
+ $supported_post_types,
+ Indexables::factory()->get( 'post' )->get_indexable_post_types()
+ );
- return $post_args;
+ return $supported_post_types;
}
/**
- * Add mapping for suggest fields
+ * If the query has a search term, add the order fields that need to be searched.
*
- * @param array $mapping ES mapping.
- * @return array
+ * @param \WP_Query $query The WP_Query
+ * @return \WP_Query
*/
- public function mapping( $mapping ) {
- $post_indexable = Indexables::factory()->get( 'post' );
+ protected function maybe_set_search_fields( \WP_Query $query ) {
+ $search_term = $this->woocommerce->get_search_term( $query );
+ if ( empty( $search_term ) ) {
+ return $query;
+ }
- $mapping = $post_indexable->add_ngram_analyzer( $mapping );
- $mapping = $post_indexable->add_term_suggest_field( $mapping );
+ $searchable_post_types = $this->get_admin_searchable_post_types();
- return $mapping;
- }
+ $post_type = $query->get( 'post_type', false );
+ if ( ! in_array( $post_type, $searchable_post_types, true ) ) {
+ return $query;
+ }
- /**
- * Set the search_fields parameter in the search template.
- *
- * @param array $search_fields Current search fields
- * @param \WP_Query $query Query being executed
- * @return array New search fields
- */
- public function set_search_fields( array $search_fields, \WP_Query $query ) : array {
- $is_orders_search_template = (bool) $query->get( 'ep_order_search_template' );
-
- if ( $is_orders_search_template ) {
- $search_fields = [
- 'meta.order_number.value',
- 'term_suggest',
- 'meta' => [
+ $default_search_fields = array( 'post_title', 'post_content', 'post_excerpt' );
+ if ( ctype_digit( $search_term ) ) {
+ $default_search_fields[] = 'ID';
+ }
+ $search_fields = $query->get( 'search_fields', $default_search_fields );
+
+ $search_fields['meta'] = array_map(
+ 'wc_clean',
+ /**
+ * Filter shop order meta fields to search for WooCommerce
+ *
+ * @hook shop_order_search_fields
+ * @param {array} $fields Shop order fields
+ * @return {array} New fields
+ */
+ apply_filters(
+ 'shop_order_search_fields',
+ array(
+ '_order_key',
+ '_billing_company',
+ '_billing_address_1',
+ '_billing_address_2',
+ '_billing_city',
+ '_billing_postcode',
+ '_billing_country',
+ '_billing_state',
'_billing_email',
+ '_billing_phone',
+ '_shipping_address_1',
+ '_shipping_address_2',
+ '_shipping_city',
+ '_shipping_postcode',
+ '_shipping_country',
+ '_shipping_state',
'_billing_last_name',
'_billing_first_name',
- ],
- ];
- }
+ '_shipping_first_name',
+ '_shipping_last_name',
+ '_items',
+ )
+ )
+ );
- return $search_fields;
+ $query->set(
+ 'search_fields',
+ /**
+ * Filter all the shop order fields to search for WooCommerce
+ *
+ * @hook ep_woocommerce_shop_order_search_fields
+ * @since 4.0.0
+ * @param {array} $fields Shop order fields
+ * @param {WP_Query} $query WP Query
+ * @return {array} New fields
+ */
+ apply_filters( 'ep_woocommerce_shop_order_search_fields', $search_fields, $query )
+ );
}
/**
- * Allow password protected to be indexed.
+ * Translate args to ElasticPress compat format. This is the meat of what the feature does
*
- * If Protected Content is enabled, do nothing. Otherwise, allow pw protected posts to be indexed.
- * The feature restricts it back in maybe_set_posts_where()
- *
- * @see maybe_set_posts_where()
- * @param array $args WP_Query args
- * @return array
+ * @param \WP_Query $query WP Query
*/
- public function maybe_query_password_protected_posts( $args ) {
- // Password protected posts are already being indexed, no need to do anything.
- if ( isset( $args['has_password'] ) && is_null( $args['has_password'] ) ) {
- return $args;
+ public function translate_args( $query ) {
+ if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
+ return;
}
+ if ( ! $this->should_integrate_with_query( $query ) ) {
+ return;
+ }
+
+ $query->set( 'ep_integrate', true );
+
/**
- * Set a flag in the query but allow it to index all password protected posts for now,
- * so WP does not inject its own where clause.
+ * Make sure filters are suppressed
*/
- $args['ep_orders_has_password'] = true;
- $args['has_password'] = null;
+ $query->query['suppress_filters'] = false;
+ $query->set( 'suppress_filters', false );
- return $args;
+ $this->maybe_set_search_fields( $query );
}
/**
- * Restrict password protected posts back but allow orders.
+ * Handle calls to OrdersAutosuggest methods
*
- * @see maybe_query_password_protected_posts
- * @param string $where Current where clause
- * @param WP_Query $query WP_Query
- * @return string
+ * @param string $method_name The method name
+ * @param array $arguments Array of arguments
*/
- public function maybe_set_posts_where( $where, $query ) {
- global $wpdb;
-
- if ( ! $query->get( 'ep_orders_has_password' ) ) {
- return $where;
- }
+ public function __call( $method_name, $arguments ) {
+ $orders_autosuggest_methods = [
+ 'after_update_feature',
+ 'check_token_permission',
+ 'enqueue_admin_assets',
+ 'epio_delete_search_template',
+ 'epio_get_search_template',
+ 'epio_save_search_template',
+ 'filter_term_suggest',
+ 'get_args_schema',
+ 'get_search_endpoint',
+ 'get_search_template',
+ 'get_template_endpoint',
+ 'get_token',
+ 'get_token_endpoint',
+ 'intercept_search_request',
+ 'is_integrated_request',
+ 'post_statuses',
+ 'post_types',
+ 'mapping',
+ 'maybe_query_password_protected_posts',
+ 'maybe_set_posts_where',
+ 'refresh_token',
+ 'rest_api_init',
+ 'set_search_fields',
+ ];
- $where .= " AND ( {$wpdb->posts}.post_password = '' OR {$wpdb->posts}.post_type = 'shop_order' )";
+ if ( in_array( $method_name, $orders_autosuggest_methods, true ) ) {
+ _deprecated_function(
+ "\ElasticPress\Feature\WooCommerce\WooCommerce\Orders::{$method_name}", // phpcs:ignore
+ '4.7.0',
+ "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders_autosuggest->{$method_name}()" // phpcs:ignore
+ );
- return $where;
+ if ( $this->woocommerce->is_orders_autosuggest_enabled() && method_exists( $this->woocommerce->orders_autosuggest, $method_name ) ) {
+ call_user_func_array( [ $this->woocommerce->orders_autosuggest, $method_name ], $arguments );
+ }
+ }
}
}
diff --git a/includes/classes/Feature/WooCommerce/OrdersAutosuggest.php b/includes/classes/Feature/WooCommerce/OrdersAutosuggest.php
new file mode 100644
index 0000000000..d2543e9362
--- /dev/null
+++ b/includes/classes/Feature/WooCommerce/OrdersAutosuggest.php
@@ -0,0 +1,612 @@
+index = Indexables::factory()->get( 'post' )->get_index_name();
+ }
+
+ /**
+ * Setup feature functionality.
+ *
+ * @return void
+ */
+ public function setup() {
+ add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
+ add_filter( 'ep_after_update_feature', [ $this, 'after_update_feature' ], 10, 3 );
+ add_filter( 'ep_after_sync_index', [ $this, 'epio_save_search_template' ] );
+ add_filter( 'ep_saved_weighting_configuration', [ $this, 'epio_save_search_template' ] );
+ add_filter( 'ep_indexable_post_status', [ $this, 'post_statuses' ] );
+ add_filter( 'ep_indexable_post_types', [ $this, 'post_types' ] );
+ add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
+ add_filter( 'ep_post_sync_args', [ $this, 'filter_term_suggest' ], 10 );
+ add_filter( 'ep_post_mapping', [ $this, 'mapping' ] );
+ add_action( 'ep_woocommerce_shop_order_search_fields', [ $this, 'set_search_fields' ], 10, 2 );
+ add_filter( 'ep_index_posts_args', [ $this, 'maybe_query_password_protected_posts' ] );
+ add_filter( 'posts_where', [ $this, 'maybe_set_posts_where' ], 10, 2 );
+ }
+
+ /**
+ * Get the endpoint for WooCommerce Orders search.
+ *
+ * @return string WooCommerce orders search endpoint.
+ */
+ public function get_search_endpoint() {
+ /**
+ * Filters the WooCommerce Orders search endpoint.
+ *
+ * @since 4.5.0
+ * @hook ep_woocommerce_order_search_endpoint
+ * @param {string} $endpoint Endpoint path.
+ * @param {string} $index Elasticsearch index.
+ */
+ return apply_filters( 'ep_woocommerce_order_search_endpoint', "api/v1/search/orders/{$this->index}", $this->index );
+ }
+
+ /**
+ * Get the endpoint for the WooCommerce Orders search template.
+ *
+ * @return string WooCommerce Orders search template endpoint.
+ */
+ public function get_template_endpoint() {
+ /**
+ * Filters the WooCommerce Orders search template API endpoint.
+ *
+ * @since 4.5.0
+ * @hook ep_woocommerce_order_search_template_endpoint
+ * @param {string} $endpoint Endpoint path.
+ * @param {string} $index Elasticsearch index.
+ * @returns {string} Search template API endpoint.
+ */
+ return apply_filters( 'ep_woocommerce_order_search_template_endpoint', "api/v1/search/orders/{$this->index}/template", $this->index );
+ }
+
+ /**
+ * Get the endpoint for temporary tokens.
+ *
+ * @return string Temporary token endpoint.
+ */
+ public function get_token_endpoint() {
+ /**
+ * Filters the temporary token API endpoint.
+ *
+ * @since 4.5.0
+ * @hook ep_token_endpoint
+ * @param {string} $endpoint Endpoint path.
+ * @returns {string} Token API endpoint.
+ */
+ return apply_filters( 'ep_token_endpoint', 'api/v1/token' );
+ }
+
+ /**
+ * Registers the API endpoint to get a token.
+ *
+ * @return void
+ */
+ public function rest_api_init() {
+ register_rest_route(
+ 'elasticpress/v1',
+ 'token',
+ [
+ [
+ 'callback' => [ $this, 'get_token' ],
+ 'permission_callback' => [ $this, 'check_token_permission' ],
+ 'methods' => 'GET',
+ ],
+ [
+ 'callback' => [ $this, 'refresh_token' ],
+ 'permission_callback' => [ $this, 'check_token_permission' ],
+ 'methods' => 'POST',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Enqueue admin assets.
+ *
+ * @param string $hook_suffix The current admin page.
+ */
+ public function enqueue_admin_assets( $hook_suffix ) {
+ if ( 'edit.php' !== $hook_suffix ) {
+ return;
+ }
+
+ if ( ! isset( $_GET['post_type'] ) || 'shop_order' !== $_GET['post_type'] ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ return;
+ }
+
+ wp_enqueue_style(
+ 'elasticpress-woocommerce-order-search',
+ EP_URL . 'dist/css/woocommerce-order-search-styles.css',
+ Utils\get_asset_info( 'woocommerce-order-search-styles', 'dependencies' ),
+ Utils\get_asset_info( 'woocommerce-order-search-styles', 'version' )
+ );
+
+ wp_enqueue_script(
+ 'elasticpress-woocommerce-order-search',
+ EP_URL . 'dist/js/woocommerce-order-search-script.js',
+ Utils\get_asset_info( 'woocommerce-order-search-script', 'dependencies' ),
+ Utils\get_asset_info( 'woocommerce-order-search-script', 'version' ),
+ true
+ );
+
+ wp_set_script_translations( 'elasticpress-woocommerce-order-search', 'elasticpress' );
+
+ $api_endpoint = $this->get_search_endpoint();
+ $api_host = Utils\get_host();
+
+ wp_localize_script(
+ 'elasticpress-woocommerce-order-search',
+ 'epWooCommerceOrderSearch',
+ array(
+ 'adminUrl' => admin_url( 'post.php' ),
+ 'apiEndpoint' => $api_endpoint,
+ 'apiHost' => ( 0 !== strpos( $api_endpoint, 'http' ) ) ? trailingslashit( esc_url_raw( $api_host ) ) : '',
+ 'argsSchema' => $this->get_args_schema(),
+ 'credentialsApiUrl' => rest_url( 'elasticpress/v1/token' ),
+ 'credentialsNonce' => wp_create_nonce( 'wp_rest' ),
+ 'dateFormat' => wc_date_format(),
+ 'statusLabels' => wc_get_order_statuses(),
+ 'timeFormat' => wc_time_format(),
+ 'requestIdBase' => Utils\get_request_id_base(),
+ )
+ );
+ }
+
+ /**
+ * Save or delete the search template on ElasticPress.io based on whether
+ * the WooCommerce feature is being activated or deactivated.
+ *
+ * @param string $feature Feature slug
+ * @param array $settings Feature settings
+ * @param array $data Feature activation data
+ *
+ * @return void
+ */
+ public function after_update_feature( $feature, $settings, $data ) {
+ if ( 'woocommerce' !== $feature ) {
+ return;
+ }
+
+ if ( true === $data['active'] ) {
+ $this->epio_save_search_template();
+ } else {
+ $this->epio_delete_search_template();
+ }
+ }
+
+ /**
+ * Save the search template to ElasticPress.io.
+ *
+ * @return void
+ */
+ public function epio_save_search_template() {
+ $endpoint = $this->get_template_endpoint();
+ $template = $this->get_search_template();
+
+ Elasticsearch::factory()->remote_request(
+ $endpoint,
+ [
+ 'blocking' => false,
+ 'body' => $template,
+ 'method' => 'PUT',
+ ]
+ );
+
+ /**
+ * Fires after the request is sent the search template API endpoint.
+ *
+ * @since 4.5.0
+ * @hook ep_woocommerce_order_search_template_saved
+ * @param {string} $template The search template (JSON).
+ * @param {string} $index Index name.
+ */
+ do_action( 'ep_woocommerce_order_search_template_saved', $template, $this->index );
+ }
+
+ /**
+ * Delete the search template from ElasticPress.io.
+ *
+ * @return void
+ */
+ public function epio_delete_search_template() {
+ $endpoint = $this->get_template_endpoint();
+
+ Elasticsearch::factory()->remote_request(
+ $endpoint,
+ [
+ 'blocking' => false,
+ 'method' => 'DELETE',
+ ]
+ );
+
+ /**
+ * Fires after the request is sent the search template API endpoint.
+ *
+ * @since 4.5.0
+ * @hook ep_woocommerce_order_search_template_deleted
+ * @param {string} $index Index name.
+ */
+ do_action( 'ep_woocommerce_order_search_template_deleted', $this->index );
+ }
+
+ /**
+ * Get the saved search template from ElasticPress.io.
+ *
+ * @return string|WP_Error Search template if found, WP_Error on error.
+ */
+ public function epio_get_search_template() {
+ $endpoint = $this->get_template_endpoint();
+ $request = Elasticsearch::factory()->remote_request( $endpoint );
+
+ if ( is_wp_error( $request ) ) {
+ return $request;
+ }
+
+ $response = wp_remote_retrieve_body( $request );
+
+ return $response;
+ }
+
+ /**
+ * Generate a search template.
+ *
+ * A search template is the JSON for an Elasticsearch query with a
+ * placeholder search term. The template is sent to ElasticPress.io where
+ * it's used to make Elasticsearch queries using search terms sent from
+ * the front end.
+ *
+ * @return string The search template as JSON.
+ */
+ public function get_search_template() {
+ $order_statuses = wc_get_order_statuses();
+
+ add_filter( 'ep_bypass_exclusion_from_search', '__return_true', 10 );
+ add_filter( 'ep_intercept_remote_request', '__return_true' );
+ add_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10, 4 );
+ add_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10, 2 );
+
+ $query = new \WP_Query(
+ array(
+ 'ep_integrate' => true,
+ 'ep_order_search_template' => true,
+ 'post_status' => array_keys( $order_statuses ),
+ 'post_type' => 'shop_order',
+ 's' => '{{ep_placeholder}}',
+ )
+ );
+
+ remove_filter( 'ep_bypass_exclusion_from_search', '__return_true', 10 );
+ remove_filter( 'ep_intercept_remote_request', '__return_true' );
+ remove_filter( 'ep_do_intercept_request', [ $this, 'intercept_search_request' ], 10 );
+ remove_filter( 'ep_is_integrated_request', [ $this, 'is_integrated_request' ], 10 );
+
+ return $this->search_template;
+ }
+
+ /**
+ * Return true if a given feature is supported by WooCommerce Orders.
+ *
+ * Applied as a filter on Utils\is_integrated_request() so that features
+ * are enabled for the query that is used to generate the search template,
+ * regardless of the request type. This avoids the need to send a request
+ * to the front end.
+ *
+ * @param bool $is_integrated Whether queries for the request will be
+ * integrated.
+ * @param string $context Context for the original check. Usually the
+ * slug of the feature doing the check.
+ * @return bool True if the check is for a feature supported by WooCommerce
+ * Order search.
+ */
+ public function is_integrated_request( $is_integrated, $context ) {
+ $supported_contexts = [
+ 'search',
+ 'woocommerce',
+ ];
+
+ return in_array( $context, $supported_contexts, true );
+ }
+
+ /**
+ * Store intercepted request body and return request result.
+ *
+ * @param object $response Response
+ * @param array $query Query
+ * @param array $args WP_Query argument array
+ * @param int $failures Count of failures in request loop
+ * @return object $response Response
+ */
+ public function intercept_search_request( $response, $query = [], $args = [], $failures = 0 ) {
+ $this->search_template = $query['args']['body'];
+
+ return wp_remote_request( $query['url'], $args );
+ }
+
+ /**
+ * Get schema for search args.
+ *
+ * @return array Search args schema.
+ */
+ public function get_args_schema() {
+ $args = array(
+ 'customer' => array(
+ 'type' => 'number',
+ ),
+ 'm' => array(
+ 'type' => 'string',
+ ),
+ 'offset' => array(
+ 'type' => 'number',
+ 'default' => 0,
+ ),
+ 'per_page' => array(
+ 'type' => 'number',
+ 'default' => 6,
+ ),
+ 'search' => array(
+ 'type' => 'string',
+ 'default' => '',
+ ),
+ );
+
+ return $args;
+ }
+
+ /**
+ * Get a temporary token.
+ *
+ * @return string|false Authorization header, or false on failure.
+ */
+ public function get_token() {
+ $user_id = get_current_user_id();
+
+ $credentials = get_user_meta( $user_id, 'ep_token', true );
+
+ if ( $credentials ) {
+ return $credentials;
+ }
+
+ return $this->refresh_token();
+ }
+
+ /**
+ * Refresh the temporary token.
+ *
+ * @return string|false Authorization header, or false on failure.
+ */
+ public function refresh_token() {
+ $user_id = get_current_user_id();
+
+ $endpoint = $this->get_token_endpoint();
+ $response = Elasticsearch::factory()->remote_request( $endpoint, [ 'method' => 'POST' ] );
+
+ if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) {
+ return false;
+ }
+
+ $response = wp_remote_retrieve_body( $response );
+ $response = json_decode( $response );
+
+ $credentials = base64_encode( "$response->username:$response->clear_password" ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
+
+ update_user_meta( $user_id, 'ep_token', $credentials );
+
+ return $credentials;
+ }
+
+ /**
+ * Checks if the token API can be used.
+ *
+ * @return boolean Whether the token API can be used.
+ */
+ public function check_token_permission() {
+ /**
+ * Filters the capability required to use the token API.
+ *
+ * @since 4.5.0
+ * @hook ep_token_capability
+ * @param {string} $capability Required capability.
+ */
+ $capability = apply_filters( 'ep_token_capability', 'edit_others_shop_orders' );
+
+ return current_user_can( $capability );
+ }
+
+ /**
+ * Index shop orders.
+ *
+ * @param array $post_types Indexable post types.
+ * @return array Indexable post types.
+ */
+ public function post_types( $post_types ) {
+ $post_types['shop_order'] = 'shop_order';
+
+ return $post_types;
+ }
+
+ /**
+ * Index order statuses.
+ *
+ * @param array $post_statuses Indexable post statuses.
+ * @return array Indexable post statuses.
+ */
+ public function post_statuses( $post_statuses ) {
+ $order_statuses = wc_get_order_statuses();
+
+ return array_unique( array_merge( $post_statuses, array_keys( $order_statuses ) ) );
+ }
+
+ /**
+ * Add term suggestions to be indexed
+ *
+ * @param array $post_args Array of ES args.
+ * @return array
+ */
+ public function filter_term_suggest( $post_args ) {
+ if ( empty( $post_args['post_type'] ) || 'shop_order' !== $post_args['post_type'] ) {
+ return $post_args;
+ }
+
+ if ( empty( $post_args['meta'] ) ) {
+ return $post_args;
+ }
+
+ /**
+ * Add the order number as a meta (text) field, so we can freely search on it.
+ */
+ $order_id = $post_args['ID'];
+ if ( function_exists( 'wc_get_order' ) ) {
+ $order = wc_get_order( $post_args['ID'] );
+ if ( $order && is_a( $order, 'WC_Order' ) && method_exists( $order, 'get_order_number' ) ) {
+ $order_id = $order->get_order_number();
+ }
+ }
+
+ $post_args['meta']['order_number'] = [
+ [
+ 'raw' => $order_id,
+ 'value' => $order_id,
+ ],
+ ];
+
+ $suggest = [];
+
+ $fields_to_ngram = [
+ '_billing_email',
+ '_billing_last_name',
+ '_billing_first_name',
+ ];
+
+ foreach ( $fields_to_ngram as $field_to_ngram ) {
+ if ( ! empty( $post_args['meta'][ $field_to_ngram ] )
+ && ! empty( $post_args['meta'][ $field_to_ngram ][0] )
+ && ! empty( $post_args['meta'][ $field_to_ngram ][0]['value'] ) ) {
+ $suggest[] = $post_args['meta'][ $field_to_ngram ][0]['value'];
+ }
+ }
+
+ if ( ! empty( $suggest ) ) {
+ $post_args['term_suggest'] = $suggest;
+ }
+
+ return $post_args;
+ }
+
+ /**
+ * Add mapping for suggest fields
+ *
+ * @param array $mapping ES mapping.
+ * @return array
+ */
+ public function mapping( $mapping ) {
+ $post_indexable = Indexables::factory()->get( 'post' );
+
+ $mapping = $post_indexable->add_ngram_analyzer( $mapping );
+ $mapping = $post_indexable->add_term_suggest_field( $mapping );
+
+ return $mapping;
+ }
+
+ /**
+ * Set the search_fields parameter in the search template.
+ *
+ * @param array $search_fields Current search fields
+ * @param \WP_Query $query Query being executed
+ * @return array New search fields
+ */
+ public function set_search_fields( array $search_fields, \WP_Query $query ) : array {
+ $is_orders_search_template = (bool) $query->get( 'ep_order_search_template' );
+
+ if ( $is_orders_search_template ) {
+ $search_fields = [
+ 'meta.order_number.value',
+ 'term_suggest',
+ 'meta' => [
+ '_billing_email',
+ '_billing_last_name',
+ '_billing_first_name',
+ ],
+ ];
+ }
+
+ return $search_fields;
+ }
+
+ /**
+ * Allow password protected to be indexed.
+ *
+ * If Protected Content is enabled, do nothing. Otherwise, allow pw protected posts to be indexed.
+ * The feature restricts it back in maybe_set_posts_where()
+ *
+ * @see maybe_set_posts_where()
+ * @param array $args WP_Query args
+ * @return array
+ */
+ public function maybe_query_password_protected_posts( $args ) {
+ // Password protected posts are already being indexed, no need to do anything.
+ if ( isset( $args['has_password'] ) && is_null( $args['has_password'] ) ) {
+ return $args;
+ }
+
+ /**
+ * Set a flag in the query but allow it to index all password protected posts for now,
+ * so WP does not inject its own where clause.
+ */
+ $args['ep_orders_has_password'] = true;
+ $args['has_password'] = null;
+
+ return $args;
+ }
+
+ /**
+ * Restrict password protected posts back but allow orders.
+ *
+ * @see maybe_query_password_protected_posts
+ * @param string $where Current where clause
+ * @param WP_Query $query WP_Query
+ * @return string
+ */
+ public function maybe_set_posts_where( $where, $query ) {
+ global $wpdb;
+
+ if ( ! $query->get( 'ep_orders_has_password' ) ) {
+ return $where;
+ }
+
+ $where .= " AND ( {$wpdb->posts}.post_password = '' OR {$wpdb->posts}.post_type = 'shop_order' )";
+
+ return $where;
+ }
+}
diff --git a/includes/classes/Feature/WooCommerce/Products.php b/includes/classes/Feature/WooCommerce/Products.php
new file mode 100644
index 0000000000..c415ed5fd6
--- /dev/null
+++ b/includes/classes/Feature/WooCommerce/Products.php
@@ -0,0 +1,986 @@
+woocommerce = $woocommerce;
+ }
+
+ /**
+ * Setup product related hooks
+ */
+ public function setup() {
+ add_action( 'ep_formatted_args', [ $this, 'price_filter' ], 10, 3 );
+ add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'allow_meta_keys' ] );
+ add_filter( 'ep_sync_taxonomies', [ $this, 'sync_taxonomies' ] );
+ add_filter( 'ep_term_suggest_post_type', [ $this, 'suggest_wc_add_post_type' ] );
+ add_filter( 'ep_facet_include_taxonomies', [ $this, 'add_product_attributes' ] );
+ add_filter( 'ep_weighting_fields_for_post_type', [ $this, 'add_product_attributes_to_weighting' ], 10, 2 );
+ add_filter( 'ep_weighting_default_post_type_weights', [ $this, 'add_product_default_post_type_weights' ], 10, 2 );
+ add_filter( 'ep_prepare_meta_data', [ $this, 'add_variations_skus_meta' ], 10, 2 );
+ add_filter( 'request', [ $this, 'admin_product_list_request_query' ], 9 );
+
+ add_action( 'pre_get_posts', [ $this, 'translate_args' ], 11, 1 );
+
+ // Custom product ordering
+ add_action( 'ep_admin_notices', [ $this, 'maybe_display_notice_about_product_ordering' ] );
+ add_action( 'woocommerce_after_product_ordering', [ $this, 'action_sync_on_woocommerce_sort_single' ], 10, 2 );
+
+ // Settings for Weight results by date
+ add_action( 'ep_weight_settings_after_search', [ $this, 'add_weight_settings_search' ] );
+ add_filter( 'ep_is_decaying_enabled', [ $this, 'maybe_disable_decaying' ], 10, 3 );
+ }
+
+ /**
+ * Modifies main query to allow filtering by price with WooCommerce "Filter by price" widget.
+ *
+ * @param array $args ES args
+ * @param array $query_args WP_Query args
+ * @param WP_Query $query WP_Query object
+ * @return array
+ */
+ public function price_filter( $args, $query_args, $query ) {
+ // Only can use widget on main query
+ if ( ! $query->is_main_query() ) {
+ return $args;
+ }
+
+ // Only can use widget on shop, product taxonomy, or search
+ if ( ! is_shop() && ! is_product_taxonomy() && ! is_search() ) {
+ return $args;
+ }
+
+ // phpcs:disable WordPress.Security.NonceVerification
+ if ( empty( $_GET['min_price'] ) && empty( $_GET['max_price'] ) ) {
+ return $args;
+ }
+
+ $min_price = ! empty( $_GET['min_price'] ) ? sanitize_text_field( wp_unslash( $_GET['min_price'] ) ) : null;
+ $max_price = ! empty( $_GET['max_price'] ) ? sanitize_text_field( wp_unslash( $_GET['max_price'] ) ) : null;
+ // phpcs:enable WordPress.Security.NonceVerification
+
+ if ( $query->is_search() ) {
+ /**
+ * This logic is iffy but the WC price filter widget is not intended for use with search anyway
+ */
+ $old_query = $args['query']['bool'];
+ unset( $args['query']['bool']['should'] );
+
+ if ( ! empty( $min_price ) ) {
+ $args['query']['bool']['must'][0]['range']['meta._price.long']['gte'] = $min_price;
+ }
+
+ if ( ! empty( $max_price ) ) {
+ $args['query']['bool']['must'][0]['range']['meta._price.long']['lte'] = $max_price;
+ }
+
+ $args['query']['bool']['must'][0]['range']['meta._price.long']['boost'] = 2.0;
+ $args['query']['bool']['must'][1]['bool'] = $old_query;
+ } else {
+ unset( $args['query']['match_all'] );
+
+ $args['query']['range']['meta._price.long']['gte'] = ! empty( $min_price ) ? $min_price : 0;
+
+ if ( ! empty( $min_price ) ) {
+ $args['query']['range']['meta._price.long']['gte'] = $min_price;
+ }
+
+ if ( ! empty( $max_price ) ) {
+ $args['query']['range']['meta._price.long']['lte'] = $max_price;
+ }
+
+ $args['query']['range']['meta._price.long']['boost'] = 2.0;
+ }
+
+ return $args;
+ }
+
+ /**
+ * Index WooCommerce products meta fields
+ *
+ * @param array $meta Existing post meta
+ * @return array
+ */
+ public function allow_meta_keys( $meta ) {
+ return array_unique(
+ array_merge(
+ $meta,
+ array(
+ '_thumbnail_id',
+ '_product_attributes',
+ '_wpb_vc_js_status',
+ '_swatch_type',
+ 'total_sales',
+ '_downloadable',
+ '_virtual',
+ '_regular_price',
+ '_sale_price',
+ '_tax_status',
+ '_tax_class',
+ '_purchase_note',
+ '_featured',
+ '_weight',
+ '_length',
+ '_width',
+ '_height',
+ '_visibility',
+ '_sku',
+ '_sale_price_dates_from',
+ '_sale_price_dates_to',
+ '_price',
+ '_sold_individually',
+ '_manage_stock',
+ '_backorders',
+ '_stock',
+ '_upsell_ids',
+ '_crosssell_ids',
+ '_stock_status',
+ '_product_version',
+ '_product_tabs',
+ '_override_tab_layout',
+ '_suggested_price',
+ '_min_price',
+ '_variable_billing',
+ '_wc_average_rating',
+ '_product_image_gallery',
+ '_bj_lazy_load_skip_post',
+ '_min_variation_price',
+ '_max_variation_price',
+ '_min_price_variation_id',
+ '_max_price_variation_id',
+ '_min_variation_regular_price',
+ '_max_variation_regular_price',
+ '_min_regular_price_variation_id',
+ '_max_regular_price_variation_id',
+ '_min_variation_sale_price',
+ '_max_variation_sale_price',
+ '_min_sale_price_variation_id',
+ '_max_sale_price_variation_id',
+ '_default_attributes',
+ '_swatch_type_options',
+ '_variations_skus',
+ )
+ )
+ );
+ }
+
+ /**
+ * Index WooCommerce taxonomies
+ *
+ * @param array $taxonomies Index taxonomies array
+ * @return array
+ */
+ public function sync_taxonomies( $taxonomies ) {
+ $woo_taxonomies = [];
+
+ $product_type = get_taxonomy( 'product_type' );
+ if ( false !== $product_type ) {
+ $woo_taxonomies[] = $product_type;
+ }
+
+ $product_visibility = get_taxonomy( 'product_visibility' );
+ if ( false !== $product_visibility ) {
+ $woo_taxonomies[] = $product_visibility;
+ }
+
+ /**
+ * Note product_shipping_class, product_cat, and product_tag are already public. Make
+ * sure to index non-attribute taxonomies.
+ */
+ $attribute_taxonomies = wc_get_attribute_taxonomies();
+
+ if ( ! empty( $attribute_taxonomies ) ) {
+ foreach ( $attribute_taxonomies as $tax ) {
+ $name = wc_attribute_taxonomy_name( $tax->attribute_name );
+
+ if ( ! empty( $name ) ) {
+ if ( empty( $tax->attribute_ ) ) {
+ $woo_taxonomies[] = get_taxonomy( $name );
+ }
+ }
+ }
+ }
+
+ return array_merge( $taxonomies, $woo_taxonomies );
+ }
+
+ /**
+ * Add WC product post type to autosuggest
+ *
+ * @param array $post_types Array of post types (e.g. post, page)
+ * @return array
+ */
+ public function suggest_wc_add_post_type( $post_types ) {
+ if ( ! in_array( 'product', $post_types, true ) ) {
+ $post_types[] = 'product';
+ }
+
+ return $post_types;
+ }
+
+ /**
+ * Add WooCommerce Product Attributes to EP Facets.
+ *
+ * @param array $taxonomies Taxonomies array
+ * @return array
+ */
+ public function add_product_attributes( $taxonomies = [] ) {
+ $attribute_names = wc_get_attribute_taxonomy_names();
+
+ foreach ( $attribute_names as $name ) {
+ if ( ! taxonomy_exists( $name ) ) {
+ continue;
+ }
+ $taxonomies[ $name ] = get_taxonomy( $name );
+ }
+
+ return $taxonomies;
+ }
+
+ /**
+ * Add WooCommerce Fields to the Weighting Dashboard.
+ *
+ * @param array $fields Current weighting fields.
+ * @param string $post_type Current post type.
+ * @return array New fields.
+ */
+ public function add_product_attributes_to_weighting( $fields, $post_type ) {
+ if ( 'product' !== $post_type ) {
+ return $fields;
+ }
+
+ if ( ! empty( $fields['attributes']['children']['author_name'] ) ) {
+ unset( $fields['attributes']['children']['author_name'] );
+ }
+
+ $sku_key = 'meta._sku.value';
+
+ $fields['attributes']['children'][ $sku_key ] = array(
+ 'key' => $sku_key,
+ 'label' => __( 'SKU', 'elasticpress' ),
+ );
+
+ $variations_skus_key = 'meta._variations_skus.value';
+
+ $fields['attributes']['children'][ $variations_skus_key ] = array(
+ 'key' => $variations_skus_key,
+ 'label' => __( 'Variations SKUs', 'elasticpress' ),
+ );
+
+ return $fields;
+ }
+
+ /**
+ * Add WooCommerce Fields to the default values of the Weighting Dashboard.
+ *
+ * @param array $defaults Default values for the post type.
+ * @param string $post_type Current post type.
+ * @return array
+ */
+ public function add_product_default_post_type_weights( $defaults, $post_type ) {
+ if ( 'product' !== $post_type ) {
+ return $defaults;
+ }
+
+ if ( ! empty( $defaults['author_name'] ) ) {
+ unset( $defaults['author_name'] );
+ }
+
+ $defaults['meta._sku.value'] = array(
+ 'enabled' => true,
+ 'weight' => 1,
+ );
+
+ $defaults['meta._variations_skus.value'] = array(
+ 'enabled' => true,
+ 'weight' => 1,
+ );
+
+ return $defaults;
+ }
+
+ /**
+ * Add a new `_variations_skus` meta field to the product to be indexed in Elasticsearch.
+ *
+ * @param array $post_meta Post meta
+ * @param WP_Post $post Post object
+ * @return array
+ */
+ public function add_variations_skus_meta( $post_meta, $post ) {
+ if ( 'product' !== $post->post_type ) {
+ return $post_meta;
+ }
+
+ $product = wc_get_product( $post );
+ if ( ! $product ) {
+ return $post_meta;
+ }
+
+ $variations_ids = $product->get_children();
+
+ $post_meta['_variations_skus'] = array_reduce(
+ $variations_ids,
+ function ( $variations_skus, $current_id ) {
+ $variation = wc_get_product( $current_id );
+ if ( ! $variation || ! $variation->exists() ) {
+ return $variations_skus;
+ }
+ $variation_sku = $variation->get_sku();
+ if ( ! $variation_sku ) {
+ return $variations_skus;
+ }
+ $variations_skus[] = $variation_sku;
+ return $variations_skus;
+ },
+ []
+ );
+
+ return $post_meta;
+ }
+
+ /**
+ * Integrate ElasticPress with the WooCommerce Admin Product List.
+ *
+ * WooCommerce uses its `WC_Admin_List_Table_Products` class to control that screen. This
+ * function adds all necessary hooks to bypass the default behavior and integrate with ElasticPress.
+ * By default, WC runs a SQL query to get the Product IDs that match the list criteria and passes
+ * that list of IDs to the main WP_Query. This integration changes that process to a single query, run
+ * by ElasticPress.
+ *
+ * @param array $query_vars Query vars.
+ * @return array
+ */
+ public function admin_product_list_request_query( $query_vars ) {
+ global $typenow, $wc_list_table;
+
+ // Return if not in the correct screen.
+ if ( ! is_a( $wc_list_table, 'WC_Admin_List_Table_Products' ) || 'product' !== $typenow ) {
+ return $query_vars;
+ }
+
+ // Return if admin WP_Query integration is not turned on, i.e., Protect Content is not enabled.
+ if ( ! has_filter( 'ep_admin_wp_query_integration', '__return_true' ) ) {
+ return $query_vars;
+ }
+
+ /**
+ * Filter to skip integration with WooCommerce Admin Product List.
+ *
+ * @hook ep_woocommerce_integrate_admin_products_list
+ * @since 4.2.0
+ * @param {bool} $integrate True to integrate, false to preserve original behavior. Defaults to true.
+ * @param {array} $query_vars Query vars.
+ * @return {bool} New integrate value
+ */
+ if ( ! apply_filters( 'ep_woocommerce_integrate_admin_products_list', true, $query_vars ) ) {
+ return $query_vars;
+ }
+
+ add_action( 'pre_get_posts', [ $this, 'translate_args_admin_products_list' ], 12 );
+
+ // This short-circuits WooCommerce search for product IDs.
+ add_filter( 'woocommerce_product_pre_search_products', '__return_empty_array' );
+
+ return $query_vars;
+ }
+
+ /**
+ * Apply the necessary changes to WP_Query in WooCommerce Admin Product List.
+ *
+ * @param WP_Query $query The WP Query being executed.
+ */
+ public function translate_args_admin_products_list( $query ) {
+ // The `translate_args()` method sets it to `true` if we should integrate it.
+ if ( ! $query->get( 'ep_integrate', false ) ) {
+ return;
+ }
+
+ // WooCommerce unsets the search term right after using it to fetch product IDs. Here we add it back.
+ $search_term = ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
+ if ( ! empty( $search_term ) ) {
+ $query->set( 's', sanitize_text_field( $search_term ) ); // phpcs:ignore WordPress.Security.NonceVerification
+
+ /**
+ * Filter the fields used in WooCommerce Admin Product Search.
+ *
+ * ```
+ * add_filter(
+ * 'ep_woocommerce_admin_products_list_search_fields',
+ * function ( $wc_admin_search_fields ) {
+ * $wc_admin_search_fields['meta'][] = 'custom_field';
+ * return $wc_admin_search_fields;
+ * }
+ * );
+ * ```
+ *
+ * @hook ep_woocommerce_admin_products_list_search_fields
+ * @since 4.2.0
+ * @param {array} $wc_admin_search_fields Fields to be used in the WooCommerce Admin Product Search
+ * @return {array} New fields
+ */
+ $search_fields = apply_filters(
+ 'ep_woocommerce_admin_products_list_search_fields',
+ [
+ 'post_title',
+ 'post_content',
+ 'post_excerpt',
+ 'meta' => [
+ '_sku',
+ '_variations_skus',
+ ],
+ ]
+ );
+
+ $query->set( 'search_fields', $search_fields );
+ }
+
+ // Sets the meta query for `product_type` if needed. Also removed from the WP_Query by WC in `WC_Admin_List_Table_Products::query_filters()`.
+ $product_type_query = $query->get( 'product_type', '' );
+ $product_type_url = ! empty( $_GET['product_type'] ) ? sanitize_text_field( wp_unslash( $_GET['product_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
+ $allowed_prod_types = [ 'virtual', 'downloadable' ];
+ if ( empty( $product_type_query ) && ! empty( $product_type_url ) && in_array( $product_type_url, $allowed_prod_types, true ) ) {
+ $meta_query = $query->get( 'meta_query', [] );
+ $meta_query[] = [
+ 'key' => "_{$product_type_url}",
+ 'value' => 'yes',
+ ];
+ $query->set( 'meta_query', $meta_query );
+ }
+
+ // Sets the meta query for `stock_status` if needed.
+ $stock_status_query = $query->get( 'stock_status', '' );
+ $stock_status_url = ! empty( $_GET['stock_status'] ) ? sanitize_text_field( wp_unslash( $_GET['stock_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
+ $allowed_stock_status = [ 'instock', 'outofstock', 'onbackorder' ];
+ if ( empty( $stock_status_query ) && ! empty( $stock_status_url ) && in_array( $stock_status_url, $allowed_stock_status, true ) ) {
+ $meta_query = $query->get( 'meta_query', [] );
+ $meta_query[] = [
+ 'key' => '_stock_status',
+ 'value' => $stock_status_url,
+ ];
+ $query->set( 'meta_query', $meta_query );
+ }
+ }
+
+ /**
+ * Depending on the number of products display an admin notice in the custom sort screen for WooCommerce Products
+ *
+ * @param array $notices Current ElasticPress admin notices
+ * @return array
+ */
+ public function maybe_display_notice_about_product_ordering( $notices ) {
+ global $pagenow, $wp_query;
+
+ /**
+ * Make sure we're on edit.php in admin dashboard.
+ */
+ if ( ! is_admin() || 'edit.php' !== $pagenow || empty( $wp_query->query['orderby'] ) || 'menu_order title' !== $wp_query->query['orderby'] ) {
+ return $notices;
+ }
+
+ $documents_per_page_sync = IndexHelper::factory()->get_index_default_per_page();
+ if ( $documents_per_page_sync >= $wp_query->found_posts ) {
+ return $notices;
+ }
+
+ $notices['woocommerce_custom_sort'] = [
+ 'html' => sprintf(
+ /* translators: Sync Page URL */
+ __( 'Due to the number of products in the site, you will need to resync after applying a custom sort order.', 'elasticpress' ),
+ Utils\get_sync_url()
+ ),
+ 'type' => 'warning',
+ 'dismiss' => true,
+ ];
+
+ return $notices;
+ }
+
+ /**
+ * Conditionally resync products after applying a custom order.
+ *
+ * @param int $sorting_id ID of post dragged and dropped
+ * @param array $menu_orders Post IDs and their new menu_order value
+ */
+ public function action_sync_on_woocommerce_sort_single( $sorting_id, $menu_orders ) {
+ $documents_per_page_sync = IndexHelper::factory()->get_index_default_per_page();
+ if ( $documents_per_page_sync < count( $menu_orders ) ) {
+ return;
+ }
+
+ $sync_manager = Indexables::factory()->get( 'post' )->sync_manager;
+ foreach ( $menu_orders as $post_id => $order ) {
+ $sync_manager->add_to_queue( $post_id );
+ }
+ }
+
+ /**
+ * Add weight by date settings related to WooCommerce
+ *
+ * @param array $settings Current settings.
+ */
+ public function add_weight_settings_search( $settings ) {
+ ?>
+
+
+ 1 ) {
+ return $is_decaying_enabled;
+ }
+
+ return false;
+ }
+
+ /**
+ * Translate args to ElasticPress compat format. This is the meat of what the feature does
+ *
+ * @param \WP_Query $query WP Query
+ */
+ public function translate_args( $query ) {
+ if ( ! $this->woocommerce->should_integrate_with_query( $query ) ) {
+ return;
+ }
+
+ if ( ! $this->should_integrate_with_query( $query ) ) {
+ return;
+ }
+
+ /**
+ * Make sure filters are suppressed
+ */
+ $query->query['suppress_filters'] = false;
+ $query->set( 'suppress_filters', false );
+
+ $query->set( 'ep_integrate', true );
+
+ $this->maybe_update_tax_query( $query );
+ $this->maybe_update_post_type( $query );
+ $this->maybe_update_meta_query( $query );
+
+ $this->maybe_handle_top_rated( $query );
+
+ $this->maybe_set_search_fields( $query );
+ $this->maybe_set_orderby( $query );
+ }
+
+ /**
+ * Determines whether or not ES should be integrating with the provided query
+ *
+ * @param \WP_Query $query Query we might integrate with
+ * @return bool
+ */
+ public function should_integrate_with_query( \WP_Query $query ) : bool {
+ /**
+ * Check for taxonomies
+ */
+ $supported_taxonomies = $this->get_supported_taxonomies();
+ $tax_query = $query->get( 'tax_query', [] );
+ $taxonomies_queried = array_merge(
+ array_column( $tax_query, 'taxonomy' ),
+ array_keys( $query->query_vars )
+ );
+ if ( ! empty( array_intersect( $supported_taxonomies, $taxonomies_queried ) ) ) {
+ return true;
+ }
+
+ /**
+ * Check the post type
+ */
+ $supported_post_types = $this->get_supported_post_types( $query );
+ $post_type = $query->get( 'post_type', false );
+ if ( ! empty( $post_type ) && ( in_array( $post_type, $supported_post_types, true ) || ( is_array( $post_type ) && ! array_diff( $post_type, $supported_post_types ) ) ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the WooCommerce supported taxonomies (related to products.)
+ *
+ * @return array
+ */
+ public function get_supported_taxonomies() : array {
+ $supported_taxonomies = array(
+ 'product_cat',
+ 'product_tag',
+ 'product_type',
+ 'product_visibility',
+ 'product_shipping_class',
+ );
+
+ // Add in any attribute taxonomies that exist
+ $attribute_taxonomies = wc_get_attribute_taxonomy_names();
+
+ $supported_taxonomies = array_merge( $supported_taxonomies, $attribute_taxonomies );
+
+ /**
+ * DEPRECATED. Filter supported custom taxonomies for WooCommerce integration.
+ *
+ * @param {array} $supported_taxonomies An array of default taxonomies.
+ * @hook ep_woocommerce_supported_taxonomies
+ * @since 2.3.0
+ * @return {array} New taxonomies
+ */
+ $supported_taxonomies = apply_filters_deprecated(
+ 'ep_woocommerce_supported_taxonomies',
+ [ $supported_taxonomies ],
+ '4.7.0',
+ 'ep_woocommerce_products_supported_taxonomies'
+ );
+
+ /**
+ * Filter supported custom taxonomies for WooCommerce product queries integration
+ *
+ * @param {array} $supported_taxonomies An array of default taxonomies.
+ * @hook ep_woocommerce_products_supported_taxonomies
+ * @since 4.7.0
+ * @return {array} New taxonomies
+ */
+ return apply_filters( 'ep_woocommerce_products_supported_taxonomies', $supported_taxonomies );
+ }
+
+ /**
+ * Get the WooCommerce supported post types (related to products.)
+ *
+ * @param \WP_Query $query The WP_Query object
+ * @return array
+ */
+ public function get_supported_post_types( \WP_Query $query ) : array {
+ $post_types = [ 'product_variation' ];
+
+ $is_main_post_type_archive = $query->is_main_query() && $query->is_post_type_archive( 'product' );
+ $has_ep_integrate_set_true = isset( $query->query_vars['ep_integrate'] ) && filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN );
+ if ( $is_main_post_type_archive || $has_ep_integrate_set_true ) {
+ $post_types[] = 'product';
+ }
+
+ /**
+ * DEPRECATED. Expands or contracts the post_types eligible for indexing.
+ *
+ * @hook ep_woocommerce_default_supported_post_types
+ * @since 4.4.0
+ * @param {array} $post_types Post types
+ * @return {array} New post types
+ */
+ $supported_post_types = apply_filters_deprecated(
+ 'ep_woocommerce_default_supported_post_types',
+ [ $post_types ],
+ '4.7.0',
+ 'ep_woocommerce_products_supported_post_types'
+ );
+
+ /**
+ * Expands or contracts the post_types related to products eligible for indexing.
+ *
+ * @hook ep_woocommerce_products_supported_post_types
+ * @since 4.7.0
+ * @param {array} $post_types Post types
+ * @param {WP_Query} $query The WP_Query object
+ * @return {array} New post types
+ */
+ $supported_post_types = apply_filters( 'ep_woocommerce_products_supported_post_types', $post_types, $query );
+
+ $supported_post_types = array_intersect(
+ $supported_post_types,
+ Indexables::factory()->get( 'post' )->get_indexable_post_types()
+ );
+
+ return $supported_post_types;
+ }
+
+ /**
+ * If needed, update the `'tax_query'` parameter
+ *
+ * If a supported taxonomy was added in the root of the args array,
+ * this method moves it to the `'tax_query'`
+ *
+ * @param \WP_Query $query The WP_Query object
+ */
+ protected function maybe_update_tax_query( \WP_Query $query ) {
+ $supported_taxonomies = $this->get_supported_taxonomies();
+ $tax_query = $query->get( 'tax_query', [] );
+
+ foreach ( $supported_taxonomies as $taxonomy ) {
+ $term = $query->get( $taxonomy, false );
+
+ if ( ! empty( $term ) ) {
+ $tax_query[] = array(
+ 'taxonomy' => $taxonomy,
+ 'field' => 'slug',
+ 'terms' => (array) $term,
+ );
+ }
+ }
+
+ $query->set( 'tax_query', $tax_query );
+ }
+
+ /**
+ * Set the post_type to product if empty
+ *
+ * @param \WP_Query $query The WP_Query object
+ */
+ protected function maybe_update_post_type( \WP_Query $query ) {
+ $post_type = $query->get( 'post_type', false );
+
+ if ( empty( $post_type ) ) {
+ $query->set( 'post_type', 'product' );
+ }
+ }
+
+ /**
+ * If the `'meta_key'` or `'meta_value'` parameters were set,
+ * move them to `'meta_query'`
+ *
+ * @param \WP_Query $query The WP_Query object
+ */
+ protected function maybe_update_meta_query( \WP_Query $query ) {
+ /**
+ * Handle meta queries
+ */
+ $meta_query = $query->get( 'meta_query', [] );
+ $meta_key = $query->get( 'meta_key', false );
+ $meta_value = $query->get( 'meta_value', false );
+
+ if ( ! empty( $meta_key ) && ! empty( $meta_value ) ) {
+ $meta_query[] = array(
+ 'key' => $meta_key,
+ 'value' => $meta_value,
+ );
+
+ $query->set( 'meta_query', $meta_query );
+ }
+ }
+
+ /**
+ * Handle the WC Top Rated Widget
+ *
+ * @param \WP_Query $query The WP_Query object
+ * @return void
+ */
+ protected function maybe_handle_top_rated( \WP_Query $query ) {
+ if ( ! has_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) ) ) {
+ return;
+ }
+
+ remove_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) );
+ $query->set( 'orderby', 'meta_value_num' );
+ $query->set( 'meta_key', '_wc_average_rating' );
+ }
+
+ /**
+ * If the query has a search term and the weighting dashboard is not
+ * available, add the needed fields
+ *
+ * @param \WP_Query $query The WP_Query
+ * @return \WP_Query
+ */
+ protected function maybe_set_search_fields( \WP_Query $query ) {
+ $search_term = $this->woocommerce->get_search_term( $query );
+ if ( empty( $search_term ) ) {
+ return $query;
+ }
+
+ $post_type = $query->get( 'post_type', false );
+ if ( 'product' !== $post_type || ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
+ return;
+ }
+
+ $search_fields = $query->get( 'search_fields', array( 'post_title', 'post_content', 'post_excerpt' ) );
+
+ // Remove author_name from this search.
+ $search_fields = $this->remove_author( $search_fields );
+
+ $search_fields['meta'] = ( ! empty( $search_fields['meta'] ) ) ? $search_fields['meta'] : [];
+ $search_fields['taxonomies'] = ( ! empty( $search_fields['taxonomies'] ) ) ? $search_fields['taxonomies'] : [];
+
+ $search_fields['meta'] = array_merge( $search_fields['meta'], array( '_sku' ) );
+ $search_fields['taxonomies'] = array_merge( $search_fields['taxonomies'], array( 'category', 'post_tag', 'product_tag', 'product_cat' ) );
+
+ $query->set( 'search_fields', $search_fields );
+ }
+
+ /**
+ * Remove the author_name from search fields.
+ *
+ * @param array $search_fields Array of search fields.
+ * @return array
+ */
+ public function remove_author( array $search_fields ) : array {
+ foreach ( $search_fields as $field_key => $field ) {
+ if ( 'author_name' === $field ) {
+ unset( $search_fields[ $field_key ] );
+ }
+ }
+
+ return $search_fields;
+ }
+
+ /**
+ * If needed, set the `'order'` and `'orderby'` parameters
+ *
+ * @param \WP_Query $query The WP_Query object
+ */
+ protected function maybe_set_orderby( \WP_Query $query ) {
+ $search_term = $this->woocommerce->get_search_term( $query );
+
+ if ( empty( $search_term ) ) {
+ /**
+ * For default sorting by popularity (total_sales) and rating
+ * Woocommerce doesn't set the orderby correctly.
+ * These lines will check the meta_key and correct the orderby based on that.
+ * And this won't run in search result and only run in main query
+ */
+ $meta_key = $query->get( 'meta_key', false );
+ if ( $meta_key && $query->is_main_query() ) {
+ switch ( $meta_key ) {
+ case 'total_sales':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( 'total_sales' ) );
+ $query->set( 'order', 'DESC' );
+ break;
+ case '_wc_average_rating':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( '_wc_average_rating' ) );
+ $query->set( 'order', 'DESC' );
+ break;
+ }
+ }
+ }
+
+ /**
+ * Set orderby and order for price/popularity when GET param not set
+ */
+ $orderby = $query->get( 'orderby', null );
+ if ( $orderby && in_array( $orderby, [ 'price', 'popularity' ], true ) ) {
+ $order = $query->get( 'order', 'DESC' );
+ $query->set( 'order', $order );
+
+ $orderby_field = 'price' === $orderby ? '_price' : 'total_sales';
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( $orderby_field ) );
+ }
+
+ /**
+ * Set orderby from GET param
+ * Also make sure the orderby param affects only the main query
+ */
+ if ( ! empty( $_GET['orderby'] ) && $query->is_main_query() ) { // phpcs:ignore WordPress.Security.NonceVerification
+ $orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
+ switch ( $orderby ) { // phpcs:ignore WordPress.Security.NonceVerification
+ case 'popularity':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( 'total_sales' ) );
+ $query->set( 'order', 'DESC' );
+ break;
+ case 'price':
+ $query->set( 'order', $query->get( 'order', 'ASC' ) );
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
+ break;
+ case 'price-desc':
+ $query->set( 'order', 'DESC' );
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
+ break;
+ case 'rating':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( '_wc_average_rating' ) );
+ $query->set( 'order', 'DESC' );
+ break;
+ case 'date':
+ case 'title':
+ case 'ID':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( $orderby ) );
+ break;
+ case 'sku':
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( '_sku' ) );
+ break;
+ default:
+ $query->set( 'orderby', $this->get_orderby_meta_mapping( 'menu_order' ) ); // Order by menu and title.
+ }
+ }
+ }
+
+ /**
+ * Fetch the ES related meta mapping for orderby
+ *
+ * @param array $meta_key The meta key to get the mapping for.
+ * @return string The mapped meta key.
+ */
+ public function get_orderby_meta_mapping( $meta_key ) : string {
+ /**
+ * Filter WooCommerce to Elasticsearch meta mapping
+ *
+ * @hook orderby_meta_mapping
+ * @param {array} $mapping Meta mapping
+ * @return {array} New mapping
+ */
+ $mapping = apply_filters(
+ 'orderby_meta_mapping',
+ array(
+ 'ID' => 'ID',
+ 'title' => 'title date',
+ 'menu_order' => 'menu_order title date',
+ 'menu_order title' => 'menu_order title date',
+ 'total_sales' => 'meta.total_sales.double date',
+ '_wc_average_rating' => 'meta._wc_average_rating.double date',
+ '_price' => 'meta._price.double date',
+ '_sku' => 'meta._sku.value.sortable date',
+ )
+ );
+
+ if ( isset( $mapping[ $meta_key ] ) ) {
+ return $mapping[ $meta_key ];
+ }
+
+ return 'date';
+ }
+}
diff --git a/includes/classes/Feature/WooCommerce/WooCommerce.php b/includes/classes/Feature/WooCommerce/WooCommerce.php
index 5bbfe41df8..25cb8868ea 100644
--- a/includes/classes/Feature/WooCommerce/WooCommerce.php
+++ b/includes/classes/Feature/WooCommerce/WooCommerce.php
@@ -23,7 +23,23 @@
*/
class WooCommerce extends Feature {
/**
- * If enabled, receive the Orders object instance
+ * If enabled, receive the OrdersAutosuggest object instance
+ *
+ * @since 4.7.0
+ * @var null|OrdersAutosuggest
+ */
+ public $orders_autosuggest = null;
+
+ /**
+ * Receive the Products object instance
+ *
+ * @since 4.7.0
+ * @var null|Products
+ */
+ public $products = null;
+
+ /**
+ * Receive the Orders object instance
*
* @since 4.5.0
* @var null|Orders
@@ -54,566 +70,339 @@ public function __construct() {
'orders' => '0',
];
- $this->orders = new Orders();
+ $this->orders = new Orders( $this );
+ $this->products = new Products( $this );
+ $this->orders_autosuggest = new OrdersAutosuggest();
parent::__construct();
}
/**
- * Index Woocommerce meta
+ * Setup all feature filters
*
- * @param array $meta Existing post meta.
- * @param array $post Post arguments array.
- * @since 2.1
- * @return array
+ * @since 2.1
*/
- public function whitelist_meta_keys( $meta, $post ) {
- return array_unique(
- array_merge(
- $meta,
- array(
- '_thumbnail_id',
- '_product_attributes',
- '_wpb_vc_js_status',
- '_swatch_type',
- 'total_sales',
- '_downloadable',
- '_virtual',
- '_regular_price',
- '_sale_price',
- '_tax_status',
- '_tax_class',
- '_purchase_note',
- '_featured',
- '_weight',
- '_length',
- '_width',
- '_height',
- '_visibility',
- '_sku',
- '_sale_price_dates_from',
- '_sale_price_dates_to',
- '_price',
- '_sold_individually',
- '_manage_stock',
- '_backorders',
- '_stock',
- '_upsell_ids',
- '_crosssell_ids',
- '_stock_status',
- '_product_version',
- '_product_tabs',
- '_override_tab_layout',
- '_suggested_price',
- '_min_price',
- '_customer_user',
- '_variable_billing',
- '_wc_average_rating',
- '_product_image_gallery',
- '_bj_lazy_load_skip_post',
- '_min_variation_price',
- '_max_variation_price',
- '_min_price_variation_id',
- '_max_price_variation_id',
- '_min_variation_regular_price',
- '_max_variation_regular_price',
- '_min_regular_price_variation_id',
- '_max_regular_price_variation_id',
- '_min_variation_sale_price',
- '_max_variation_sale_price',
- '_min_sale_price_variation_id',
- '_max_sale_price_variation_id',
- '_default_attributes',
- '_swatch_type_options',
- '_order_key',
- '_billing_company',
- '_billing_address_1',
- '_billing_address_2',
- '_billing_city',
- '_billing_postcode',
- '_billing_country',
- '_billing_state',
- '_billing_email',
- '_billing_phone',
- '_shipping_address_1',
- '_shipping_address_2',
- '_shipping_city',
- '_shipping_postcode',
- '_shipping_country',
- '_shipping_state',
- '_billing_last_name',
- '_billing_first_name',
- '_shipping_first_name',
- '_shipping_last_name',
- '_variations_skus',
- )
- )
- );
+ public function setup() {
+ if ( ! function_exists( 'WC' ) ) {
+ return;
+ }
+
+ $this->products->setup();
+ $this->orders->setup();
+
+ add_filter( 'ep_integrate_search_queries', [ $this, 'disallow_coupons' ], 10, 2 );
+
+ // These hooks are deprecated and will be removed in an upcoming major version of ElasticPress
+ add_filter( 'woocommerce_layered_nav_query_post_ids', [ $this, 'convert_post_object_to_id' ], 10, 4 );
+ add_filter( 'woocommerce_unfiltered_product_ids', [ $this, 'convert_post_object_to_id' ], 10, 4 );
+ add_action( 'ep_wp_query_search_cached_posts', [ $this, 'disallow_duplicated_query' ], 10, 2 );
+
+ // Orders Autosuggest feature.
+ if ( $this->is_orders_autosuggest_enabled() ) {
+ $this->orders_autosuggest->setup();
+ }
}
/**
- * Make sure all loop shop post ins are IDS. We have to pass post objects here since we override
- * the fields=>id query for the layered filter nav query
+ * Given a WP_Query object, return its search term (if any)
*
- * @param array $posts Post object array.
- * @since 2.1
- * @return array
+ * This method also accounts for the `'search'` parameter used by
+ * WooCommerce, in addition to the regular `'s'` parameter.
+ *
+ * @param \WP_Query $query The WP_Query object
+ * @return string
*/
- public function convert_post_object_to_id( $posts ) {
- _doing_it_wrong( __METHOD__, 'This filter was removed from WooCommerce and will be removed from ElasticPress in a future release.', '4.5.0' );
- return $posts;
+ public function get_search_term( \WP_Query $query ) : string {
+ $search = $query->get( 'search' );
+ return ( ! empty( $search ) ) ? $search : $query->get( 's', '' );
}
/**
- * Index Woocommerce taxonomies
+ * Make search coupons don't go through ES
*
- * @param array $taxonomies Index taxonomies array.
- * @param array $post Post properties array.
- * @since 2.1
- * @return array
+ * @param bool $enabled Coupons enabled or not
+ * @param WP_Query $query WP Query
+ * @since 4.7.0
+ * @return bool
*/
- public function whitelist_taxonomies( $taxonomies, $post ) {
- $woo_taxonomies = [];
-
- $product_type = get_taxonomy( 'product_type' );
- if ( false !== $product_type ) {
- $woo_taxonomies[] = $product_type;
- }
-
- $product_visibility = get_taxonomy( 'product_visibility' );
- if ( false !== $product_visibility ) {
- $woo_taxonomies[] = $product_visibility;
+ public function disallow_coupons( $enabled, $query ) {
+ if ( is_admin() ) {
+ return $enabled;
}
- /**
- * Note product_shipping_class, product_cat, and product_tag are already public. Make
- * sure to index non-attribute taxonomies.
- */
- $attribute_taxonomies = wc_get_attribute_taxonomies();
-
- if ( ! empty( $attribute_taxonomies ) ) {
- foreach ( $attribute_taxonomies as $tax ) {
- $name = wc_attribute_taxonomy_name( $tax->attribute_name );
-
- if ( ! empty( $name ) ) {
- if ( empty( $tax->attribute_ ) ) {
- $woo_taxonomies[] = get_taxonomy( $name );
- }
- }
- }
+ if ( 'shop_coupon' === $query->get( 'post_type' ) && empty( $query->query_vars['ep_integrate'] ) ) {
+ return false;
}
- return array_merge( $taxonomies, $woo_taxonomies );
+ return $enabled;
}
/**
- * Disallow duplicated ES queries on Orders page.
- *
- * @since 2.4
- *
- * @param array $value Original filter values.
- * @param WP_Query $query WP_Query
+ * Output feature box long
*
- * @return array
+ * @since 2.1
*/
- public function disallow_duplicated_query( $value, $query ) {
- _doing_it_wrong( __METHOD__, 'This filter was removed from WooCommerce and will be removed from ElasticPress in a future release.', '4.5.0' );
-
- return $value;
+ public function output_feature_box_long() {
+ ?>
+
+ should_integrate_with_query( $query ) ) {
- return;
- }
-
- // Flag to check and make sure we are in a WooCommerce specific query
- $integrate = false;
-
- /**
- * Force ElasticPress if we are querying WC taxonomy
- */
- $tax_query = $query->get( 'tax_query', [] );
-
- $supported_taxonomies = array(
- 'product_cat',
- 'product_tag',
- 'product_type',
- 'product_visibility',
- 'product_shipping_class',
- );
+ public function output_feature_box_settings() {
+ $available = $this->is_orders_autosuggest_available();
+ $enabled = $this->is_orders_autosuggest_enabled();
+ ?>
+
+ get( $taxonomy, false );
-
- if ( ! empty( $term ) ) {
- $integrate = true;
-
- $tax_query[] = array(
- 'taxonomy' => $taxonomy,
- 'field' => 'slug',
- 'terms' => (array) $term,
- );
- }
+ if ( ! class_exists( 'WooCommerce' ) ) {
+ $status->code = 2;
+ $status->message = esc_html__( 'WooCommerce not installed.', 'elasticpress' );
}
- /**
- * Force ElasticPress if product post type query
- */
- $post_type = $query->get( 'post_type', false );
-
- // Act only on a defined subset of all indexable post types here
- $post_types = array(
- 'shop_order',
- 'shop_order_refund',
- 'product_variation',
- );
+ return $status;
+ }
- $is_main_post_type_archive = $query->is_main_query() && $query->is_post_type_archive( 'product' );
- $has_ep_integrate_set_true = isset( $query->query_vars['ep_integrate'] ) && filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN );
- if ( $is_main_post_type_archive || $has_ep_integrate_set_true ) {
- $post_types[] = 'product';
+ /**
+ * Determines whether or not ES should be integrating with the provided query
+ *
+ * @param \WP_Query $query Query we might integrate with
+ *
+ * @return bool
+ */
+ public function should_integrate_with_query( $query ) {
+ // Lets make sure this doesn't interfere with the CLI
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ return false;
}
- /**
- * Expands or contracts the post_types eligible for indexing.
- *
- * @hook ep_woocommerce_default_supported_post_types
- * @since 4.4.0
- * @param {array} $post_types Post types
- * @return {array} New post types
- */
- $supported_post_types = apply_filters( 'ep_woocommerce_default_supported_post_types', $post_types );
-
- $supported_post_types = array_intersect(
- $supported_post_types,
- Indexables::factory()->get( 'post' )->get_indexable_post_types()
- );
+ if ( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST ) {
+ return false;
+ }
- // For orders it queries an array of shop_order and shop_order_refund post types, hence an array_diff
- if ( ! empty( $post_type ) && ( in_array( $post_type, $supported_post_types, true ) || ( is_array( $post_type ) && ! array_diff( $post_type, $supported_post_types ) ) ) ) {
- $integrate = true;
+ if ( isset( $query->query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
+ return false;
}
/**
- * If we have a WooCommerce specific query, lets hook it to ElasticPress and make the query ElasticSearch friendly
+ * Filter to skip WP Query integration
+ *
+ * @hook ep_skip_query_integration
+ * @param {bool} $skip True to skip
+ * @param {WP_Query} $query WP Query to evaluate
+ * @return {bool} New skip value
*/
- if ( ! $integrate ) {
- return;
- }
-
- // Set tax_query again since we may have added things
- $query->set( 'tax_query', $tax_query );
-
- // Default to product if no post type is set
- if ( empty( $post_type ) ) {
- $post_type = 'product';
- $query->set( 'post_type', 'product' );
+ if ( apply_filters( 'ep_skip_query_integration', false, $query ) ) {
+ return false;
}
- // Handles the WC Top Rated Widget
- if ( has_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) ) ) {
- remove_filter( 'posts_clauses', array( WC()->query, 'order_by_rating_post_clauses' ) );
- $query->set( 'orderby', 'meta_value_num' );
- $query->set( 'meta_key', '_wc_average_rating' );
+ if ( ! Utils\is_integrated_request( $this->slug ) ) {
+ return false;
}
/**
- * WordPress have to be version 4.6 or newer to have "fields" support
- * since it requires the "posts_pre_query" filter.
- *
- * @see WP_Query::get_posts
+ * Do nothing for single product queries
*/
- $fields = $query->get( 'fields', false );
- if ( ! version_compare( get_bloginfo( 'version' ), '4.6', '>=' ) && ( 'ids' === $fields || 'id=>parent' === $fields ) ) {
- $query->set( 'fields', 'default' );
+ $product_name = $query->get( 'product', false );
+ if ( ! empty( $product_name ) || $query->is_single() ) {
+ return false;
}
/**
- * Handle meta queries
+ * ElasticPress does not yet support post_parent queries
*/
- $meta_query = $query->get( 'meta_query', [] );
- $meta_key = $query->get( 'meta_key', false );
- $meta_value = $query->get( 'meta_value', false );
-
- if ( ! empty( $meta_key ) && ! empty( $meta_value ) ) {
- $meta_query[] = array(
- 'key' => $meta_key,
- 'value' => $meta_value,
- );
-
- $query->set( 'meta_query', $meta_query );
+ $post_parent = $query->get( 'post_parent', false );
+ if ( ! empty( $post_parent ) ) {
+ return false;
}
/**
- * Make sure filters are suppressed
+ * If this is just a preview, let's not use Elasticsearch.
*/
- $query->query['suppress_filters'] = false;
- $query->set( 'suppress_filters', false );
-
- // Integrate with WooCommerce custom searches as well
- $search = $query->get( 'search' );
- if ( ! empty( $search ) ) {
- $s = $search;
- $query->set( 's', $s );
- } else {
- $s = $query->get( 's' );
+ if ( $query->get( 'preview', false ) ) {
+ return false;
}
- $query->query_vars['ep_integrate'] = true;
- $query->query['ep_integrate'] = true;
-
- if ( ! empty( $s ) ) {
-
- $searchable_post_types = $this->get_admin_searchable_post_types();
-
- if ( in_array( $post_type, $searchable_post_types, true ) ) {
- $default_search_fields = array( 'post_title', 'post_content', 'post_excerpt' );
- if ( ctype_digit( $s ) ) {
- $default_search_fields[] = 'ID';
- }
- $search_fields = $query->get( 'search_fields', $default_search_fields );
-
- $search_fields['meta'] = array_map(
- 'wc_clean',
- /**
- * Filter shop order meta fields to search for WooCommerce
- *
- * @hook shop_order_search_fields
- * @param {array} $fields Shop order fields
- * @return {array} New fields
- */
- apply_filters(
- 'shop_order_search_fields',
- array(
- '_order_key',
- '_billing_company',
- '_billing_address_1',
- '_billing_address_2',
- '_billing_city',
- '_billing_postcode',
- '_billing_country',
- '_billing_state',
- '_billing_email',
- '_billing_phone',
- '_shipping_address_1',
- '_shipping_address_2',
- '_shipping_city',
- '_shipping_postcode',
- '_shipping_country',
- '_shipping_state',
- '_billing_last_name',
- '_billing_first_name',
- '_shipping_first_name',
- '_shipping_last_name',
- '_items',
- )
- )
- );
-
- $query->set(
- 'search_fields',
- /**
- * Filter all the shop order fields to search for WooCommerce
- *
- * @hook ep_woocommerce_shop_order_search_fields
- * @since 4.0.0
- * @param {array} $fields Shop order fields
- * @param {WP_Query} $query WP Query
- * @return {array} New fields
- */
- apply_filters( 'ep_woocommerce_shop_order_search_fields', $search_fields, $query )
- );
- } elseif ( 'product' === $post_type && defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
- $search_fields = $query->get( 'search_fields', array( 'post_title', 'post_content', 'post_excerpt' ) );
-
- // Remove author_name from this search.
- $search_fields = $this->remove_author( $search_fields );
-
- foreach ( $search_fields as $field_key => $field ) {
- if ( 'author_name' === $field ) {
- unset( $search_fields[ $field_key ] );
- }
- }
-
- $search_fields['meta'] = ( ! empty( $search_fields['meta'] ) ) ? $search_fields['meta'] : [];
- $search_fields['taxonomies'] = ( ! empty( $search_fields['taxonomies'] ) ) ? $search_fields['taxonomies'] : [];
-
- $search_fields['meta'] = array_merge( $search_fields['meta'], array( '_sku' ) );
- $search_fields['taxonomies'] = array_merge( $search_fields['taxonomies'], array( 'category', 'post_tag', 'product_tag', 'product_cat' ) );
-
- $query->set( 'search_fields', $search_fields );
- }
- } else {
- /**
- * For default sorting by popularity (total_sales) and rating
- * Woocommerce doesn't set the orderby correctly.
- * These lines will check the meta_key and correct the orderby based on that.
- * And this won't run in search result and only run in main query
- */
- $meta_key = $query->get( 'meta_key', false );
- if ( $meta_key && $query->is_main_query() ) {
- switch ( $meta_key ) {
- case 'total_sales':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( 'total_sales' ) );
- $query->set( 'order', 'DESC' );
- break;
- case '_wc_average_rating':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_wc_average_rating' ) );
- $query->set( 'order', 'DESC' );
- break;
- }
- }
- }
+ return true;
+ }
+ /**
+ * Whether orders autosuggest is available or not
+ *
+ * @since 4.5.0
+ * @return boolean
+ */
+ public function is_orders_autosuggest_available() : bool {
/**
- * Set orderby and order for price/popularity when GET param not set
+ * Whether the autosuggest feature is available for non
+ * ElasticPress.io customers.
+ *
+ * @since 4.5.0
+ * @hook ep_woocommerce_orders_autosuggest_available
+ * @param {boolean} $available Whether the feature is available.
*/
- if ( isset( $query->query_vars['orderby'], $query->query_vars['order'] ) && $query->is_main_query() ) {
- switch ( $query->query_vars['orderby'] ) {
- case 'price':
- $query->set( 'order', $query->query_vars['order'] );
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
- break;
- case 'popularity':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( 'total_sales' ) );
- $query->set( 'order', 'DESC' );
- break;
- }
- }
+ return apply_filters( 'ep_woocommerce_orders_autosuggest_available', Utils\is_epio() );
+ }
- /**
- * Set orderby from GET param
- * Also make sure the orderby param affects only the main query
- */
- if ( ! empty( $_GET['orderby'] ) && $query->is_main_query() ) { // phpcs:ignore WordPress.Security.NonceVerification
- $orderby = sanitize_text_field( wp_unslash( $_GET['orderby'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
- switch ( $orderby ) { // phpcs:ignore WordPress.Security.NonceVerification
- case 'popularity':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( 'total_sales' ) );
- $query->set( 'order', 'DESC' );
- break;
- case 'price':
- $query->set( 'order', $query->get( 'order', 'ASC' ) );
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
- break;
- case 'price-desc':
- $query->set( 'order', 'DESC' );
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
- break;
- case 'rating':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_wc_average_rating' ) );
- $query->set( 'order', 'DESC' );
- break;
- case 'date':
- case 'title':
- case 'ID':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( $orderby ) );
- break;
- case 'sku':
- $query->set( 'orderby', $this->get_orderby_meta_mapping( '_sku' ) );
- break;
- default:
- $query->set( 'orderby', $this->get_orderby_meta_mapping( 'menu_order' ) ); // Order by menu and title.
- }
- }
+ /**
+ * Whether orders autosuggest is enabled or not
+ *
+ * @since 4.5.0
+ * @return boolean
+ */
+ public function is_orders_autosuggest_enabled() : bool {
+ return $this->is_orders_autosuggest_available() && '1' === $this->get_setting( 'orders' );
}
/**
- * Fetch the ES related meta mapping for orderby
+ * DEPRECATED. Translate args to ElasticPress compat format. This is the meat of what the feature does
+ *
+ * @param \WP_Query $query WP Query
+ * @since 2.1
+ */
+ public function translate_args( $query ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->translate_args() OR \ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->translate_args()" );
+ $this->products->translate_args( $query );
+ $this->orders->translate_args( $query );
+ }
+
+ /**
+ * DEPRECATED. Fetch the ES related meta mapping for orderby
*
* @param array $meta_key The meta key to get the mapping for.
* @since 2.1
* @return string The mapped meta key.
*/
public function get_orderby_meta_mapping( $meta_key ) {
- /**
- * Filter WooCommerce to Elasticsearch meta mapping
- *
- * @hook orderby_meta_mapping
- * @param {array} $mapping Meta mapping
- * @return {array} New mapping
- */
- $mapping = apply_filters(
- 'orderby_meta_mapping',
- array(
- 'ID' => 'ID',
- 'title' => 'title date',
- 'menu_order' => 'menu_order title date',
- 'menu_order title' => 'menu_order title date',
- 'total_sales' => 'meta.total_sales.double date',
- '_wc_average_rating' => 'meta._wc_average_rating.double date',
- '_price' => 'meta._price.double date',
- '_sku' => 'meta._sku.value.sortable date',
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->get_orderby_meta_mapping()" );
+ return $this->products->get_orderby_meta_mapping( $meta_key );
+ }
+
+ /**
+ * DEPRECATED. Remove the author_name from search fields.
+ *
+ * @param array $search_fields Array of search fields.
+ * @since 3.0
+ * @return array
+ */
+ public function remove_author( $search_fields ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->remove_author()" );
+ return $this->products->remove_author( $search_fields );
+ }
+
+ /**
+ * DEPRECATED. Index Woocommerce meta
+ *
+ * @param array $meta Existing post meta.
+ * @param array $post Post arguments array.
+ * @since 2.1
+ * @return array
+ */
+ public function whitelist_meta_keys( $meta, $post ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->allow_meta_keys() AND/OR \ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->allow_meta_keys()" );
+ return array_unique(
+ array_merge(
+ $this->products->allow_meta_keys( $meta ),
+ $this->orders->allow_meta_keys( $meta )
)
);
+ }
- if ( isset( $mapping[ $meta_key ] ) ) {
- return $mapping[ $meta_key ];
- }
+ /**
+ * DEPRECATED. Make sure all loop shop post ins are IDS. We have to pass post objects here since we override
+ * the fields=>id query for the layered filter nav query
+ *
+ * @param array $posts Post object array.
+ * @since 2.1
+ * @return array
+ */
+ public function convert_post_object_to_id( $posts ) {
+ _doing_it_wrong( __METHOD__, 'This filter was removed from WooCommerce and will be removed from ElasticPress in a future release.', '4.5.0' );
+ return $posts;
+ }
- return 'date';
+ /**
+ * DEPRECATED. Index Woocommerce taxonomies
+ *
+ * @param array $taxonomies Index taxonomies array.
+ * @param array $post Post properties array.
+ * @since 2.1
+ * @return array
+ */
+ public function whitelist_taxonomies( $taxonomies, $post ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->sync_taxonomies()" );
+ return $this->products->sync_taxonomies( $taxonomies );
}
+ /**
+ * DEPRECATED. Disallow duplicated ES queries on Orders page.
+ *
+ * @since 2.4
+ *
+ * @param array $value Original filter values.
+ * @param WP_Query $query WP_Query
+ *
+ * @return array
+ */
+ public function disallow_duplicated_query( $value, $query ) {
+ _doing_it_wrong( __METHOD__, 'This filter was removed from WooCommerce and will be removed from ElasticPress in a future release.', '4.5.0' );
+
+ return $value;
+ }
/**
- * Returns the WooCommerce-oriented post types in admin that EP will search
+ * DEPRECATED. Returns the WooCommerce-oriented post types in admin that EP will search
*
* @since 4.4.0
* @return mixed|void
*/
public function get_admin_searchable_post_types() {
- $searchable_post_types = array( 'shop_order' );
-
- /**
- * Filter admin searchable WooCommerce post types
- *
- * @hook ep_woocommerce_admin_searchable_post_types
- * @since 4.4.0
- * @param {array} $post_types Post types
- * @return {array} New post types
- */
- return apply_filters( 'ep_woocommerce_admin_searchable_post_types', $searchable_post_types );
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->get_admin_searchable_post_types()" );
+ return $this->orders->get_admin_searchable_post_types();
}
/**
- * Make search coupons don't go through ES
+ * DEPRECATED. Make search coupons don't go through ES
*
* @param bool $enabled Coupons enabled or not
* @param WP_Query $query WP Query
@@ -621,19 +410,12 @@ public function get_admin_searchable_post_types() {
* @return bool
*/
public function blacklist_coupons( $enabled, $query ) {
- if ( is_admin() ) {
- return $enabled;
- }
-
- if ( 'shop_coupon' === $query->get( 'post_type' ) && empty( $query->query_vars['ep_integrate'] ) ) {
- return false;
- }
-
- return $enabled;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->disallow_coupons()" );
+ return $this->disallow_coupons( $enabled, $query );
}
/**
- * Allow order creations on the front end to get synced
+ * DEPRECATED. Allow order creations on the front end to get synced
*
* @since 2.1
* @param bool $override Original order perms check value
@@ -641,17 +423,12 @@ public function blacklist_coupons( $enabled, $query ) {
* @return bool
*/
public function bypass_order_permissions_check( $override, $post_id ) {
- $searchable_post_types = $this->get_admin_searchable_post_types();
-
- if ( in_array( get_post_type( $post_id ), $searchable_post_types, true ) ) {
- return true;
- }
-
- return $override;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->price_filter()" );
+ return $this->orders->bypass_order_permissions_check( $override, $post_id );
}
/**
- * Sets woocommerce meta search fields to an empty array if we are integrating the main query with ElasticSearch
+ * DEPRECATED. Sets woocommerce meta search fields to an empty array if we are integrating the main query with ElasticSearch
*
* Woocommerce calls this action as part of its own callback on parse_query. We add this filter only if the query
* is integrated with ElasticSearch.
@@ -661,29 +438,12 @@ public function bypass_order_permissions_check( $override, $post_id ) {
* @param \WP_Query $query Current query
*/
public function maybe_hook_woocommerce_search_fields( $query ) {
- global $pagenow, $wp, $wc_list_table, $wp_filter;
-
- if ( ! $this->should_integrate_with_query( $query ) ) {
- return;
- }
-
- /**
- * Determines actions to be applied, or removed, if doing a WooCommerce serarch
- *
- * @hook ep_woocommerce_hook_search_fields
- * @since 4.4.0
- */
- do_action( 'ep_woocommerce_hook_search_fields' );
-
- if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] || ! isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
- return;
- }
-
- remove_action( 'parse_query', [ $wc_list_table, 'search_custom_fields' ] );
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->maybe_hook_woocommerce_search_fields()" );
+ return $this->orders->maybe_hook_woocommerce_search_fields( $query );
}
/**
- * Enhance WooCommerce search order by order id, email, phone number, name, etc..
+ * DEPRECATED. Enhance WooCommerce search order by order id, email, phone number, name, etc..
* What this function does:
* 1. Reverse the woocommerce shop_order_search_custom_fields query
* 2. If the search key is integer and it is an Order Id, just query with post__in
@@ -693,295 +453,82 @@ public function maybe_hook_woocommerce_search_fields( $query ) {
* @since 2.3
*/
public function search_order( $wp ) {
- if ( ! $this->should_integrate_with_query( $wp ) ) {
- return;
- }
-
- global $pagenow;
-
- $searchable_post_types = $this->get_admin_searchable_post_types();
-
- if ( 'edit.php' !== $pagenow || empty( $wp->query_vars['post_type'] ) || ! in_array( $wp->query_vars['post_type'], $searchable_post_types, true ) ||
- ( empty( $wp->query_vars['s'] ) && empty( $wp->query_vars['shop_order_search'] ) ) ) {
- return;
- }
-
- // phpcs:disable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
- if ( isset( $_GET['s'] ) ) {
- $search_key_safe = str_replace( array( 'Order #', '#' ), '', wc_clean( $_GET['s'] ) );
- unset( $wp->query_vars['post__in'] );
- $wp->query_vars['s'] = $search_key_safe;
- }
- // phpcs:enable WordPress.Security.NonceVerification, WordPress.Security.ValidatedSanitizedInput
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->search_order()" );
+ return $this->orders->search_order( $wp );
}
/**
- * Add order items as a searchable string.
+ * DEPRECATED. Add order items as a searchable string.
*
* This mimics how WooCommerce currently does in the order_itemmeta
* table. They combine the titles of the products and put them in a
* meta field called "Items".
*
- * @since 2.4
- *
- * @param array $post_args Post arguments
- * @param string|int $post_id Post id
- *
- * @return array
- */
- public function add_order_items_search( $post_args, $post_id ) {
- $searchable_post_types = $this->get_admin_searchable_post_types();
-
- // Make sure it is only WooCommerce orders we touch.
- if ( ! in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
- return $post_args;
- }
-
- $post_indexable = Indexables::factory()->get( 'post' );
-
- // Get order items.
- $order = wc_get_order( $post_id );
- $item_meta = [];
- foreach ( $order->get_items() as $delta => $product_item ) {
- // WooCommerce 3.x uses WC_Order_Item_Product instance while 2.x an array
- if ( is_object( $product_item ) && method_exists( $product_item, 'get_name' ) ) {
- $item_meta['_items'][] = $product_item->get_name( 'edit' );
- } elseif ( is_array( $product_item ) && isset( $product_item['name'] ) ) {
- $item_meta['_items'][] = $product_item['name'];
- }
- }
-
- // Prepare order items.
- $item_meta['_items'] = empty( $item_meta['_items'] ) ? '' : implode( '|', $item_meta['_items'] );
- $post_args['meta'] = array_merge( $post_args['meta'], $post_indexable->prepare_meta_types( $item_meta ) );
-
- return $post_args;
- }
-
- /**
- * Add WooCommerce Product Attributes to EP Facets.
- *
- * @param array $taxonomies Taxonomies array
- * @return array
- */
- public function add_product_attributes( $taxonomies = [] ) {
- $attribute_names = wc_get_attribute_taxonomy_names();
-
- foreach ( $attribute_names as $name ) {
- if ( ! taxonomy_exists( $name ) ) {
- continue;
- }
- $taxonomies[ $name ] = get_taxonomy( $name );
- }
-
- return $taxonomies;
- }
-
- /**
- * Add WooCommerce Fields to the Weighting Dashboard.
- *
- * @since 3.x
- *
- * @param array $fields Current weighting fields.
- * @param string $post_type Current post type.
- * @return array New fields.
- */
- public function add_product_attributes_to_weighting( $fields, $post_type ) {
- if ( 'product' === $post_type ) {
- if ( ! empty( $fields['attributes']['children']['author_name'] ) ) {
- unset( $fields['attributes']['children']['author_name'] );
- }
-
- $sku_key = 'meta._sku.value';
-
- $fields['attributes']['children'][ $sku_key ] = array(
- 'key' => $sku_key,
- 'label' => __( 'SKU', 'elasticpress' ),
- );
-
- $variations_skus_key = 'meta._variations_skus.value';
-
- $fields['attributes']['children'][ $variations_skus_key ] = array(
- 'key' => $variations_skus_key,
- 'label' => __( 'Variations SKUs', 'elasticpress' ),
- );
- }
- return $fields;
- }
-
- /**
- * Add WooCommerce Fields to the default values of the Weighting Dashboard.
- *
- * @since 3.x
- *
- * @param array $defaults Default values for the post type.
- * @param string $post_type Current post type.
- * @return array
- */
- public function add_product_default_post_type_weights( $defaults, $post_type ) {
- if ( 'product' === $post_type ) {
- if ( ! empty( $defaults['author_name'] ) ) {
- unset( $defaults['author_name'] );
- }
-
- $defaults['meta._sku.value'] = array(
- 'enabled' => true,
- 'weight' => 1,
- );
-
- $defaults['meta._variations_skus.value'] = array(
- 'enabled' => true,
- 'weight' => 1,
- );
- }
- return $defaults;
- }
-
- /**
- * Add WC post type to autosuggest
- *
- * @param array $post_types Array of post types (e.g. post, page).
- * @since 2.6
- * @return array
- */
- public function suggest_wc_add_post_type( $post_types ) {
- if ( ! in_array( 'product', $post_types, true ) ) {
- $post_types[] = 'product';
- }
-
- return $post_types;
- }
-
- /**
- * Setup all feature filters
- *
- * @since 2.1
+ * @since 2.4
+ *
+ * @param array $post_args Post arguments
+ * @param string|int $post_id Post id
+ *
+ * @return array
*/
- public function setup() {
- if ( ! function_exists( 'WC' ) ) {
- return;
- }
-
- add_action( 'ep_formatted_args', [ $this, 'price_filter' ], 10, 3 );
- add_filter( 'ep_sync_insert_permissions_bypass', [ $this, 'bypass_order_permissions_check' ], 10, 2 );
- add_filter( 'ep_integrate_search_queries', [ $this, 'blacklist_coupons' ], 10, 2 );
- add_filter( 'ep_prepare_meta_allowed_protected_keys', [ $this, 'whitelist_meta_keys' ], 10, 2 );
- add_filter( 'woocommerce_layered_nav_query_post_ids', [ $this, 'convert_post_object_to_id' ], 10, 4 );
- add_filter( 'woocommerce_unfiltered_product_ids', [ $this, 'convert_post_object_to_id' ], 10, 4 );
- add_filter( 'ep_sync_taxonomies', [ $this, 'whitelist_taxonomies' ], 10, 2 );
- add_filter( 'ep_post_sync_args_post_prepare_meta', [ $this, 'add_order_items_search' ], 20, 2 );
- add_filter( 'ep_pc_skip_post_content_cleanup', [ $this, 'keep_order_fields' ], 20, 2 );
- add_action( 'pre_get_posts', [ $this, 'translate_args' ], 11, 1 );
- add_action( 'ep_wp_query_search_cached_posts', [ $this, 'disallow_duplicated_query' ], 10, 2 );
- add_action( 'parse_query', [ $this, 'maybe_hook_woocommerce_search_fields' ], 1 );
- add_action( 'parse_query', [ $this, 'search_order' ], 11 );
- add_filter( 'ep_term_suggest_post_type', [ $this, 'suggest_wc_add_post_type' ] );
- add_filter( 'ep_facet_include_taxonomies', [ $this, 'add_product_attributes' ] );
- add_filter( 'ep_weighting_fields_for_post_type', [ $this, 'add_product_attributes_to_weighting' ], 10, 2 );
- add_filter( 'ep_weighting_default_post_type_weights', [ $this, 'add_product_default_post_type_weights' ], 10, 2 );
- add_filter( 'ep_prepare_meta_data', [ $this, 'add_variations_skus_meta' ], 10, 2 );
- add_filter( 'request', [ $this, 'admin_product_list_request_query' ], 9 );
-
- // Custom product ordering
- add_action( 'ep_admin_notices', [ $this, 'maybe_display_notice_about_product_ordering' ] );
- add_action( 'woocommerce_after_product_ordering', [ $this, 'action_sync_on_woocommerce_sort_single' ], 10, 2 );
-
- // Orders Autosuggest feature.
- if ( $this->is_orders_autosuggest_enabled() ) {
- $this->orders->setup();
- }
-
- // Add WooCommerce Settings for Weight results by date
- add_action( 'ep_weight_settings_after_search', [ $this, 'add_weight_settings_search' ] );
- // Modify decaying based on WooCommerce Settings
- add_filter( 'ep_is_decaying_enabled', [ $this, 'maybe_disable_decaying' ], 10, 3 );
+ public function add_order_items_search( $post_args, $post_id ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->add_order_items_search()" );
+ return $this->orders->add_order_items_search( $post_args, $post_id );
}
/**
- * Output feature box long
+ * DEPRECATED. Add WooCommerce Product Attributes to EP Facets.
*
- * @since 2.1
+ * @param array $taxonomies Taxonomies array
+ * @return array
*/
- public function output_feature_box_long() {
- ?>
-
- get_registered_feature( 'woocommerce' )->products->add_product_attributes()" );
+ return $this->products->add_product_attributes( $taxonomies );
}
/**
- * Dashboard WooCommerce settings
+ * DEPRECATED. Add WooCommerce Fields to the Weighting Dashboard.
*
- * @since 4.5.0
+ * @since 3.x
+ *
+ * @param array $fields Current weighting fields.
+ * @param string $post_type Current post type.
+ * @return array New fields.
*/
- public function output_feature_box_settings() {
- $available = $this->is_orders_autosuggest_available();
- $enabled = $this->is_orders_autosuggest_enabled();
- ?>
-
- get_registered_feature( 'woocommerce' )->products->add_product_attributes_to_weighting()" );
+ return $this->products->add_product_attributes_to_weighting( $fields, $post_type );
}
/**
- * Remove the author_name from search fields.
+ * DEPRECATED. Add WooCommerce Fields to the default values of the Weighting Dashboard.
*
- * @param array $search_fields Array of search fields.
- * @since 3.0
+ * @since 3.x
+ *
+ * @param array $defaults Default values for the post type.
+ * @param string $post_type Current post type.
* @return array
*/
- public function remove_author( $search_fields ) {
- foreach ( $search_fields as $field_key => $field ) {
- if ( 'author_name' === $field ) {
- unset( $search_fields[ $field_key ] );
- }
- }
-
- return $search_fields;
+ public function add_product_default_post_type_weights( $defaults, $post_type ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->add_product_default_post_type_weights()" );
+ return $this->products->add_product_default_post_type_weights( $defaults, $post_type );
}
/**
- * Determine WC feature reqs status
+ * DEPRECATED. Add WC post type to autosuggest
*
- * @since 2.2
- * @return EP_Feature_Requirements_Status
+ * @param array $post_types Array of post types (e.g. post, page).
+ * @since 2.6
+ * @return array
*/
- public function requirements_status() {
- $status = new FeatureRequirementsStatus( 0 );
-
- if ( ! class_exists( 'WooCommerce' ) ) {
- $status->code = 2;
- $status->message = esc_html__( 'WooCommerce not installed.', 'elasticpress' );
- }
-
- return $status;
+ public function suggest_wc_add_post_type( $post_types ) {
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->suggest_wc_add_post_type()" );
+ return $this->products->suggest_wc_add_post_type( $post_types );
}
/**
- * Modifies main query to allow filtering by price with WooCommerce "Filter by price" widget.
+ * DEPRECATED. Modifies main query to allow filtering by price with WooCommerce "Filter by price" widget.
*
* @param array $args ES args
* @param array $query_args WP_Query args
@@ -990,63 +537,12 @@ public function requirements_status() {
* @return array
*/
public function price_filter( $args, $query_args, $query ) {
- // Only can use widget on main query
- if ( ! $query->is_main_query() ) {
- return $args;
- }
-
- // Only can use widget on shop, product taxonomy, or search
- if ( ! is_shop() && ! is_product_taxonomy() && ! is_search() ) {
- return $args;
- }
-
- // phpcs:disable WordPress.Security.NonceVerification
- if ( empty( $_GET['min_price'] ) && empty( $_GET['max_price'] ) ) {
- return $args;
- }
-
- $min_price = ! empty( $_GET['min_price'] ) ? sanitize_text_field( wp_unslash( $_GET['min_price'] ) ) : null;
- $max_price = ! empty( $_GET['max_price'] ) ? sanitize_text_field( wp_unslash( $_GET['max_price'] ) ) : null;
- // phpcs:enable WordPress.Security.NonceVerification
-
- if ( $query->is_search() ) {
- /**
- * This logic is iffy but the WC price filter widget is not intended for use with search anyway
- */
- $old_query = $args['query']['bool'];
- unset( $args['query']['bool']['should'] );
-
- if ( ! empty( $min_price ) ) {
- $args['query']['bool']['must'][0]['range']['meta._price.long']['gte'] = $min_price;
- }
-
- if ( ! empty( $max_price ) ) {
- $args['query']['bool']['must'][0]['range']['meta._price.long']['lte'] = $max_price;
- }
-
- $args['query']['bool']['must'][0]['range']['meta._price.long']['boost'] = 2.0;
- $args['query']['bool']['must'][1]['bool'] = $old_query;
- } else {
- unset( $args['query']['match_all'] );
-
- $args['query']['range']['meta._price.long']['gte'] = ! empty( $min_price ) ? $min_price : 0;
-
- if ( ! empty( $min_price ) ) {
- $args['query']['range']['meta._price.long']['gte'] = $min_price;
- }
-
- if ( ! empty( $max_price ) ) {
- $args['query']['range']['meta._price.long']['lte'] = $max_price;
- }
-
- $args['query']['range']['meta._price.long']['boost'] = 2.0;
- }
-
- return $args;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->price_filter()" );
+ return $this->products->price_filter( $args, $query_args, $query );
}
/**
- * Prevent order fields from being removed.
+ * DEPRECATED. Prevent order fields from being removed.
*
* When Protected Content is enabled, all posts with password have their content removed.
* This can't happen for orders, as the order key is added in that field.
@@ -1059,17 +555,12 @@ public function price_filter( $args, $query_args, $query ) {
* @return bool
*/
public function keep_order_fields( $skip, $post_args ) {
- $searchable_post_types = $this->get_admin_searchable_post_types();
-
- if ( in_array( $post_args['post_type'], $searchable_post_types, true ) ) {
- return true;
- }
-
- return $skip;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders->keep_order_fields()" );
+ return $this->orders->keep_order_fields( $skip, $post_args );
}
/**
- * Add a new `_variations_skus` meta field to the product to be indexed in Elasticsearch.
+ * DEPRECATED. Add a new `_variations_skus` meta field to the product to be indexed in Elasticsearch.
*
* @since 4.2.0
* @param array $post_meta Post meta
@@ -1077,39 +568,12 @@ public function keep_order_fields( $skip, $post_args ) {
* @return array
*/
public function add_variations_skus_meta( $post_meta, $post ) {
- if ( 'product' !== $post->post_type ) {
- return $post_meta;
- }
-
- $product = wc_get_product( $post );
- if ( ! $product ) {
- return $post_meta;
- }
-
- $variations_ids = $product->get_children();
-
- $post_meta['_variations_skus'] = array_reduce(
- $variations_ids,
- function ( $variations_skus, $current_id ) {
- $variation = wc_get_product( $current_id );
- if ( ! $variation || ! $variation->exists() ) {
- return $variations_skus;
- }
- $variation_sku = $variation->get_sku();
- if ( ! $variation_sku ) {
- return $variations_skus;
- }
- $variations_skus[] = $variation_sku;
- return $variations_skus;
- },
- []
- );
-
- return $post_meta;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->add_variations_skus_meta()" );
+ return $this->products->add_variations_skus_meta( $post_meta, $post );
}
/**
- * Integrate ElasticPress with the WooCommerce Admin Product List.
+ * DEPRECATED. Integrate ElasticPress with the WooCommerce Admin Product List.
*
* WooCommerce uses its `WC_Admin_List_Table_Products` class to control that screen. This
* function adds all necessary hooks to bypass the default behavior and integrate with ElasticPress.
@@ -1122,277 +586,57 @@ function ( $variations_skus, $current_id ) {
* @return array
*/
public function admin_product_list_request_query( $query_vars ) {
- global $typenow, $wc_list_table;
-
- // Return if not in the correct screen.
- if ( ! is_a( $wc_list_table, 'WC_Admin_List_Table_Products' ) || 'product' !== $typenow ) {
- return $query_vars;
- }
-
- // Return if admin WP_Query integration is not turned on, i.e., Protect Content is not enabled.
- if ( ! has_filter( 'ep_admin_wp_query_integration', '__return_true' ) ) {
- return $query_vars;
- }
-
- /**
- * Filter to skip integration with WooCommerce Admin Product List.
- *
- * @hook ep_woocommerce_integrate_admin_products_list
- * @since 4.2.0
- * @param {bool} $integrate True to integrate, false to preserve original behavior. Defaults to true.
- * @param {array} $query_vars Query vars.
- * @return {bool} New integrate value
- */
- if ( ! apply_filters( 'ep_woocommerce_integrate_admin_products_list', true, $query_vars ) ) {
- return $query_vars;
- }
-
- add_action( 'pre_get_posts', [ $this, 'translate_args_admin_products_list' ], 12 );
-
- // This short-circuits WooCommerce search for product IDs.
- add_filter( 'woocommerce_product_pre_search_products', '__return_empty_array' );
-
- return $query_vars;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->admin_product_list_request_query()" );
+ return $this->products->admin_product_list_request_query( $query_vars );
}
/**
- * Apply the necessary changes to WP_Query in WooCommerce Admin Product List.
+ * DEPRECATED. Apply the necessary changes to WP_Query in WooCommerce Admin Product List.
*
* @param WP_Query $query The WP Query being executed.
*/
public function translate_args_admin_products_list( $query ) {
- // The `translate_args()` method sets it to `true` if we should integrate it.
- if ( ! $query->get( 'ep_integrate', false ) ) {
- return;
- }
-
- // WooCommerce unsets the search term right after using it to fetch product IDs. Here we add it back.
- $search_term = ! empty( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
- if ( ! empty( $search_term ) ) {
- $query->set( 's', sanitize_text_field( $search_term ) ); // phpcs:ignore WordPress.Security.NonceVerification
-
- /**
- * Filter the fields used in WooCommerce Admin Product Search.
- *
- * ```
- * add_filter(
- * 'ep_woocommerce_admin_products_list_search_fields',
- * function ( $wc_admin_search_fields ) {
- * $wc_admin_search_fields['meta'][] = 'custom_field';
- * return $wc_admin_search_fields;
- * }
- * );
- * ```
- *
- * @hook ep_woocommerce_admin_products_list_search_fields
- * @since 4.2.0
- * @param {array} $wc_admin_search_fields Fields to be used in the WooCommerce Admin Product Search
- * @return {array} New fields
- */
- $search_fields = apply_filters(
- 'ep_woocommerce_admin_products_list_search_fields',
- [
- 'post_title',
- 'post_content',
- 'post_excerpt',
- 'meta' => [
- '_sku',
- '_variations_skus',
- ],
- ]
- );
-
- $query->set( 'search_fields', $search_fields );
- }
-
- // Sets the meta query for `product_type` if needed. Also removed from the WP_Query by WC in `WC_Admin_List_Table_Products::query_filters()`.
- $product_type_query = $query->get( 'product_type', '' );
- $product_type_url = ! empty( $_GET['product_type'] ) ? sanitize_text_field( wp_unslash( $_GET['product_type'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
- $allowed_prod_types = [ 'virtual', 'downloadable' ];
- if ( empty( $product_type_query ) && ! empty( $product_type_url ) && in_array( $product_type_url, $allowed_prod_types, true ) ) {
- $meta_query = $query->get( 'meta_query', [] );
- $meta_query[] = [
- 'key' => "_{$product_type_url}",
- 'value' => 'yes',
- ];
- $query->set( 'meta_query', $meta_query );
- }
-
- // Sets the meta query for `stock_status` if needed.
- $stock_status_query = $query->get( 'stock_status', '' );
- $stock_status_url = ! empty( $_GET['stock_status'] ) ? sanitize_text_field( wp_unslash( $_GET['stock_status'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification
- $allowed_stock_status = [ 'instock', 'outofstock', 'onbackorder' ];
- if ( empty( $stock_status_query ) && ! empty( $stock_status_url ) && in_array( $stock_status_url, $allowed_stock_status, true ) ) {
- $meta_query = $query->get( 'meta_query', [] );
- $meta_query[] = [
- 'key' => '_stock_status',
- 'value' => $stock_status_url,
- ];
- $query->set( 'meta_query', $meta_query );
- }
- }
-
- /**
- * Determines whether or not ES should be integrating with the provided query
- *
- * @param \WP_Query $query Query we might integrate with
- *
- * @return bool
- */
- protected function should_integrate_with_query( $query ) {
- // Lets make sure this doesn't interfere with the CLI
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
- return false;
- }
-
- if ( defined( 'WC_API_REQUEST' ) && WC_API_REQUEST ) {
- return false;
- }
-
- if ( isset( $query->query_vars['ep_integrate'] ) && ! filter_var( $query->query_vars['ep_integrate'], FILTER_VALIDATE_BOOLEAN ) ) {
- return false;
- }
-
- /**
- * Filter to skip WP Query integration
- *
- * @hook ep_skip_query_integration
- * @param {bool} $skip True to skip
- * @param {WP_Query} $query WP Query to evaluate
- * @return {bool} New skip value
- */
- if ( apply_filters( 'ep_skip_query_integration', false, $query ) ) {
- return false;
- }
-
- if ( ! Utils\is_integrated_request( $this->slug ) ) {
- return false;
- }
-
- /**
- * Do nothing for single product queries
- */
- $product_name = $query->get( 'product', false );
- if ( ! empty( $product_name ) || $query->is_single() ) {
- return false;
- }
-
- /**
- * ElasticPress does not yet support post_parent queries
- */
- $post_parent = $query->get( 'post_parent', false );
- if ( ! empty( $post_parent ) ) {
- return false;
- }
-
- /**
- * If this is just a preview, let's not use Elasticsearch.
- */
- if ( $query->get( 'preview', false ) ) {
- return false;
- }
-
- return true;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->price_filter()" );
+ $this->products->translate_args_admin_products_list( $query );
}
/**
- * Depending on the number of products display an admin notice in the custom sort screen for WooCommerce Products
+ * DEPRECATED. Depending on the number of products display an admin notice in the custom sort screen for WooCommerce Products
*
* @since 4.4.0
* @param array $notices Current ElasticPress admin notices
* @return array
*/
public function maybe_display_notice_about_product_ordering( $notices ) {
- global $pagenow, $wp_query;
-
- /**
- * Make sure we're on edit.php in admin dashboard.
- */
- if ( ! is_admin() || 'edit.php' !== $pagenow || empty( $wp_query->query['orderby'] ) || 'menu_order title' !== $wp_query->query['orderby'] ) {
- return $notices;
- }
-
- $documents_per_page_sync = IndexHelper::factory()->get_index_default_per_page();
- if ( $documents_per_page_sync >= $wp_query->found_posts ) {
- return $notices;
- }
-
- $notices['woocommerce_custom_sort'] = [
- 'html' => sprintf(
- /* translators: Sync Page URL */
- __( 'Due to the number of products in the site, you will need to resync after applying a custom sort order.', 'elasticpress' ),
- Utils\get_sync_url()
- ),
- 'type' => 'warning',
- 'dismiss' => true,
- ];
-
- return $notices;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->maybe_display_notice_about_product_ordering()" );
+ return $this->products->maybe_display_notice_about_product_ordering( $notices );
}
/**
- * Conditionally resync products after applying a custom order.
+ * DEPRECATED. Conditionally resync products after applying a custom order.
*
* @since 4.4.0
* @param int $sorting_id ID of post dragged and dropped
* @param array $menu_orders Post IDs and their new menu_order value
*/
public function action_sync_on_woocommerce_sort_single( $sorting_id, $menu_orders ) {
-
- $documents_per_page_sync = IndexHelper::factory()->get_index_default_per_page();
- if ( $documents_per_page_sync < count( $menu_orders ) ) {
- return;
- }
-
- $sync_manager = Indexables::factory()->get( 'post' )->sync_manager;
- foreach ( $menu_orders as $post_id => $order ) {
- $sync_manager->add_to_queue( $post_id );
- }
- }
-
- /**
- * Whether orders autosuggest is available or not
- *
- * @since 4.5.0
- * @return boolean
- */
- public function is_orders_autosuggest_available() : bool {
- /**
- * Whether the autosuggest feature is available for non
- * ElasticPress.io customers.
- *
- * @since 4.5.0
- * @hook ep_woocommerce_orders_autosuggest_available
- * @param {boolean} $available Whether the feature is available.
- */
- return apply_filters( 'ep_woocommerce_orders_autosuggest_available', Utils\is_epio() );
- }
-
- /**
- * Whether orders autosuggest is enabled or not
- *
- * @since 4.5.0
- * @return boolean
- */
- public function is_orders_autosuggest_enabled() : bool {
- return $this->is_orders_autosuggest_available() && '1' === $this->get_setting( 'orders' );
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->action_sync_on_woocommerce_sort_single()" );
+ return $this->products->action_sync_on_woocommerce_sort_single( $sorting_id, $menu_orders );
}
/**
- * Add weight by date settings related to WooCommerce
+ * DEPRECATED. Add weight by date settings related to WooCommerce
*
* @since 4.6.0
* @param array $settings Current settings.
*/
public function add_weight_settings_search( $settings ) {
- ?>
-
-
- get_registered_feature( 'woocommerce' )->products->add_weight_settings_search()" );
+ $this->products->add_weight_settings_search( $settings );
}
/**
- * Conditionally disable decaying by date based on WooCommerce Decay settings.
+ * DEPRECATED. Conditionally disable decaying by date based on WooCommerce Decay settings.
*
* @since 4.6.0
* @param bool $is_decaying_enabled Whether decay by date is enabled or not
@@ -1401,25 +645,7 @@ public function add_weight_settings_search( $settings ) {
* @return bool
*/
public function maybe_disable_decaying( $is_decaying_enabled, $settings, $args ) {
- if ( ! in_array( $settings['decaying_enabled'], [ 'disabled_only_products', 'disabled_includes_products' ], true ) ) {
- return $is_decaying_enabled;
- }
-
- if ( ! isset( $args['post_type'] ) || ! in_array( 'product', (array) $args['post_type'], true ) ) {
- return $is_decaying_enabled;
- }
-
- $post_types = (array) $args['post_type'];
-
- if ( 'disabled_only_products' === $settings['decaying_enabled'] && count( $post_types ) > 1 ) {
- return $is_decaying_enabled;
- }
-
- if ( 'disabled_includes_products' === $settings['decaying_enabled'] && ! in_array( 'product', $post_types, true ) ) {
- return $is_decaying_enabled;
- }
-
- return false;
+ _deprecated_function( __METHOD__, '4.7.0', "\ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products->maybe_disable_decaying()" );
+ return $this->products->maybe_disable_decaying( $is_decaying_enabled, $settings, $args );
}
-
}
diff --git a/includes/classes/StatusReport/ElasticPressIo.php b/includes/classes/StatusReport/ElasticPressIo.php
index 4ebd236952..31f67f6096 100644
--- a/includes/classes/StatusReport/ElasticPressIo.php
+++ b/includes/classes/StatusReport/ElasticPressIo.php
@@ -248,7 +248,7 @@ protected function get_orders_search_field() : array {
}
$woocommerce_feature = \ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
- $template = $woocommerce_feature->orders->get_search_template();
+ $template = $woocommerce_feature->orders_autosuggest->get_search_template();
if ( is_wp_error( $template ) ) {
return [
diff --git a/tests/php/features/WooCommerce/TestWooCommerce.php b/tests/php/features/WooCommerce/TestWooCommerce.php
new file mode 100644
index 0000000000..886f90ba68
--- /dev/null
+++ b/tests/php/features/WooCommerce/TestWooCommerce.php
@@ -0,0 +1,357 @@
+suppress_errors();
+
+ $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
+
+ wp_set_current_user( $admin_id );
+
+ ElasticPress\Elasticsearch::factory()->delete_all_indices();
+ ElasticPress\Indexables::factory()->get( 'post' )->put_mapping();
+
+ ElasticPress\Indexables::factory()->get( 'post' )->sync_manager->sync_queue = [];
+
+ $this->setup_test_post_type();
+ }
+
+ /**
+ * Clean up after each test. Reset our mocks
+ *
+ * @since 2.1
+ * @group woocommerce
+ */
+ public function tear_down() {
+ parent::tear_down();
+
+ $this->fired_actions = array();
+ }
+
+ /**
+ * Test search integration is on in general for product searches
+ *
+ * @since 2.1
+ * @group woocommerce
+ */
+ public function testSearchOnAllFrontEnd() {
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 's' => 'findme',
+ 'post_type' => 'product',
+ );
+
+ $query = new \WP_Query( $args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ }
+
+ /**
+ * Tests the search query for a shop_coupon.
+ *
+ * @since 4.4.1
+ * @group woocommerce
+ */
+ public function testSearchQueryForCoupon() {
+
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ // ensures that the search query doesn't use Elasticsearch.
+ $query = new \WP_Query(
+ [
+ 'post_type' => 'shop_coupon',
+ 's' => 'test-coupon',
+ ]
+ );
+ $this->assertNull( $query->elasticsearch_success );
+
+ // ensures that the search query doesn't use Elasticsearch when ep_integrate set to false.
+ $query = new \WP_Query(
+ [
+ 'post_type' => 'shop_coupon',
+ 's' => 'test-coupon',
+ 'ep_integrate' => false,
+ ]
+ );
+ $this->assertNull( $query->elasticsearch_success );
+
+ // ensures that the search query use Elasticsearch when ep_integrate set to true.
+ $query = new \WP_Query(
+ [
+ 'post_type' => 'shop_coupon',
+ 's' => 'test-coupon',
+ 'ep_integrate' => true,
+ ]
+ );
+ $this->assertTrue( $query->elasticsearch_success );
+ }
+
+ /**
+ * Tests the search query for a shop_coupon in admin use Elasticsearch when protected content is enabled.
+ *
+ * @since 4.4.1
+ * @group woocommerce
+ */
+ public function testSearchQueryForCouponWhenProtectedContentIsEnable() {
+
+ set_current_screen( 'dashboard' );
+ $this->assertTrue( is_admin() );
+
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create(
+ array(
+ 'post_content' => 'test-coupon',
+ 'post_type' => 'shop_coupon',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $query = new \WP_Query(
+ [
+ 'post_type' => 'shop_coupon',
+ 's' => 'test-coupon',
+ ]
+ );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ }
+
+ /**
+ * Tests the search query for a shop_coupon in admin does not use Elasticsearch when protected content is not enabled.
+ *
+ * @since 4.4.1
+ * @group woocommerce
+ */
+ public function testSearchQueryForCouponWhenProtectedContentIsNotEnable() {
+
+ set_current_screen( 'dashboard' );
+ $this->assertTrue( is_admin() );
+
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create(
+ array(
+ 'post_content' => 'test-coupon',
+ 'post_type' => 'shop_coupon',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $query = new \WP_Query(
+ [
+ 'post_type' => 'shop_coupon',
+ 's' => 'test-coupon',
+ 'ep_integrate' => true,
+ ]
+ );
+
+ $this->assertNull( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ }
+
+ /**
+ * Test the `is_orders_autosuggest_available` method
+ *
+ * @since 4.5.0
+ * @group woocommerce
+ */
+ public function testIsOrdersAutosuggestAvailable() {
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+
+ $this->assertSame( $woocommerce_feature->is_orders_autosuggest_available(), \ElasticPress\Utils\is_epio() );
+
+ /**
+ * Test the `ep_woocommerce_orders_autosuggest_available` filter
+ */
+ add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
+ $this->assertTrue( $woocommerce_feature->is_orders_autosuggest_available() );
+
+ add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_false' );
+ $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_available() );
+ }
+
+ /**
+ * Test the `is_orders_autosuggest_available` method
+ *
+ * @since 4.5.0
+ * @group woocommerce
+ */
+ public function testIsOrdersAutosuggestEnabled() {
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+
+ $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+
+ /**
+ * Make it available but it won't be enabled
+ */
+ add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
+ $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+
+ /**
+ * Enable it
+ */
+ $filter = function() {
+ return [
+ 'woocommerce' => [
+ 'orders' => '1',
+ ],
+ ];
+ };
+ add_filter( 'pre_site_option_ep_feature_settings', $filter );
+ add_filter( 'pre_option_ep_feature_settings', $filter );
+ $this->assertTrue( $woocommerce_feature->is_orders_autosuggest_enabled() );
+
+ /**
+ * Make it unavailable. Even activated, it should not be considered enabled if not available anymore.
+ */
+ remove_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
+ $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+ }
+
+ /**
+ * Test the addition of variations skus to product meta
+ *
+ * @since 4.2.0
+ * @group woocommerce
+ * @expectedDeprecated ElasticPress\Feature\WooCommerce\WooCommerce::add_variations_skus_meta
+ */
+ public function testAddVariationsSkusMeta() {
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->assertTrue( class_exists( '\WC_Product_Variable' ) );
+ $this->assertTrue( class_exists( '\WC_Product_Variation' ) );
+
+ $main_product = new \WC_Product_Variable();
+ $main_product->set_sku( 'main-product_sku' );
+ $main_product_id = $main_product->save();
+
+ $variation_1 = new \WC_Product_Variation();
+ $variation_1->set_parent_id( $main_product_id );
+ $variation_1->set_sku( 'child-sku-1' );
+ $variation_1->save();
+
+ $variation_2 = new \WC_Product_Variation();
+ $variation_2->set_parent_id( $main_product_id );
+ $variation_2->set_sku( 'child-sku-2' );
+ $variation_2->save();
+
+ $main_product_as_post = get_post( $main_product_id );
+ $product_meta_to_index = ElasticPress\Features::factory()
+ ->get_registered_feature( 'woocommerce' )
+ ->add_variations_skus_meta( [], $main_product_as_post );
+
+ $this->assertArrayHasKey( '_variations_skus', $product_meta_to_index );
+ $this->assertContains( 'child-sku-1', $product_meta_to_index['_variations_skus'] );
+ $this->assertContains( 'child-sku-2', $product_meta_to_index['_variations_skus'] );
+ }
+
+ /**
+ * Test the translate_args_admin_products_list method
+ *
+ * @since 4.2.0
+ * @group woocommerce
+ * @expectedDeprecated ElasticPress\Feature\WooCommerce\WooCommerce::translate_args_admin_products_list
+ */
+ public function testTranslateArgsAdminProductsList() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ parse_str( 'post_type=product&s=product&product_type=downloadable&stock_status=instock', $_GET );
+
+ $query_args = [
+ 'ep_integrate' => true,
+ ];
+
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ add_action( 'pre_get_posts', [ $woocommerce_feature, 'translate_args_admin_products_list' ] );
+
+ $query = new \WP_Query( $query_args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( $query->query_vars['s'], 'product' );
+ $this->assertEquals( $query->query_vars['meta_query'][0]['key'], '_downloadable' );
+ $this->assertEquals( $query->query_vars['meta_query'][0]['value'], 'yes' );
+ $this->assertEquals( $query->query_vars['meta_query'][1]['key'], '_stock_status' );
+ $this->assertEquals( $query->query_vars['meta_query'][1]['value'], 'instock' );
+ $this->assertEquals(
+ $query->query_vars['search_fields'],
+ [
+ 'post_title',
+ 'post_content',
+ 'post_excerpt',
+ 'meta' => [
+ '_sku',
+ '_variations_skus',
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Test the ep_woocommerce_admin_products_list_search_fields filter
+ *
+ * @since 4.2.0
+ * @group woocommerce
+ * @expectedDeprecated ElasticPress\Feature\WooCommerce\WooCommerce::translate_args_admin_products_list
+ */
+ public function testEPWoocommerceAdminProductsListSearchFields() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ parse_str( 'post_type=product&s=product&product_type=downloadable', $_GET );
+
+ $query_args = [
+ 'ep_integrate' => true,
+ ];
+
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ add_action( 'pre_get_posts', [ $woocommerce_feature, 'translate_args_admin_products_list' ] );
+
+ $search_fields_function = function () {
+ return [ 'post_title', 'post_content' ];
+ };
+ add_filter( 'ep_woocommerce_admin_products_list_search_fields', $search_fields_function );
+
+ $query = new \WP_Query( $query_args );
+ $this->assertEquals(
+ $query->query_vars['search_fields'],
+ [ 'post_title', 'post_content' ]
+ );
+ }
+}
diff --git a/tests/php/features/WooCommerce/TestWooCommerceOrders.php b/tests/php/features/WooCommerce/TestWooCommerceOrders.php
new file mode 100644
index 0000000000..9c1eff486e
--- /dev/null
+++ b/tests/php/features/WooCommerce/TestWooCommerceOrders.php
@@ -0,0 +1,334 @@
+orders = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->orders;
+ }
+
+ /**
+ * Test search integration is on for shop orders
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testSearchOnShopOrderAdmin() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create(
+ array(
+ 'post_content' => 'findme',
+ 'post_type' => 'shop_order',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ // mock the pagenow to bypass the search_order checks
+ global $pagenow;
+ $pagenow = 'edit.php';
+
+ parse_str( 's=findme', $_GET );
+ $args = array(
+ 's' => 'findme',
+ 'post_type' => 'shop_order',
+ );
+
+ $query = new \WP_Query( $args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ $this->assertEquals( 1, $query->found_posts );
+
+ $pagenow = 'index.php';
+ }
+
+ /**
+ * Test Shop Order post type query does not get integrated when the protected content feature is deactivated.
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testShopOrderPostTypeQueryOn() {
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create();
+ $this->ep_factory->post->create(
+ array(
+ 'post_type' => 'shop_order',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 'post_type' => 'shop_order',
+ );
+ $query = new \WP_Query( $args );
+
+ $this->assertNull( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ $this->assertEquals( 1, $query->found_posts );
+ }
+
+
+ /**
+ * Test Shop Order post type query does get integrated when the protected content feature is activated.
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testShopOrderPostTypeQueryWhenProtectedContentEnable() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create();
+ $this->ep_factory->post->create(
+ array(
+ 'post_type' => 'shop_order',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 'post_type' => 'shop_order',
+ );
+ $query = new \WP_Query( $args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ $this->assertEquals( 1, $query->found_posts );
+ }
+
+ /**
+ * Test Shop Order post type query does not get integrated when the protected content feature is activated and ep_integrate is set to false.
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testShopOrderPostTypeQueryWhenEPIntegrateSetFalse() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->ep_factory->post->create();
+ $this->ep_factory->post->create(
+ array(
+ 'post_type' => 'shop_order',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 'post_type' => 'shop_order',
+ 'ep_integrate' => false,
+ );
+ $query = new \WP_Query( $args );
+
+ $this->assertNull( $query->elasticsearch_success );
+ }
+
+ /**
+ * Test search for shop orders by order ID
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testSearchShopOrderById() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $shop_order_id = $this->ep_factory->post->create(
+ array(
+ 'post_type' => 'shop_order',
+ )
+ );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 's' => (string) $shop_order_id,
+ 'post_type' => 'shop_order',
+ );
+
+ $query = new \WP_Query( $args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( 1, $query->post_count );
+ $this->assertEquals( 1, $query->found_posts );
+ }
+
+ /**
+ * Test search for shop orders matching field and ID.
+ *
+ * If searching for a number that is an order ID and part of another order's metadata,
+ * both should be returned.
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testSearchShopOrderByMetaFieldAndId() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $this->assertTrue( class_exists( '\WC_Order' ) );
+
+ $shop_order_1 = new \WC_Order();
+ $shop_order_1->save();
+ $shop_order_id_1 = $shop_order_1->get_id();
+ ElasticPress\Indexables::factory()->get( 'post' )->index( $shop_order_id_1, true );
+
+ $shop_order_2 = new \WC_Order();
+ $shop_order_2->set_billing_phone( 'Phone number that matches an order ID: ' . $shop_order_id_1 );
+ $shop_order_2->save();
+ ElasticPress\Indexables::factory()->get( 'post' )->index( $shop_order_2->get_id(), true );
+
+ ElasticPress\Elasticsearch::factory()->refresh_indices();
+
+ $args = array(
+ 's' => (string) $shop_order_id_1,
+ 'post_type' => 'shop_order',
+ 'post_status' => 'any',
+ );
+
+ $query = new \WP_Query( $args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( 2, $query->post_count );
+ $this->assertEquals( 2, $query->found_posts );
+ }
+
+ /**
+ * Test the `get_admin_searchable_post_types` method
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testGetAdminSearchablePostTypes() {
+ $default_post_types = $this->orders->get_admin_searchable_post_types();
+ $this->assertSame( $default_post_types, [ 'shop_order' ] );
+
+ /**
+ * Test the `ep_woocommerce_admin_searchable_post_types` filter
+ */
+ $add_post_type = function ( $post_types ) {
+ $post_types[] = 'shop_order_custom';
+ return $post_types;
+ };
+ add_filter( 'ep_woocommerce_admin_searchable_post_types', $add_post_type );
+
+ $new_post_types = $this->orders->get_admin_searchable_post_types();
+ $this->assertSame( $new_post_types, [ 'shop_order', 'shop_order_custom' ] );
+ }
+
+ /**
+ * Test the `get_supported_post_types` method
+ *
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testGetSupportedPostTypes() {
+ $default_supported = $this->orders->get_supported_post_types();
+ $this->assertSame( $default_supported, [] );
+
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $default_supported = $this->orders->get_supported_post_types();
+ $this->assertSame( $default_supported, [ 'shop_order', 'shop_order_refund' ] );
+
+ /**
+ * Test the `ep_woocommerce_orders_supported_post_types` filter
+ */
+ $add_post_type = function( $post_types ) {
+ $post_types[] = 'shop_order_custom';
+ return $post_types;
+ };
+ add_filter( 'ep_woocommerce_orders_supported_post_types', $add_post_type );
+
+ $custom_supported = $this->orders->get_supported_post_types();
+ $this->assertSame( $custom_supported, [ 'shop_order', 'shop_order_refund' ] );
+ }
+
+ /**
+ * Test if methods moved to OrdersAutosuggest are correctly flagged
+ *
+ * @param string $method The method name
+ * @param array $args Method arguments
+ * @dataProvider ordersAutosuggestMethodsDataProvider
+ * @group woocommerce
+ * @group woocommerce-orders
+ */
+ public function testOrdersAutosuggestMethods( $method, $args ) {
+ $this->setExpectedDeprecated( "\ElasticPress\Feature\WooCommerce\WooCommerce\Orders::{$method}" );
+ $this->orders->$method( ...$args );
+ }
+
+ /**
+ * Data provider for the testOrdersAutosuggestMethods method.
+ *
+ * @return array
+ */
+ public function ordersAutosuggestMethodsDataProvider() : array {
+ return [
+ [ 'after_update_feature', [ 'test', [], [] ] ],
+ [ 'check_token_permission', [] ],
+ [ 'enqueue_admin_assets', [ '' ] ],
+ [ 'epio_delete_search_template', [] ],
+ [ 'epio_get_search_template', [] ],
+ [ 'epio_save_search_template', [] ],
+ [ 'filter_term_suggest', [ [] ] ],
+ [ 'get_args_schema', [] ],
+ [ 'get_search_endpoint', [] ],
+ [ 'get_search_template', [] ],
+ [ 'get_template_endpoint', [] ],
+ [ 'get_token', [] ],
+ [ 'get_token_endpoint', [] ],
+ [ 'intercept_search_request', [ (object) [] ] ],
+ [ 'is_integrated_request', [ true, [] ] ],
+ [ 'post_statuses', [ [] ] ],
+ [ 'post_types', [ [] ] ],
+ [ 'mapping', [ [] ] ],
+ [ 'maybe_query_password_protected_posts', [ [] ] ],
+ [ 'maybe_set_posts_where', [ '', new \WP_Query( [] ) ] ],
+ [ 'refresh_token', [] ],
+ [ 'rest_api_init', [] ],
+ [ 'set_search_fields', [] ],
+ ];
+ }
+}
diff --git a/tests/php/features/TestWooCommerceOrders.php b/tests/php/features/WooCommerce/TestWooCommerceOrdersAutosuggest.php
similarity index 80%
rename from tests/php/features/TestWooCommerceOrders.php
rename to tests/php/features/WooCommerce/TestWooCommerceOrdersAutosuggest.php
index 1160d78124..de3682e8d6 100644
--- a/tests/php/features/TestWooCommerceOrders.php
+++ b/tests/php/features/WooCommerce/TestWooCommerceOrdersAutosuggest.php
@@ -13,7 +13,7 @@
/**
* WC Orders test class
*/
-class TestWooCommerceOrders extends BaseTestCase {
+class TestWooCommerceOrdersAutosuggest extends BaseTestCase {
/**
* Instance of the feature
*
@@ -24,14 +24,15 @@ class TestWooCommerceOrders extends BaseTestCase {
/**
* Orders instance
*
- * @var \ElasticPress\Feature\WooCommerce\Orders
+ * @var \ElasticPress\Feature\WooCommerce\OrdersAutosuggest
*/
- public $orders;
+ public $orders_autosuggest;
/**
* Setup each test.
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function set_up() {
parent::set_up();
@@ -45,13 +46,14 @@ public function set_up() {
ElasticPress\Features::factory()->setup_features();
- $this->orders = $this->woocommerce_feature->orders;
+ $this->orders_autosuggest = $this->woocommerce_feature->orders_autosuggest;
}
/**
* Test the `filter_term_suggest` method
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function testFilterTermSuggest() {
$order = [
@@ -70,7 +72,7 @@ public function testFilterTermSuggest() {
],
];
- $order_with_suggest = $this->orders->filter_term_suggest( $order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $order );
$this->assertArrayHasKey( 'term_suggest', $order_with_suggest );
$this->assertContains( '_billing_email_example', $order_with_suggest['term_suggest'] );
@@ -86,16 +88,16 @@ public function testFilterTermSuggest() {
);
unset( $order['post_type'] );
- $order_with_suggest = $this->orders->filter_term_suggest( $order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $order );
$this->assertArrayNotHasKey( 'term_suggest', $order_with_suggest );
$order['post_type'] = 'not_shop_order';
- $order_with_suggest = $this->orders->filter_term_suggest( $order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $order );
$this->assertArrayNotHasKey( 'term_suggest', $order_with_suggest );
$order['post_type'] = 'shop_order';
unset( $order['meta'] );
- $order_with_suggest = $this->orders->filter_term_suggest( $order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $order );
$this->assertArrayNotHasKey( 'term_suggest', $order_with_suggest );
}
@@ -104,7 +106,8 @@ public function testFilterTermSuggest() {
*
* This method steps into WooCommerce functionality a bit.
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function testFilterTermSuggestWithCustomOrderId() {
$shop_order_1 = new \WC_Order();
@@ -115,7 +118,7 @@ public function testFilterTermSuggestWithCustomOrderId() {
$shop_order_id_1 = (string) $shop_order_1->get_id();
$prepared_shop_order = ElasticPress\Indexables::factory()->get( 'post' )->prepare_document( $shop_order_id_1 );
- $order_with_suggest = $this->orders->filter_term_suggest( $prepared_shop_order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $prepared_shop_order );
$this->assertSame(
[
@@ -133,7 +136,7 @@ public function testFilterTermSuggestWithCustomOrderId() {
};
add_filter( 'woocommerce_order_number', $set_custom_order_id );
- $order_with_suggest = $this->orders->filter_term_suggest( $prepared_shop_order );
+ $order_with_suggest = $this->orders_autosuggest->filter_term_suggest( $prepared_shop_order );
$this->assertSame(
[
@@ -147,7 +150,8 @@ public function testFilterTermSuggestWithCustomOrderId() {
/**
* Test the `mapping` method with the ES 7 mapping
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function testMappingEs7() {
$original_mapping = [
@@ -157,7 +161,7 @@ public function testMappingEs7() {
],
],
];
- $changed_mapping = $this->orders->mapping( $original_mapping );
+ $changed_mapping = $this->orders_autosuggest->mapping( $original_mapping );
$expected_mapping = [
'mappings' => [
@@ -192,7 +196,8 @@ public function testMappingEs7() {
/**
* Test the `mapping` method with the ES 5 mapping
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function testMappingEs5() {
$change_es_version = function() {
@@ -210,7 +215,7 @@ public function testMappingEs5() {
],
];
- $changed_mapping = $this->orders->mapping( $original_mapping );
+ $changed_mapping = $this->orders_autosuggest->mapping( $original_mapping );
$expected_mapping = [
'mappings' => [
@@ -247,7 +252,8 @@ public function testMappingEs5() {
/**
* Test the `set_search_fields` method
*
- * @group WooCommerceOrders
+ * @group woocommerce
+ * @group woocommerce-orders-autosuggest
*/
public function testSetSearchFields() {
$original_search_fields = [ 'old_search_field' ];
@@ -261,7 +267,7 @@ public function testSetSearchFields() {
]
);
- $changed_search_fields = $this->orders->set_search_fields( $original_search_fields, $wp_query );
+ $changed_search_fields = $this->orders_autosuggest->set_search_fields( $original_search_fields, $wp_query );
$this->assertSame( $original_search_fields, $changed_search_fields );
@@ -274,7 +280,7 @@ public function testSetSearchFields() {
]
);
- $changed_search_fields = $this->orders->set_search_fields( $original_search_fields, $wp_query );
+ $changed_search_fields = $this->orders_autosuggest->set_search_fields( $original_search_fields, $wp_query );
$expected_fields = [
'meta.order_number.value',
diff --git a/tests/php/features/TestWooCommerce.php b/tests/php/features/WooCommerce/TestWooCommerceProduct.php
similarity index 67%
rename from tests/php/features/TestWooCommerce.php
rename to tests/php/features/WooCommerce/TestWooCommerceProduct.php
index 8e5be36887..bb1fda8696 100644
--- a/tests/php/features/TestWooCommerce.php
+++ b/tests/php/features/WooCommerce/TestWooCommerceProduct.php
@@ -1,7 +1,8 @@
suppress_errors();
-
- $admin_id = $this->factory->user->create( array( 'role' => 'administrator' ) );
-
- wp_set_current_user( $admin_id );
-
- ElasticPress\Elasticsearch::factory()->delete_all_indices();
- ElasticPress\Indexables::factory()->get( 'post' )->put_mapping();
-
- ElasticPress\Indexables::factory()->get( 'post' )->sync_manager->sync_queue = [];
-
- $this->setup_test_post_type();
- }
+ protected $products;
/**
- * Clean up after each test. Reset our mocks
+ * Setup each test.
*
- * @since 2.1
* @group woocommerce
+ * @group woocommerce-orders
*/
- public function tear_down() {
- parent::tear_down();
-
- $this->fired_actions = array();
+ public function set_up() {
+ parent::set_up();
+ $this->products = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' )->products;
}
/**
* Test products post type query does not get integrated when the feature is active
*
- * @since 2.1
* @group woocommerce
+ * @group woocommerce-products
*/
public function testProductsPostTypeQueryOn() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -82,8 +65,8 @@ public function testProductsPostTypeQueryOn() {
/**
* Test products post type query does get integrated when querying WC product_cat taxonomy
*
- * @since 2.1
* @group woocommerce
+ * @group woocommerce-products
*/
public function testProductsPostTypeQueryProductCatTax() {
ElasticPress\Features::factory()->activate_feature( 'admin' );
@@ -107,209 +90,20 @@ public function testProductsPostTypeQueryProductCatTax() {
$query = new \WP_Query( $args );
$this->assertTrue( $query->elasticsearch_success );
- }
-
- /**
- * Test search integration is on for shop orders
- *
- * @since 2.1
- * @group woocommerce
- */
- public function testSearchOnShopOrderAdmin() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create(
- array(
- 'post_content' => 'findme',
- 'post_type' => 'shop_order',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- // mock the pagenow to bypass the search_order checks
- global $pagenow;
- $pagenow = 'edit.php';
-
- parse_str( 's=findme', $_GET );
- $args = array(
- 's' => 'findme',
- 'post_type' => 'shop_order',
- );
-
- $query = new \WP_Query( $args );
-
- $this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- $this->assertEquals( 1, $query->found_posts );
-
- $pagenow = 'index.php';
- }
-
- /**
- * Test Shop Order post type query does not get integrated when the protected content feature is deactivated.
- *
- * @since 4.5
- */
- public function testShopOrderPostTypeQueryOn() {
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create();
- $this->ep_factory->post->create(
- array(
- 'post_type' => 'shop_order',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $args = array(
- 'post_type' => 'shop_order',
- );
- $query = new \WP_Query( $args );
-
- $this->assertNull( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- $this->assertEquals( 1, $query->found_posts );
- }
-
- /**
- * Test Shop Order post type query does get integrated when the protected content feature is activated.
- *
- * @since 4.5
- */
- public function testShopOrderPostTypeQueryWhenProtectedContentEnable() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create();
- $this->ep_factory->post->create(
- array(
- 'post_type' => 'shop_order',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $args = array(
- 'post_type' => 'shop_order',
- );
- $query = new \WP_Query( $args );
-
- $this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- $this->assertEquals( 1, $query->found_posts );
- }
-
- /**
- * Test Shop Order post type query does not get integrated when the protected content feature is activated and ep_integrate is set to false.
- *
- * @since 4.5
- */
- public function testShopOrderPostTypeQueryWhenEPIntegrateSetFalse() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create();
- $this->ep_factory->post->create(
- array(
- 'post_type' => 'shop_order',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $args = array(
- 'post_type' => 'shop_order',
- 'ep_integrate' => false,
- );
- $query = new \WP_Query( $args );
-
- $this->assertNull( $query->elasticsearch_success );
- }
-
- /**
- * Test search for shop orders by order ID
- *
- * @since 4.0.0
- * @group woocommerce
- */
- public function testSearchShopOrderById() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $shop_order_id = $this->ep_factory->post->create(
- array(
- 'post_type' => 'shop_order',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
- $args = array(
- 's' => (string) $shop_order_id,
- 'post_type' => 'shop_order',
- );
+ $args = [ 'product_cat' => 'cat' ];
$query = new \WP_Query( $args );
$this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- $this->assertEquals( 1, $query->found_posts );
}
- /**
- * Test search for shop orders matching field and ID.
- *
- * If searching for a number that is an order ID and part of another order's metadata,
- * both should be returned.
- *
- * @since 4.0.0
- * @group woocommerce
- */
- public function testSearchShopOrderByMetaFieldAndId() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->assertTrue( class_exists( '\WC_Order' ) );
-
- $shop_order_1 = new \WC_Order();
- $shop_order_1->save();
- $shop_order_id_1 = $shop_order_1->get_id();
- ElasticPress\Indexables::factory()->get( 'post' )->index( $shop_order_id_1, true );
-
- $shop_order_2 = new \WC_Order();
- $shop_order_2->set_billing_phone( 'Phone number that matches an order ID: ' . $shop_order_id_1 );
- $shop_order_2->save();
- ElasticPress\Indexables::factory()->get( 'post' )->index( $shop_order_2->get_id(), true );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $args = array(
- 's' => (string) $shop_order_id_1,
- 'post_type' => 'shop_order',
- 'post_status' => 'any',
- );
-
- $query = new \WP_Query( $args );
-
- $this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( 2, $query->post_count );
- $this->assertEquals( 2, $query->found_posts );
- }
/**
* Test search integration is on in general for product searches
*
- * @since 2.1
* @group woocommerce
+ * @group woocommerce-products
*/
public function testSearchOnAllFrontEnd() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -327,229 +121,11 @@ public function testSearchOnAllFrontEnd() {
$this->assertTrue( $query->elasticsearch_success );
}
- /**
- * Test the addition of variations skus to product meta
- *
- * @since 4.2.0
- * @group woocommerce
- */
- public function testAddVariationsSkusMeta() {
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->assertTrue( class_exists( '\WC_Product_Variable' ) );
- $this->assertTrue( class_exists( '\WC_Product_Variation' ) );
-
- $main_product = new \WC_Product_Variable();
- $main_product->set_sku( 'main-product_sku' );
- $main_product_id = $main_product->save();
-
- $variation_1 = new \WC_Product_Variation();
- $variation_1->set_parent_id( $main_product_id );
- $variation_1->set_sku( 'child-sku-1' );
- $variation_1->save();
-
- $variation_2 = new \WC_Product_Variation();
- $variation_2->set_parent_id( $main_product_id );
- $variation_2->set_sku( 'child-sku-2' );
- $variation_2->save();
-
- $main_product_as_post = get_post( $main_product_id );
- $product_meta_to_index = ElasticPress\Features::factory()
- ->get_registered_feature( 'woocommerce' )
- ->add_variations_skus_meta( [], $main_product_as_post );
-
- $this->assertArrayHasKey( '_variations_skus', $product_meta_to_index );
- $this->assertContains( 'child-sku-1', $product_meta_to_index['_variations_skus'] );
- $this->assertContains( 'child-sku-2', $product_meta_to_index['_variations_skus'] );
- }
-
- /**
- * Test the translate_args_admin_products_list method
- *
- * @since 4.2.0
- * @group woocommerce
- */
- public function testTranslateArgsAdminProductsList() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- parse_str( 'post_type=product&s=product&product_type=downloadable&stock_status=instock', $_GET );
-
- $query_args = [
- 'ep_integrate' => true,
- ];
-
- $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
- add_action( 'pre_get_posts', [ $woocommerce_feature, 'translate_args_admin_products_list' ] );
-
- $query = new \WP_Query( $query_args );
-
- $this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( $query->query_vars['s'], 'product' );
- $this->assertEquals( $query->query_vars['meta_query'][0]['key'], '_downloadable' );
- $this->assertEquals( $query->query_vars['meta_query'][0]['value'], 'yes' );
- $this->assertEquals( $query->query_vars['meta_query'][1]['key'], '_stock_status' );
- $this->assertEquals( $query->query_vars['meta_query'][1]['value'], 'instock' );
- $this->assertEquals(
- $query->query_vars['search_fields'],
- [
- 'post_title',
- 'post_content',
- 'post_excerpt',
- 'meta' => [
- '_sku',
- '_variations_skus',
- ],
- ]
- );
- }
-
- /**
- * Test the ep_woocommerce_admin_products_list_search_fields filter
- *
- * @since 4.2.0
- * @group woocommerce
- */
- public function testEPWoocommerceAdminProductsListSearchFields() {
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- parse_str( 'post_type=product&s=product&product_type=downloadable', $_GET );
-
- $query_args = [
- 'ep_integrate' => true,
- ];
-
- $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
- add_action( 'pre_get_posts', [ $woocommerce_feature, 'translate_args_admin_products_list' ] );
-
- $search_fields_function = function () {
- return [ 'post_title', 'post_content' ];
- };
- add_filter( 'ep_woocommerce_admin_products_list_search_fields', $search_fields_function );
-
- $query = new \WP_Query( $query_args );
- $this->assertEquals(
- $query->query_vars['search_fields'],
- [ 'post_title', 'post_content' ]
- );
- }
-
- /**
- * Tests the search query for a shop_coupon.
- *
- * @since 4.4.1
- */
- public function testSearchQueryForCoupon() {
-
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- // ensures that the search query doesn't use Elasticsearch.
- $query = new \WP_Query(
- [
- 'post_type' => 'shop_coupon',
- 's' => 'test-coupon',
- ]
- );
- $this->assertNull( $query->elasticsearch_success );
-
- // ensures that the search query doesn't use Elasticsearch when ep_integrate set to false.
- $query = new \WP_Query(
- [
- 'post_type' => 'shop_coupon',
- 's' => 'test-coupon',
- 'ep_integrate' => false,
- ]
- );
- $this->assertNull( $query->elasticsearch_success );
-
- // ensures that the search query use Elasticsearch when ep_integrate set to true.
- $query = new \WP_Query(
- [
- 'post_type' => 'shop_coupon',
- 's' => 'test-coupon',
- 'ep_integrate' => true,
- ]
- );
- $this->assertTrue( $query->elasticsearch_success );
- }
-
- /**
- * Tests the search query for a shop_coupon in admin use Elasticsearch when protected content is enabled.
- *
- * @since 4.4.1
- */
- public function testSearchQueryForCouponWhenProtectedContentIsEnable() {
-
- set_current_screen( 'dashboard' );
- $this->assertTrue( is_admin() );
-
- ElasticPress\Features::factory()->activate_feature( 'protected_content' );
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create(
- array(
- 'post_content' => 'test-coupon',
- 'post_type' => 'shop_coupon',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $query = new \WP_Query(
- [
- 'post_type' => 'shop_coupon',
- 's' => 'test-coupon',
- ]
- );
-
- $this->assertTrue( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- }
-
- /**
- * Tests the search query for a shop_coupon in admin does not use Elasticsearch when protected content is not enabled.
- *
- * @since 4.4.1
- */
- public function testSearchQueryForCouponWhenProtectedContentIsNotEnable() {
-
- set_current_screen( 'dashboard' );
- $this->assertTrue( is_admin() );
-
- ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
- ElasticPress\Features::factory()->setup_features();
-
- $this->ep_factory->post->create(
- array(
- 'post_content' => 'test-coupon',
- 'post_type' => 'shop_coupon',
- )
- );
-
- ElasticPress\Elasticsearch::factory()->refresh_indices();
-
- $query = new \WP_Query(
- [
- 'post_type' => 'shop_coupon',
- 's' => 'test-coupon',
- 'ep_integrate' => true,
- ]
- );
-
- $this->assertNull( $query->elasticsearch_success );
- $this->assertEquals( 1, $query->post_count );
- }
-
/**
* Test all the product attributes are synced.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testWoocommerceAttributeTaxonomiesAreSync() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -686,7 +262,7 @@ public function productQueryOrderDataProvider() : array {
}
/**
- * Test the product query order.
+ * Test the product query order.
*
* @param string $product_arg_key Field slug
* @param array $query_args Query array
@@ -694,7 +270,8 @@ public function productQueryOrderDataProvider() : array {
* @param array $expected Value expected
* @param string $order Order
* @dataProvider productQueryOrderDataProvider
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testProductQueryOrder( $product_arg_key, $query_args, $query_string, $expected, $order = '' ) {
global $wp_the_query;
@@ -764,7 +341,8 @@ function ( $formatted_args ) use ( $expected ) {
/**
* Test the product query not use Elasticsearch if preview.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testQueryShouldNotUseElasticsearchIfPreview() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -780,10 +358,12 @@ public function testQueryShouldNotUseElasticsearchIfPreview() {
$this->assertNull( $query->elasticsearch_success );
}
+
/**
* Test that on Admin Product List use Elasticsearch.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testProductListInAdminUseElasticSearch() {
global $typenow, $wc_list_table;
@@ -830,7 +410,8 @@ function( $filters, $args, $query ) {
/**
* Test that Search in Admin Product List use Elasticsearch.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testProductListSearchInAdminUseElasticSearch() {
global $typenow, $wc_list_table;
@@ -882,7 +463,8 @@ function ( $formatted_args, $args, $wp_query ) {
/**
* Test the product query when price filter is set.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testPriceFilter() {
global $wp_the_query, $wp_query;
@@ -951,7 +533,8 @@ function ( $formatted_args ) {
/**
* Test the product search query when price filter is set.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testPriceFilterWithSearchQuery() {
global $wp_the_query, $wp_query;
@@ -1010,7 +593,8 @@ public function testPriceFilterWithSearchQuery() {
/**
* Tests that attributes filter uses Elasticsearch.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testAttributesFilterUseES() {
global $wp_the_query;
@@ -1066,7 +650,8 @@ function( \WP_Query $query ) {
/**
* Tests that get_posts() uses Elasticsearch when ep_integrate is true.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testGetPosts() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -1089,7 +674,8 @@ public function testGetPosts() {
/**
* Tests that get_posts() does not use Elasticsearch when ep_integrate is not set.
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
public function testGetPostQueryDoesNotUseElasticSearchByDefault() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -1112,7 +698,7 @@ public function testGetPostQueryDoesNotUseElasticSearchByDefault() {
/**
* Tests that Weighting dashboard shows SKU and Variation SKUs option.
*
- * @since 4.5.0
+ * @group woocommerce
*/
public function testSkuOptionAddInWeightDashboard() {
ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
@@ -1132,68 +718,123 @@ public function testSkuOptionAddInWeightDashboard() {
}
/**
- * Test the `is_orders_autosuggest_available` method
+ * Test the addition of variations skus to product meta
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
- public function testIsOrdersAutosuggestAvailable() {
- $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ public function testAddVariationsSkusMeta() {
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
- $this->assertSame( $woocommerce_feature->is_orders_autosuggest_available(), \ElasticPress\Utils\is_epio() );
+ $this->assertTrue( class_exists( '\WC_Product_Variable' ) );
+ $this->assertTrue( class_exists( '\WC_Product_Variation' ) );
- /**
- * Test the `ep_woocommerce_orders_autosuggest_available` filter
- */
- add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
- $this->assertTrue( $woocommerce_feature->is_orders_autosuggest_available() );
+ $main_product = new \WC_Product_Variable();
+ $main_product->set_sku( 'main-product_sku' );
+ $main_product_id = $main_product->save();
+
+ $variation_1 = new \WC_Product_Variation();
+ $variation_1->set_parent_id( $main_product_id );
+ $variation_1->set_sku( 'child-sku-1' );
+ $variation_1->save();
+
+ $variation_2 = new \WC_Product_Variation();
+ $variation_2->set_parent_id( $main_product_id );
+ $variation_2->set_sku( 'child-sku-2' );
+ $variation_2->save();
+
+ $main_product_as_post = get_post( $main_product_id );
+ $product_meta_to_index = ElasticPress\Features::factory()
+ ->get_registered_feature( 'woocommerce' )
+ ->products
+ ->add_variations_skus_meta( [], $main_product_as_post );
- add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_false' );
- $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_available() );
+ $this->assertArrayHasKey( '_variations_skus', $product_meta_to_index );
+ $this->assertContains( 'child-sku-1', $product_meta_to_index['_variations_skus'] );
+ $this->assertContains( 'child-sku-2', $product_meta_to_index['_variations_skus'] );
}
/**
- * Test the `is_orders_autosuggest_available` method
+ * Test the translate_args_admin_products_list method
*
- * @since 4.5.0
+ * @group woocommerce
+ * @group woocommerce-products
*/
- public function testIsOrdersAutosuggestEnabled() {
- $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ public function testTranslateArgsAdminProductsList() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
- $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+ parse_str( 'post_type=product&s=product&product_type=downloadable&stock_status=instock', $_GET );
- /**
- * Make it available but it won't be enabled
- */
- add_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
- $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+ $query_args = [
+ 'ep_integrate' => true,
+ ];
- /**
- * Enable it
- */
- $filter = function() {
- return [
- 'woocommerce' => [
- 'orders' => '1',
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ add_action( 'pre_get_posts', [ $woocommerce_feature->products, 'translate_args_admin_products_list' ] );
+
+ $query = new \WP_Query( $query_args );
+
+ $this->assertTrue( $query->elasticsearch_success );
+ $this->assertEquals( $query->query_vars['s'], 'product' );
+ $this->assertEquals( $query->query_vars['meta_query'][0]['key'], '_downloadable' );
+ $this->assertEquals( $query->query_vars['meta_query'][0]['value'], 'yes' );
+ $this->assertEquals( $query->query_vars['meta_query'][1]['key'], '_stock_status' );
+ $this->assertEquals( $query->query_vars['meta_query'][1]['value'], 'instock' );
+ $this->assertEquals(
+ $query->query_vars['search_fields'],
+ [
+ 'post_title',
+ 'post_content',
+ 'post_excerpt',
+ 'meta' => [
+ '_sku',
+ '_variations_skus',
],
- ];
+ ]
+ );
+ }
+
+ /**
+ * Test the ep_woocommerce_admin_products_list_search_fields filter
+ *
+ * @group woocommerce
+ * @group woocommerce-products
+ */
+ public function testEPWoocommerceAdminProductsListSearchFields() {
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ parse_str( 'post_type=product&s=product&product_type=downloadable', $_GET );
+
+ $query_args = [
+ 'ep_integrate' => true,
+ ];
+
+ $woocommerce_feature = ElasticPress\Features::factory()->get_registered_feature( 'woocommerce' );
+ add_action( 'pre_get_posts', [ $woocommerce_feature->products, 'translate_args_admin_products_list' ] );
+
+ $search_fields_function = function () {
+ return [ 'post_title', 'post_content' ];
};
- add_filter( 'pre_site_option_ep_feature_settings', $filter );
- add_filter( 'pre_option_ep_feature_settings', $filter );
- $this->assertTrue( $woocommerce_feature->is_orders_autosuggest_enabled() );
+ add_filter( 'ep_woocommerce_admin_products_list_search_fields', $search_fields_function );
- /**
- * Make it unavailable. Even activated, it should not be considered enabled if not available anymore.
- */
- remove_filter( 'ep_woocommerce_orders_autosuggest_available', '__return_true' );
- $this->assertFalse( $woocommerce_feature->is_orders_autosuggest_enabled() );
+ $query = new \WP_Query( $query_args );
+ $this->assertEquals(
+ $query->query_vars['search_fields'],
+ [ 'post_title', 'post_content' ]
+ );
}
/**
* Test if decaying is disabled on products.
*
- * @since 4.6.0
* @dataProvider decayingDisabledOnProductsProvider
* @group woocommerce
+ * @group woocommerce-products
*
* @param string $setting Value for `decaying_enabled`
* @param array|string $post_type Post types to be queried
@@ -1225,7 +866,6 @@ public function testDecayingDisabledOnProducts( $setting, $post_type, $assert )
/**
* Data provider for the testDecayingDisabledOnProducts method.
*
- * @since 4.6.0
* @return array
*/
public function decayingDisabledOnProductsProvider() : array {
@@ -1267,4 +907,118 @@ public function decayingDisabledOnProductsProvider() : array {
],
];
}
+
+ /**
+ * Test the `get_supported_post_types` method
+ *
+ * @group woocommerce
+ * @group woocommerce-products
+ */
+ public function testGetSupportedPostTypes() {
+ $query = new \WP_Query( [] );
+
+ $default_supported = $this->products->get_supported_post_types( $query );
+ $this->assertSame( $default_supported, [] );
+
+ ElasticPress\Features::factory()->activate_feature( 'protected_content' );
+ ElasticPress\Features::factory()->activate_feature( 'woocommerce' );
+ ElasticPress\Features::factory()->setup_features();
+
+ $default_supported = $this->products->get_supported_post_types( $query );
+ $this->assertSame( $default_supported, [ 'product_variation' ] );
+
+ /**
+ * Test the `ep_woocommerce_products_supported_post_types` filter
+ */
+ $add_post_type = function( $post_types, $filter_query ) use ( $query ) {
+ $this->assertSame( $filter_query, $query );
+ $post_types[] = 'post';
+ return $post_types;
+ };
+ add_filter( 'ep_woocommerce_products_supported_post_types', $add_post_type, 10, 2 );
+
+ $custom_supported = $this->products->get_supported_post_types( $query );
+ $this->assertSame( $custom_supported, [ 'product_variation', 'post' ] );
+
+ $this->markTestIncomplete( 'This test should also test the addition of the `product` post type under some circumstances.' );
+ }
+
+ /**
+ * Test the `get_supported_taxonomies` method
+ *
+ * @group woocommerce
+ * @group woocommerce-products
+ */
+ public function testGetSupportedTaxonomies() {
+ $default_supported = $this->products->get_supported_taxonomies();
+ $expected = [
+ 'product_cat',
+ 'product_tag',
+ 'product_type',
+ 'product_visibility',
+ 'product_shipping_class',
+ ];
+ $this->assertSame( $default_supported, $expected );
+
+ /**
+ * Test the `ep_woocommerce_products_supported_taxonomies` filter
+ */
+ $add_taxonomy = function( $taxonomies ) {
+ $taxonomies[] = 'custom_category';
+ return $taxonomies;
+ };
+ add_filter( 'ep_woocommerce_products_supported_taxonomies', $add_taxonomy );
+
+ $custom_supported = $this->products->get_supported_taxonomies();
+ $this->assertSame( $custom_supported, array_merge( $expected, [ 'custom_category' ] ) );
+ }
+
+ /**
+ * Test the `get_orderby_meta_mapping` method
+ *
+ * @dataProvider orderbyMetaMappingDataProvider
+ * @group woocommerce
+ * @group woocommerce-products
+ *
+ * @param string $meta_key Original meta key value
+ * @param string $translated Expected translated version
+ */
+ public function testOrderbyMetaMapping( $meta_key, $translated ) {
+ $this->assertSame( $this->products->get_orderby_meta_mapping( $meta_key ), $translated );
+ }
+
+ /**
+ * Data provider for the testOrderbyMetaMapping method.
+ *
+ * @return array
+ */
+ public function orderbyMetaMappingDataProvider() {
+ return [
+ [ 'ID', 'ID' ],
+ [ 'title', 'title date' ],
+ [ 'menu_order', 'menu_order title date' ],
+ [ 'menu_order title', 'menu_order title date' ],
+ [ 'total_sales', 'meta.total_sales.double date' ],
+ [ '_wc_average_rating', 'meta._wc_average_rating.double date' ],
+ [ '_price', 'meta._price.double date' ],
+ [ '_sku', 'meta._sku.value.sortable date' ],
+ [ 'custom_parameter', 'date' ],
+ ];
+ }
+
+ /**
+ * Test the `orderby_meta_mapping` filter
+ *
+ * @group woocommerce
+ * @group woocommerce-products
+ */
+ public function testOrderbyMetaMappingFilter() {
+ $add_value = function ( $mapping ) {
+ $mapping['custom_parameter'] = 'meta.custom_parameter.long';
+ return $mapping;
+ };
+ add_filter( 'orderby_meta_mapping', $add_value );
+
+ $this->assertSame( $this->products->get_orderby_meta_mapping( 'custom_parameter' ), 'meta.custom_parameter.long' );
+ }
}