Merge branch 'trunk' into add/29609
This commit is contained in:
commit
13ed3b80e0
|
@ -140,7 +140,7 @@ jQuery( function ( $ ) {
|
|||
is_billing = Boolean( $edit_address.find( 'input[name^="_billing_"]' ).length );
|
||||
|
||||
$address.hide();
|
||||
$this.parent().find( 'a' ).trigger( 'toggle' );
|
||||
$this.parent().find( 'a' ).toggle();
|
||||
|
||||
if ( ! $country_input.val() ) {
|
||||
$country_input.val( woocommerce_admin_meta_boxes_order.default_country ).trigger( 'change' );
|
||||
|
|
|
@ -554,8 +554,8 @@ jQuery( function( $ ) {
|
|||
if (!(month && year)) {
|
||||
return false;
|
||||
}
|
||||
month = 'string' === typeof month ? month.trim() : '';
|
||||
year = 'string' === typeof year ? year.trim() : '';
|
||||
month = ( month || 0 == month ) ? month.toString().trim() : '';
|
||||
year = ( year || 0 == year ) ? year.toString().trim() : '';
|
||||
if (!/^\d+$/.test(month)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -584,7 +584,7 @@ jQuery( function( $ ) {
|
|||
|
||||
$.payment.validateCardCVC = function(cvc, type) {
|
||||
var card, _ref;
|
||||
cvc = 'string' === typeof cvc ? cvc.trim() : '';
|
||||
cvc = ( cvc || 0 == cvc ) ? cvc.toString().trim() : '';
|
||||
if (!/^\d+$/.test(cvc)) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -3707,7 +3707,7 @@ S2.define('select2/data/tags',[
|
|||
};
|
||||
|
||||
Tags.prototype.createTag = function (decorated, params) {
|
||||
var term = params.term.trim();
|
||||
var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : '';
|
||||
|
||||
if (term === '') {
|
||||
return null;
|
||||
|
@ -4941,7 +4941,7 @@ S2.define('select2/defaults',[
|
|||
|
||||
function matcher (params, data) {
|
||||
// Always return the object if there is nothing to compare
|
||||
if ( params.term.trim() === '' ) {
|
||||
if ( params.term == null || params.term.toString().trim() === '' ) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -5801,7 +5801,7 @@ S2.define('select2/compat/utils',[
|
|||
function syncCssClasses ($dest, $src, adapter) {
|
||||
var classes, replacements = [], adapted;
|
||||
|
||||
classes = $dest.attr('class').trim();
|
||||
classes = ( $dest.attr('class') || 0 == $dest.attr('class') ) ? $dest.attr('class').toString().trim() : '';
|
||||
|
||||
if (classes) {
|
||||
classes = '' + classes; // for IE which returns object
|
||||
|
@ -5814,7 +5814,7 @@ S2.define('select2/compat/utils',[
|
|||
});
|
||||
}
|
||||
|
||||
classes = $src.attr('class').trim();
|
||||
classes = ( $src.attr('class') || 0 == $src.attr('class') ) ? $src.attr('class').toString().trim() : '';
|
||||
|
||||
if (classes) {
|
||||
classes = '' + classes; // for IE which returns object
|
||||
|
@ -6131,7 +6131,7 @@ S2.define('select2/compat/matcher',[
|
|||
function wrappedMatcher (params, data) {
|
||||
var match = $.extend(true, {}, data);
|
||||
|
||||
if (params.term == null || params.term.trim() === '') {
|
||||
if ( params.term == null || params.term.trim() === '' ) {
|
||||
return match;
|
||||
}
|
||||
|
||||
|
|
|
@ -3707,7 +3707,7 @@ S2.define('select2/data/tags',[
|
|||
};
|
||||
|
||||
Tags.prototype.createTag = function (decorated, params) {
|
||||
var term = params.term.trim();
|
||||
var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : '';
|
||||
|
||||
if (term === '') {
|
||||
return null;
|
||||
|
@ -4941,7 +4941,7 @@ S2.define('select2/defaults',[
|
|||
|
||||
function matcher (params, data) {
|
||||
// Always return the object if there is nothing to compare
|
||||
if ( params.term.trim() === '' ) {
|
||||
if ( params.term == null || params.term.toString().trim() === '' ) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -3707,7 +3707,7 @@ S2.define('select2/data/tags',[
|
|||
};
|
||||
|
||||
Tags.prototype.createTag = function (decorated, params) {
|
||||
var term = 'string' === typeof params.term ? params.term.trim() : '';
|
||||
var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : '';
|
||||
|
||||
if (term === '') {
|
||||
return null;
|
||||
|
@ -4941,7 +4941,7 @@ S2.define('select2/defaults',[
|
|||
|
||||
function matcher (params, data) {
|
||||
// Always return the object if there is nothing to compare
|
||||
if ( 'undefined' === typeof params.term || ( 'string' === typeof params.term && '' === params.term.trim() ) ) {
|
||||
if ( params.term == null || params.term.toString().trim() === '' ) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
@ -5801,7 +5801,7 @@ S2.define('select2/compat/utils',[
|
|||
function syncCssClasses ($dest, $src, adapter) {
|
||||
var classes, replacements = [], adapted;
|
||||
|
||||
classes = 'string' === typeof $dest.attr('class') ? $dest.attr('class').trim() : '';
|
||||
classes = ( $dest.attr('class') || 0 == $dest.attr('class') ) ? $dest.attr('class').toString().trim() : '';
|
||||
|
||||
if (classes) {
|
||||
classes = '' + classes; // for IE which returns object
|
||||
|
@ -5814,7 +5814,7 @@ S2.define('select2/compat/utils',[
|
|||
});
|
||||
}
|
||||
|
||||
classes = 'string' === typeof $src.attr('class') ? $src.attr('class').trim() : '';
|
||||
classes = ( $src.attr('class') || 0 == $src.attr('class') ) ? $src.attr('class').toString().trim() : '';
|
||||
|
||||
if (classes) {
|
||||
classes = '' + classes; // for IE which returns object
|
||||
|
@ -6131,7 +6131,7 @@ S2.define('select2/compat/matcher',[
|
|||
function wrappedMatcher (params, data) {
|
||||
var match = $.extend(true, {}, data);
|
||||
|
||||
if ( params.term == null || ( 'string' === typeof params.term && '' === params.term.trim() ) ) {
|
||||
if ( params.term == null || params.term.trim() === '' ) {
|
||||
return match;
|
||||
}
|
||||
|
||||
|
|
|
@ -3707,7 +3707,7 @@ S2.define('select2/data/tags',[
|
|||
};
|
||||
|
||||
Tags.prototype.createTag = function (decorated, params) {
|
||||
var term = params.term.trim();
|
||||
var term = ( params.term || 0 == params.term ) ? params.term.toString().trim() : '';
|
||||
|
||||
if (term === '') {
|
||||
return null;
|
||||
|
@ -4941,7 +4941,7 @@ S2.define('select2/defaults',[
|
|||
|
||||
function matcher (params, data) {
|
||||
// Always return the object if there is nothing to compare
|
||||
if ( params.term.trim() === '' ) {
|
||||
if ( params.term == null || params.term.toString().trim() === '' ) {
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,91 @@
|
|||
== Changelog ==
|
||||
|
||||
= 5.4.0 2021-06-08 =
|
||||
|
||||
**WooCommerce**
|
||||
|
||||
* Localization - Added Venezuelan states. #29477
|
||||
* Add - Feature WooCommerce Payments in the extensions store for stores in the US, Canada, UK, Ireland, Australia and New Zealand. #29843
|
||||
* Add - Product attributes lookup table and debug tools to manually fill or delete it. #29778
|
||||
* Add - dates_are_gmt parameters in REST API to searched posts using the post_date_gmt column.
|
||||
* Add - labels and searching terms in WooCommerce Navigation link block variations. #29772
|
||||
* Enhancement - Improved accessibility of the country and state address fields. #29706
|
||||
* Tweak - Updates the date_query usage in the CRUD controller to be consistent, generating an array of queries. #29909
|
||||
* Tweak - Search all extension categories instead of just searching the selected category. #29694
|
||||
* Tweak - Use WC Admin's native notice nonce generation. #29637
|
||||
* Tweak - Change email settings help text to include troubleshooting steps. #29599
|
||||
* Fix - Bulk edit on external products causes an error when changing the Backorders setting. #29766
|
||||
* Fix - wc_get_low_stock_amount was returning a string, not an integer, for products not having an explicit low stock value set. #29721
|
||||
* Fix - Products left without default category assignment when all categories are deleted. #29681
|
||||
* Fix - Removed rounding at several places to better support precision when prices are entered more than 2dp. #29318
|
||||
* Fix - Migrate deprecated jQuery 3 functions. #29044
|
||||
* Dev - Refactored Tracker to use direct DB calls instead of CRUD. #29877
|
||||
* Dev - Introduce an option for assignment of variations for Remote Inbox Notification A/B testing. #29894
|
||||
|
||||
**WooCommerce Admin - 2.3.0 & 2.3.1**
|
||||
|
||||
* Add - Add plugin installer to allow installation of plugins via URL #6805
|
||||
* Add - Optional children prop to SummaryNumber component #6748
|
||||
* Dev - Add data source filter to remote inbox notification system #6794
|
||||
* Dev - Add A/A test #6669
|
||||
* Dev - Add support for nonces in note actions #6726
|
||||
* Dev - Add support for running php unit tests in PHP 8. #6678
|
||||
* Dev - Add event recording to start of gateway connections #6801
|
||||
* Dev - Do a git clean before the core release. #6945
|
||||
* Dev - Fix a bug where trying to load an asset registry causes a crash. #6951
|
||||
* Feature - Add recommended payment methods in payment settings. #6760
|
||||
* Fix - Disable the continue btn on OBW when requested are being made #6838
|
||||
* Fix - Event tracking for merchant email notes #6616
|
||||
* Fix - Use the store timezone to make time data requests #6632
|
||||
* Fix - Update the checked input radio button margin style #6701
|
||||
* Fix - Convert date to timestamp before passing to set_date_prop to persist timezone #6795
|
||||
* Fix - Make pagination buttons height and width consistent #6725
|
||||
* Fix - Retain persisted queries when navigating to Homescreen #6614
|
||||
* Fix - Update folded header style #6724
|
||||
* Fix - Unreleated variations showing up in the Products reports #6647
|
||||
* Fix - Check active plugins before getting the PayPal onboarding status #6625
|
||||
* Fix - Remove no-reply from inbox notification emails #6644
|
||||
* Fix - Set up shipping costs task, redirect to shipping settings after completion. #6791
|
||||
* Fix - Onboarding logic on WooCommerce update to keep task list present. #6803
|
||||
* Fix - Pause inbox message “GivingFeedbackNotes” #6802
|
||||
* Fix - Missed DB version number updates causing unnecessary upgrades. #6818
|
||||
* Fix - Parsing bad JSON string data from user WooCommerce meta. #6819
|
||||
* Fix - Remove PayPal for India #6828
|
||||
* Fix - Address an issue with OBW when installing only WooCommerce payments and Jetpack. #6957
|
||||
* Fix - Calling of get_script_asset_filename with extra parameter #6955
|
||||
* Fix - Show Google Listing and Ads in installed marketing extensions section. #7029
|
||||
* Performance - Avoid updating customer info synchronously from the front end. #6765
|
||||
* Tweak - Add settings_section event prop for CES #6762
|
||||
* Tweak - Refactor payments to allow management of methods #6786
|
||||
* Tweak - Add tracking data for the preview site button #6623
|
||||
* Tweak - Update WC Payments copy on the task list #6734
|
||||
* Tweak - Add check to see if value for contains is array, show warning if not. #6645
|
||||
* Tweak - Sort the extension task list by completion status and allow toggling visibility. #6792
|
||||
* Tweak - Update PayU logo #6829
|
||||
* Tweak - Store profiler - Changed MailPoet's title and description #6886
|
||||
* Tweak - Store profiler - Changed MailPoet's title and description #6990
|
||||
* Tweak - Adjust WC Pay supported countries #7048
|
||||
* Update - Replace marketing extension - Google Listings and Ads. #6939
|
||||
* Update - Update choose niche note cta URL #6733
|
||||
* Update - UI updates to Payment Task screen #6766
|
||||
* Update - Adding setup required icon for non-configured payment methods #6811
|
||||
* Update - Payment recommendation screen transition and add external link icon. #7022
|
||||
|
||||
**WooCommerce Blocks Package - 5.1.0**
|
||||
|
||||
* Add - Introduced AssetsController and BlockTypesController classes (which replace Assets.php and Library.php). #4094
|
||||
* Tweak - Replaced usage of the `woocommerce_shared_settings` hook. This will be deprecated. #4092
|
||||
|
||||
**WooCommerce Blocks Feature Plugin - 5.1.0**
|
||||
|
||||
* Add - Added support to the Store API for batching requests. This allows multiple POST requests to be made at once to reduce the number of separate requests being made to the API. #4075
|
||||
* Tweak - Improve error message displayed when a payment method didn't have all its dependencies registered. #4176
|
||||
* Tweak - Improvements to `emitEventWithAbort`. #4158
|
||||
* Tweak - Rename onCheckoutBeforeProcessing to onCheckoutValidationBeforeProcessing.
|
||||
* Tweak - Switched to `rest_preload_api_request` for API hydration in cart and checkout blocks. #4090
|
||||
* Fix - Prevent parts of old addresses being displayed in the shipping calculator when changing countries. #4038
|
||||
* Fix - issue in which email and phone fields are cleared when using a separate billing address. #4162
|
||||
|
||||
= 5.3.0 2021-05-11 =
|
||||
|
||||
**WooCommerce**
|
||||
|
|
|
@ -121,7 +121,7 @@ class WC_Geolocation {
|
|||
}
|
||||
}
|
||||
|
||||
set_transient( $transient_name, $external_ip_address, WEEK_IN_SECONDS );
|
||||
set_transient( $transient_name, $external_ip_address, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $external_ip_address;
|
||||
|
@ -161,7 +161,7 @@ class WC_Geolocation {
|
|||
* @param array $geolocation Geolocation data, including country, state, city, and postcode.
|
||||
* @param string $ip_address IP Address.
|
||||
*/
|
||||
$geolocation = apply_filters(
|
||||
$geolocation = apply_filters(
|
||||
'woocommerce_get_geolocation',
|
||||
array(
|
||||
'country' => $country_code,
|
||||
|
@ -302,7 +302,7 @@ class WC_Geolocation {
|
|||
}
|
||||
}
|
||||
|
||||
set_transient( 'geoip_' . $ip_address, $country_code, WEEK_IN_SECONDS );
|
||||
set_transient( 'geoip_' . $ip_address, $country_code, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
return $country_code;
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @package WooCommerce\Classes
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -34,10 +36,19 @@ class WC_Query {
|
|||
*/
|
||||
private static $chosen_attributes;
|
||||
|
||||
/**
|
||||
* The instance of the class that helps filtering with the product attributes lookup table.
|
||||
*
|
||||
* @var Filterer
|
||||
*/
|
||||
private $filterer;
|
||||
|
||||
/**
|
||||
* Constructor for the query class. Hooks in methods.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->filterer = wc_get_container()->get( Filterer::class );
|
||||
|
||||
add_action( 'init', array( $this, 'add_endpoints' ) );
|
||||
if ( ! is_admin() ) {
|
||||
add_action( 'wp_loaded', array( $this, 'get_errors' ), 20 );
|
||||
|
@ -49,6 +60,13 @@ class WC_Query {
|
|||
$this->init_query_vars();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the chosen attributes so that get_layered_nav_chosen_attributes will get them from the query again.
|
||||
*/
|
||||
public static function reset_chosen_attributes() {
|
||||
self::$chosen_attributes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any errors from querystring.
|
||||
*/
|
||||
|
@ -134,7 +152,7 @@ class WC_Query {
|
|||
$title = __( 'Add payment method', 'woocommerce' );
|
||||
break;
|
||||
case 'lost-password':
|
||||
if ( in_array( $action, array( 'rp', 'resetpass', 'newaccount' ) ) ) {
|
||||
if ( in_array( $action, array( 'rp', 'resetpass', 'newaccount' ), true ) ) {
|
||||
$title = __( 'Set password', 'woocommerce' );
|
||||
} else {
|
||||
$title = __( 'Lost password', 'woocommerce' );
|
||||
|
@ -487,12 +505,38 @@ class WC_Query {
|
|||
self::$product_query = $q;
|
||||
|
||||
// Additonal hooks to change WP Query.
|
||||
add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
|
||||
add_filter(
|
||||
'posts_clauses',
|
||||
function( $args, $wp_query ) {
|
||||
return $this->product_query_post_clauses( $args, $wp_query );
|
||||
},
|
||||
10,
|
||||
2
|
||||
);
|
||||
add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
|
||||
|
||||
do_action( 'woocommerce_product_query', $q, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra clauses to the product query.
|
||||
*
|
||||
* @param array $args Product query clauses.
|
||||
* @param WP_Query $wp_query The current product query.
|
||||
* @return array The updated product query clauses array.
|
||||
*/
|
||||
private function product_query_post_clauses( $args, $wp_query ) {
|
||||
$args = $this->price_filter_post_clauses( $args, $wp_query );
|
||||
$args = $this->filterer->filter_by_attribute_post_clauses( $args, $wp_query, $this->get_layered_nav_chosen_attributes() );
|
||||
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$args['where'] .= ' AND ' . $search;
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the query.
|
||||
*/
|
||||
|
@ -722,8 +766,8 @@ class WC_Query {
|
|||
);
|
||||
}
|
||||
|
||||
// Layered nav filters on terms.
|
||||
if ( $main_query ) {
|
||||
if ( $main_query && ! $this->filterer->filtering_via_lookup_table_is_active() ) {
|
||||
// Layered nav filters on terms.
|
||||
foreach ( $this->get_layered_nav_chosen_attributes() as $taxonomy => $data ) {
|
||||
$tax_query[] = array(
|
||||
'taxonomy' => $taxonomy,
|
||||
|
|
|
@ -96,23 +96,53 @@ class WC_Template_Loader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get the default filename for a template.
|
||||
* Checks whether a block template with that name exists.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @param string $template_name Template to check.
|
||||
* @return boolean
|
||||
*/
|
||||
private static function has_block_template( $template_name ) {
|
||||
if ( ! $template_name ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_readable(
|
||||
get_stylesheet_directory() . '/block-templates/' . $template_name . '.html'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default filename for a template except if a block template with
|
||||
* the same name exists.
|
||||
*
|
||||
* @since 3.0.0
|
||||
* @since 5.5.0 If a block template with the same name exists, return an
|
||||
* empty string.
|
||||
* @return string
|
||||
*/
|
||||
private static function get_template_loader_default_file() {
|
||||
if ( is_singular( 'product' ) ) {
|
||||
if (
|
||||
is_singular( 'product' ) &&
|
||||
! self::has_block_template( 'single-product' )
|
||||
) {
|
||||
$default_file = 'single-product.php';
|
||||
} elseif ( is_product_taxonomy() ) {
|
||||
$object = get_queried_object();
|
||||
|
||||
if ( is_tax( 'product_cat' ) || is_tax( 'product_tag' ) ) {
|
||||
$default_file = 'taxonomy-' . $object->taxonomy . '.php';
|
||||
} else {
|
||||
if ( self::has_block_template( 'taxonomy-' . $object->taxonomy ) ) {
|
||||
$default_file = '';
|
||||
} else {
|
||||
$default_file = 'taxonomy-' . $object->taxonomy . '.php';
|
||||
}
|
||||
} elseif ( ! self::has_block_template( 'archive-product' ) ) {
|
||||
$default_file = 'archive-product.php';
|
||||
}
|
||||
} elseif ( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) {
|
||||
} elseif (
|
||||
( is_post_type_archive( 'product' ) || is_page( wc_get_page_id( 'shop' ) ) ) &&
|
||||
! self::has_block_template( 'archive-product' )
|
||||
) {
|
||||
$default_file = self::$theme_support ? 'archive-product.php' : '';
|
||||
} else {
|
||||
$default_file = '';
|
||||
|
|
|
@ -21,6 +21,7 @@ class WC_Shop_Customizer {
|
|||
add_action( 'customize_controls_print_styles', array( $this, 'add_styles' ) );
|
||||
add_action( 'customize_controls_print_scripts', array( $this, 'add_scripts' ), 30 );
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'add_frontend_scripts' ) );
|
||||
add_action( 'admin_menu', array( $this, 'add_fse_customize_link' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,6 +84,19 @@ class WC_Shop_Customizer {
|
|||
width: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
<?php
|
||||
// For FSE themes hide the back button so we only surface WooCommerce options.
|
||||
if ( function_exists( 'gutenberg_is_fse_theme' ) && gutenberg_is_fse_theme() ) {
|
||||
?>
|
||||
#sub-accordion-panel-woocommerce .customize-panel-back{
|
||||
display: none;
|
||||
}
|
||||
#customize-controls #sub-accordion-panel-woocommerce .panel-meta.customize-info .accordion-section-title {
|
||||
margin-left: 0;
|
||||
}
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
</style>
|
||||
<?php
|
||||
}
|
||||
|
@ -251,6 +265,29 @@ class WC_Shop_Customizer {
|
|||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* For FSE themes add a "Customize WooCommerce" link to the Appearance menu.
|
||||
*
|
||||
* FSE themes hide the "Customize" link in the Appearance menu. In WooCommerce we have several options that can currently
|
||||
* only be edited via the Customizer. For now, we are thus adding a new link for WooCommerce specific Customizer options.
|
||||
*/
|
||||
public function add_fse_customize_link() {
|
||||
|
||||
// Exit early if the FSE theme feature isn't present or the current theme is not a FSE theme.
|
||||
if ( ! function_exists( 'gutenberg_is_fse_theme' ) || function_exists( 'gutenberg_is_fse_theme' ) && ! gutenberg_is_fse_theme() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a link to the WooCommerce panel in the Customizer.
|
||||
add_submenu_page(
|
||||
'themes.php',
|
||||
__( 'Customize WooCommerce', 'woocommerce' ),
|
||||
__( 'Customize WooCommerce', 'woocommerce' ),
|
||||
'edit_theme_options',
|
||||
admin_url( 'customize.php?autofocus[panel]=woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the shop page & category display setting.
|
||||
*
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* @version 2.6.0
|
||||
*/
|
||||
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
/**
|
||||
|
@ -342,72 +344,7 @@ class WC_Widget_Layered_Nav extends WC_Widget {
|
|||
* @return array
|
||||
*/
|
||||
protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
|
||||
global $wpdb;
|
||||
|
||||
$tax_query = $this->get_main_tax_query();
|
||||
$meta_query = $this->get_main_meta_query();
|
||||
|
||||
if ( 'or' === $query_type ) {
|
||||
foreach ( $tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
unset( $tax_query[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$meta_query = new WP_Meta_Query( $meta_query );
|
||||
$tax_query = new WP_Tax_Query( $tax_query );
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
|
||||
$term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||
|
||||
// Generate query.
|
||||
$query = array();
|
||||
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) AS term_count, terms.term_id AS term_count_id";
|
||||
$query['from'] = "FROM {$wpdb->posts}";
|
||||
$query['join'] = "
|
||||
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id
|
||||
INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->terms} AS terms USING( term_id )
|
||||
" . $tax_query_sql['join'] . $meta_query_sql['join'];
|
||||
|
||||
$query['where'] = "
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
{$tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND terms.term_id IN $term_ids_sql";
|
||||
|
||||
$search = $this->get_main_search_query_sql();
|
||||
if ( $search ) {
|
||||
$query['where'] .= ' AND ' . $search;
|
||||
}
|
||||
|
||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
||||
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
|
||||
$query_sql = implode( ' ', $query );
|
||||
|
||||
// We have a query - let's see if cached results of this query already exist.
|
||||
$query_hash = md5( $query_sql );
|
||||
|
||||
// Maybe store a transient of the count values.
|
||||
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
|
||||
if ( true === $cache ) {
|
||||
$cached_counts = (array) get_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ) );
|
||||
} else {
|
||||
$cached_counts = array();
|
||||
}
|
||||
|
||||
if ( ! isset( $cached_counts[ $query_hash ] ) ) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$results = $wpdb->get_results( $query_sql, ARRAY_A );
|
||||
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
|
||||
$cached_counts[ $query_hash ] = $counts;
|
||||
if ( true === $cache ) {
|
||||
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
|
||||
return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
|
||||
return wc_get_container()->get( Filterer::class )->get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,9 @@ namespace Automattic\WooCommerce\Internal\DependencyManagement\ServiceProviders;
|
|||
|
||||
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\DataRegenerator;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer;
|
||||
use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
|
||||
use Automattic\WooCommerce\Proxies\LegacyProxy;
|
||||
|
||||
/**
|
||||
* Service provider for the ProductAttributesLookupServiceProvider namespace.
|
||||
|
@ -21,6 +23,8 @@ class ProductAttributesLookupServiceProvider extends AbstractServiceProvider {
|
|||
*/
|
||||
protected $provides = array(
|
||||
DataRegenerator::class,
|
||||
Filterer::class,
|
||||
LookupDataStore::class,
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -28,6 +32,7 @@ class ProductAttributesLookupServiceProvider extends AbstractServiceProvider {
|
|||
*/
|
||||
public function register() {
|
||||
$this->share( DataRegenerator::class )->addArgument( LookupDataStore::class );
|
||||
$this->share( Filterer::class )->addArgument( LookupDataStore::class )->addArgument( LegacyProxy::class );
|
||||
$this->share( LookupDataStore::class );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,318 @@
|
|||
<?php
|
||||
/**
|
||||
* Filterer class file.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\ProductAttributesLookup;
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for filtering products using the product attributes lookup table.
|
||||
*/
|
||||
class Filterer {
|
||||
|
||||
/**
|
||||
* The product attributes lookup data store to use.
|
||||
*
|
||||
* @var LookupDataStore
|
||||
*/
|
||||
private $data_store;
|
||||
|
||||
/**
|
||||
* The name of the product attributes lookup table.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $lookup_table_name;
|
||||
|
||||
/**
|
||||
* Class initialization, invoked by the DI container.
|
||||
*
|
||||
* @internal
|
||||
* @param LookupDataStore $data_store The data store to use.
|
||||
*/
|
||||
final public function init( LookupDataStore $data_store ) {
|
||||
$this->data_store = $data_store;
|
||||
$this->lookup_table_name = $data_store->get_lookup_table_name();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the product attribute filtering via lookup table feature is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function filtering_via_lookup_table_is_active() {
|
||||
return 'yes' === get_option( 'woocommerce_attribute_lookup__enabled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds post clauses for filtering via lookup table.
|
||||
* This method should be invoked within a 'posts_clauses' filter.
|
||||
*
|
||||
* @param array $args Product query clauses as supplied to the 'posts_clauses' filter.
|
||||
* @param \WP_Query $wp_query Current product query as supplied to the 'posts_clauses' filter.
|
||||
* @param array $attributes_to_filter_by Attribute filtering data as generated by WC_Query::get_layered_nav_chosen_attributes.
|
||||
* @return array The updated product query clauses.
|
||||
*/
|
||||
public function filter_by_attribute_post_clauses( array $args, \WP_Query $wp_query, array $attributes_to_filter_by ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( ! $wp_query->is_main_query() || ! $this->filtering_via_lookup_table_is_active() ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
$clause_root = " {$wpdb->prefix}posts.ID IN (";
|
||||
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
|
||||
$in_stock_clause = ' AND in_stock = 1';
|
||||
} else {
|
||||
$in_stock_clause = '';
|
||||
}
|
||||
|
||||
foreach ( $attributes_to_filter_by as $taxonomy => $data ) {
|
||||
$all_terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
|
||||
$term_ids_by_slug = wp_list_pluck( $all_terms, 'term_id', 'slug' );
|
||||
$term_ids_to_filter_by = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) );
|
||||
$term_ids_to_filter_by = array_map( 'absint', $term_ids_to_filter_by );
|
||||
$term_ids_to_filter_by_list = '(' . join( ',', $term_ids_to_filter_by ) . ')';
|
||||
$is_and_query = 'and' === $data['query_type'];
|
||||
|
||||
$count = count( $term_ids_to_filter_by );
|
||||
if ( 0 !== $count ) {
|
||||
if ( $is_and_query ) {
|
||||
$clauses[] = "
|
||||
{$clause_root}
|
||||
SELECT product_or_parent_id
|
||||
FROM {$this->lookup_table_name} lt
|
||||
WHERE is_variation_attribute=0
|
||||
{$in_stock_clause}
|
||||
AND term_id in {$term_ids_to_filter_by_list}
|
||||
GROUP BY product_id
|
||||
HAVING COUNT(product_id)={$count}
|
||||
UNION
|
||||
SELECT product_or_parent_id
|
||||
FROM {$this->lookup_table_name} lt
|
||||
WHERE is_variation_attribute=1
|
||||
{$in_stock_clause}
|
||||
AND term_id in {$term_ids_to_filter_by_list}
|
||||
)";
|
||||
} else {
|
||||
$clauses[] = "
|
||||
{$clause_root}
|
||||
SELECT product_or_parent_id
|
||||
FROM {$this->lookup_table_name} lt
|
||||
WHERE term_id in {$term_ids_to_filter_by_list}
|
||||
{$in_stock_clause}
|
||||
)";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $clauses ) ) {
|
||||
$args['where'] .= ' AND (' . join( ' AND ', $clauses ) . ')';
|
||||
} elseif ( ! empty( $attributes_to_filter_by ) ) {
|
||||
$args['where'] .= ' AND 1=0';
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count products within certain terms, taking the main WP query into consideration,
|
||||
* for the WC_Widget_Layered_Nav widget.
|
||||
*
|
||||
* This query allows counts to be generated based on the viewed products, not all products.
|
||||
*
|
||||
* @param array $term_ids Term IDs.
|
||||
* @param string $taxonomy Taxonomy.
|
||||
* @param string $query_type Query Type.
|
||||
* @return array
|
||||
*/
|
||||
public function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
|
||||
global $wpdb;
|
||||
|
||||
$use_lookup_table = $this->filtering_via_lookup_table_is_active();
|
||||
|
||||
$tax_query = \WC_Query::get_main_tax_query();
|
||||
$meta_query = \WC_Query::get_main_meta_query();
|
||||
if ( 'or' === $query_type ) {
|
||||
foreach ( $tax_query as $key => $query ) {
|
||||
if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
|
||||
unset( $tax_query[ $key ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$meta_query = new \WP_Meta_Query( $meta_query );
|
||||
$tax_query = new \WP_Tax_Query( $tax_query );
|
||||
|
||||
if ( $use_lookup_table ) {
|
||||
$query = $this->get_product_counts_query_using_lookup_table( $tax_query, $meta_query, $taxonomy, $term_ids );
|
||||
} else {
|
||||
$query = $this->get_product_counts_query_not_using_lookup_table( $tax_query, $meta_query, $term_ids );
|
||||
}
|
||||
|
||||
$query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
|
||||
$query_sql = implode( ' ', $query );
|
||||
|
||||
// We have a query - let's see if cached results of this query already exist.
|
||||
$query_hash = md5( $query_sql );
|
||||
// Maybe store a transient of the count values.
|
||||
$cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
|
||||
if ( true === $cache ) {
|
||||
$cached_counts = (array) get_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ) );
|
||||
} else {
|
||||
$cached_counts = array();
|
||||
}
|
||||
if ( ! isset( $cached_counts[ $query_hash ] ) ) {
|
||||
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$results = $wpdb->get_results( $query_sql, ARRAY_A );
|
||||
$counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
|
||||
$cached_counts[ $query_hash ] = $counts;
|
||||
if ( true === $cache ) {
|
||||
set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query for counting products by terms using the product attributes lookup table.
|
||||
*
|
||||
* @param \WP_Tax_Query $tax_query The current main tax query.
|
||||
* @param \WP_Meta_Query $meta_query The current main meta query.
|
||||
* @param string $taxonomy The attribute name to get the term counts for.
|
||||
* @param string $term_ids The term ids to include in the search.
|
||||
* @return array An array of SQL query parts.
|
||||
*/
|
||||
private function get_product_counts_query_using_lookup_table( $tax_query, $meta_query, $taxonomy, $term_ids ) {
|
||||
global $wpdb;
|
||||
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $this->lookup_table_name, 'product_or_parent_id' );
|
||||
$tax_query_sql = $tax_query->get_sql( $this->lookup_table_name, 'product_or_parent_id' );
|
||||
$hide_out_of_stock = 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' );
|
||||
$in_stock_clause = $hide_out_of_stock ? ' AND in_stock = 1' : '';
|
||||
|
||||
$query['select'] = 'SELECT COUNT(DISTINCT product_or_parent_id) as term_count, term_id as term_count_id';
|
||||
$query['from'] = "FROM {$this->lookup_table_name}";
|
||||
$query['join'] = "INNER JOIN {$wpdb->posts} ON {$wpdb->posts}.ID = {$this->lookup_table_name}.product_or_parent_id";
|
||||
|
||||
$term_ids_sql = $this->get_term_ids_sql( $term_ids );
|
||||
$query['where'] = "
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
{$tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND {$this->lookup_table_name}.taxonomy='{$taxonomy}'
|
||||
AND {$this->lookup_table_name}.term_id IN $term_ids_sql
|
||||
{$in_stock_clause}";
|
||||
|
||||
if ( ! empty( $term_ids ) ) {
|
||||
$attributes_to_filter_by = \WC_Query::get_layered_nav_chosen_attributes();
|
||||
|
||||
if ( ! empty( $attributes_to_filter_by ) ) {
|
||||
$all_terms_to_filter_by = array();
|
||||
foreach ( $attributes_to_filter_by as $taxonomy => $data ) {
|
||||
$all_terms = get_terms( $taxonomy, array( 'hide_empty' => false ) );
|
||||
$term_ids_by_slug = wp_list_pluck( $all_terms, 'term_id', 'slug' );
|
||||
$term_ids_to_filter_by = array_values( array_intersect_key( $term_ids_by_slug, array_flip( $data['terms'] ) ) );
|
||||
$all_terms_to_filter_by = array_merge( $all_terms_to_filter_by, $term_ids_to_filter_by );
|
||||
$term_ids_to_filter_by_list = '(' . join( ',', $term_ids_to_filter_by ) . ')';
|
||||
|
||||
$count = count( $term_ids_to_filter_by );
|
||||
if ( 0 !== $count ) {
|
||||
$query['where'] .= ' AND product_or_parent_id IN (';
|
||||
if ( 'and' === $attributes_to_filter_by[ $taxonomy ]['query_type'] ) {
|
||||
$query['where'] .= "
|
||||
SELECT product_or_parent_id
|
||||
FROM {$this->lookup_table_name} lt
|
||||
WHERE is_variation_attribute=0
|
||||
{$in_stock_clause}
|
||||
AND term_id in {$term_ids_to_filter_by_list}
|
||||
GROUP BY product_id
|
||||
HAVING COUNT(product_id)={$count}
|
||||
UNION
|
||||
SELECT product_or_parent_id
|
||||
FROM {$this->lookup_table_name} lt
|
||||
WHERE is_variation_attribute=1
|
||||
{$in_stock_clause}
|
||||
AND term_id in {$term_ids_to_filter_by_list}
|
||||
)";
|
||||
} else {
|
||||
$query['where'] .= "
|
||||
SELECT product_or_parent_id FROM {$this->lookup_table_name}
|
||||
WHERE term_id in {$term_ids_to_filter_by_list}
|
||||
{$in_stock_clause}
|
||||
)";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$query['where'] .= $in_stock_clause;
|
||||
}
|
||||
} elseif ( $hide_out_of_stock ) {
|
||||
$query['where'] .= " AND {$this->lookup_table_name}.in_stock=1";
|
||||
}
|
||||
|
||||
$search_query_sql = \WC_Query::get_main_search_query_sql();
|
||||
if ( $search_query_sql ) {
|
||||
$query['where'] .= ' AND ' . $search_query_sql;
|
||||
}
|
||||
|
||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
||||
$query['group_by'] = "GROUP BY {$this->lookup_table_name}.term_id";
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query for counting products by terms NOT using the product attributes lookup table.
|
||||
*
|
||||
* @param \WP_Tax_Query $tax_query The current main tax query.
|
||||
* @param \WP_Meta_Query $meta_query The current main meta query.
|
||||
* @param string $term_ids The term ids to include in the search.
|
||||
* @return array An array of SQL query parts.
|
||||
*/
|
||||
private function get_product_counts_query_not_using_lookup_table( $tax_query, $meta_query, $term_ids ) {
|
||||
global $wpdb;
|
||||
|
||||
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
|
||||
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
|
||||
|
||||
// Generate query.
|
||||
$query = array();
|
||||
$query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) AS term_count, terms.term_id AS term_count_id";
|
||||
$query['from'] = "FROM {$wpdb->posts}";
|
||||
$query['join'] = "
|
||||
INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id
|
||||
INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
|
||||
INNER JOIN {$wpdb->terms} AS terms USING( term_id )
|
||||
" . $tax_query_sql['join'] . $meta_query_sql['join'];
|
||||
|
||||
$term_ids_sql = $this->get_term_ids_sql( $term_ids );
|
||||
$query['where'] = "
|
||||
WHERE {$wpdb->posts}.post_type IN ( 'product' )
|
||||
AND {$wpdb->posts}.post_status = 'publish'
|
||||
{$tax_query_sql['where']} {$meta_query_sql['where']}
|
||||
AND terms.term_id IN $term_ids_sql";
|
||||
|
||||
$search_query_sql = \WC_Query::get_main_search_query_sql();
|
||||
if ( $search_query_sql ) {
|
||||
$query['where'] .= ' AND ' . $search_query_sql;
|
||||
}
|
||||
|
||||
$query['group_by'] = 'GROUP BY terms.term_id';
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of term ids as "(id,id,id)".
|
||||
*
|
||||
* @param array $term_ids The list of terms to format.
|
||||
* @return string The formatted list.
|
||||
*/
|
||||
private function get_term_ids_sql( $term_ids ) {
|
||||
return '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
|
||||
}
|
||||
}
|
|
@ -94,6 +94,15 @@ AND table_name = %s;',
|
|||
$this->is_feature_visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the lookup table.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_lookup_table_name() {
|
||||
return $this->lookup_table_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert/update the appropriate lookup table entries for a new or modified product or variation.
|
||||
* This must be invoked after a product or a variation is created (including untrashing and duplication)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue