Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WooCommerce behavior in the Product Admin List View #2757

Merged
merged 6 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 113 additions & 6 deletions includes/classes/Feature/WooCommerce/WooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -511,14 +511,14 @@ public function translate_args( $query ) {
* Also make sure the orderby param affects only the main query
*/
if ( ! empty( $_GET['orderby'] ) && $query->is_main_query() ) { // phpcs:ignore WordPress.Security.NonceVerification

switch ( $_GET['orderby'] ) { // phpcs:ignore WordPress.Security.NonceVerification
$orderby = sanitize_text_field( $_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', 'ASC' );
$query->set( 'order', $query->get( 'order', 'ASC' ) );
$query->set( 'orderby', $this->get_orderby_meta_mapping( '_price' ) );
break;
case 'price-desc':
Expand All @@ -530,10 +530,12 @@ public function translate_args( $query ) {
$query->set( 'order', 'DESC' );
break;
case 'date':
$query->set( 'orderby', $this->get_orderby_meta_mapping( 'date' ) );
break;
case 'title':
case 'ID':
$query->set( 'orderby', $this->get_orderby_meta_mapping( '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.
Expand All @@ -560,11 +562,13 @@ public function get_orderby_meta_mapping( $meta_key ) {
'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',
)
);

Expand Down Expand Up @@ -807,6 +811,7 @@ public function setup() {
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 );
}

/**
Expand Down Expand Up @@ -973,6 +978,108 @@ function ( $variations_skus, $current_id ) {
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.
*
* @since 4.2.0
* @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( $_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 to skip integration with WooCommerce Admin Product List.
*
* @hook ep_woocommerce_admin_products_list_search_fields
* @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
*/
$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( $_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 );
}
}

/**
* Determines whether or not ES should be integrating with the provided query
*
Expand Down
74 changes: 74 additions & 0 deletions tests/php/features/TestWooCommerce.php
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,78 @@ public function testAddVariationsSkusMeta() {
$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', $_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['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' ]
);

remove_filter( 'ep_woocommerce_admin_products_list_search_fields', $search_fields_function );
}
}