WooCommerce - Version 4.5.2

Version Description

  • 2020-09-14 =
  • Fix - Revert the changes in filtering by attribute that were introduced in WooCommerce 4.4. #27625
  • Fix - Adjusted validation to allow for variations with "0" as an attribute value. #27633
Download this release

Release Info

Developer sadowski
Plugin Icon 128x128 WooCommerce
Version 4.5.2
Comparing to
See all releases

Code changes from version 4.5.1 to 4.5.2

i18n/languages/woocommerce.pot CHANGED
@@ -2,14 +2,14 @@
2
  # This file is distributed under the same license as the WooCommerce plugin.
3
  msgid ""
4
  msgstr ""
5
- "Project-Id-Version: WooCommerce 4.5.1\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/woocommerce\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
- "POT-Creation-Date: 2020-09-09T15:51:54+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: woocommerce\n"
@@ -7200,7 +7200,7 @@ msgid "via %s"
7200
  msgstr ""
7201
 
7202
  #: includes/abstracts/abstract-wc-order.php:1972
7203
- #: includes/class-wc-cart.php:1547
7204
  #: includes/class-wc-product-grouped.php:120
7205
  msgid "Free!"
7206
  msgstr ""
@@ -17220,17 +17220,17 @@ msgstr ""
17220
  msgid "Sorry, we do not have enough \"%1$s\" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused."
17221
  msgstr ""
17222
 
17223
- #: includes/class-wc-cart.php:1068
17224
  msgid "Please choose product options&hellip;"
17225
  msgstr ""
17226
 
17227
  #. translators: %s: Attribute name.
17228
- #: includes/class-wc-cart.php:1100
17229
  msgid "Invalid value posted for %s"
17230
  msgstr ""
17231
 
17232
  #. translators: %s: Attribute name.
17233
- #: includes/class-wc-cart.php:1110
17234
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:802
17235
  msgid "%s is a required field"
17236
  msgid_plural "%s are required fields"
@@ -17238,19 +17238,19 @@ msgstr[0] ""
17238
  msgstr[1] ""
17239
 
17240
  #. translators: %s: product name
17241
- #: includes/class-wc-cart.php:1130
17242
  msgid "You cannot add another \"%s\" to your cart."
17243
  msgstr ""
17244
 
17245
- #: includes/class-wc-cart.php:1141
17246
- #: includes/class-wc-cart.php:1202
17247
  #: includes/class-wc-frontend-scripts.php:558
17248
  #: includes/wc-cart-functions.php:125
17249
  #: includes/wc-template-functions.php:2120
17250
  msgid "View cart"
17251
  msgstr ""
17252
 
17253
- #: includes/class-wc-cart.php:1146
17254
  #: packages/woocommerce-blocks/assets/js/atomic/blocks/product-elements/add-to-cart/shared/product-unavailable.js:7
17255
  #: packages/woocommerce-blocks/build/all-products.js:1
17256
  #: packages/woocommerce-blocks/build/atomic-block-components/add-to-cart-frontend.js:1
@@ -17259,19 +17259,19 @@ msgid "Sorry, this product cannot be purchased."
17259
  msgstr ""
17260
 
17261
  #. translators: %s: product name
17262
- #: includes/class-wc-cart.php:1161
17263
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:161
17264
  msgid "You cannot add &quot;%s&quot; to the cart because the product is out of stock."
17265
  msgstr ""
17266
 
17267
  #. translators: 1: product name 2: quantity in stock
17268
- #: includes/class-wc-cart.php:1178
17269
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:177
17270
  msgid "You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining)."
17271
  msgstr ""
17272
 
17273
  #. translators: 1: quantity in stock 2: current quantity
17274
- #: includes/class-wc-cart.php:1204
17275
  msgid "You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart."
17276
  msgstr ""
17277
 
2
  # This file is distributed under the same license as the WooCommerce plugin.
3
  msgid ""
4
  msgstr ""
5
+ "Project-Id-Version: WooCommerce 4.5.2\n"
6
  "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/woocommerce\n"
7
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
8
  "Language-Team: LANGUAGE <LL@li.org>\n"
9
  "MIME-Version: 1.0\n"
10
  "Content-Type: text/plain; charset=UTF-8\n"
11
  "Content-Transfer-Encoding: 8bit\n"
12
+ "POT-Creation-Date: 2020-09-14T17:19:58+00:00\n"
13
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
14
  "X-Generator: WP-CLI 2.4.0\n"
15
  "X-Domain: woocommerce\n"
7200
  msgstr ""
7201
 
7202
  #: includes/abstracts/abstract-wc-order.php:1972
7203
+ #: includes/class-wc-cart.php:1546
7204
  #: includes/class-wc-product-grouped.php:120
7205
  msgid "Free!"
7206
  msgstr ""
17220
  msgid "Sorry, we do not have enough \"%1$s\" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused."
17221
  msgstr ""
17222
 
17223
+ #: includes/class-wc-cart.php:1067
17224
  msgid "Please choose product options&hellip;"
17225
  msgstr ""
17226
 
17227
  #. translators: %s: Attribute name.
17228
+ #: includes/class-wc-cart.php:1099
17229
  msgid "Invalid value posted for %s"
17230
  msgstr ""
17231
 
17232
  #. translators: %s: Attribute name.
17233
+ #: includes/class-wc-cart.php:1109
17234
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:802
17235
  msgid "%s is a required field"
17236
  msgid_plural "%s are required fields"
17238
  msgstr[1] ""
17239
 
17240
  #. translators: %s: product name
17241
+ #: includes/class-wc-cart.php:1129
17242
  msgid "You cannot add another \"%s\" to your cart."
17243
  msgstr ""
17244
 
17245
+ #: includes/class-wc-cart.php:1140
17246
+ #: includes/class-wc-cart.php:1201
17247
  #: includes/class-wc-frontend-scripts.php:558
17248
  #: includes/wc-cart-functions.php:125
17249
  #: includes/wc-template-functions.php:2120
17250
  msgid "View cart"
17251
  msgstr ""
17252
 
17253
+ #: includes/class-wc-cart.php:1145
17254
  #: packages/woocommerce-blocks/assets/js/atomic/blocks/product-elements/add-to-cart/shared/product-unavailable.js:7
17255
  #: packages/woocommerce-blocks/build/all-products.js:1
17256
  #: packages/woocommerce-blocks/build/atomic-block-components/add-to-cart-frontend.js:1
17259
  msgstr ""
17260
 
17261
  #. translators: %s: product name
17262
+ #: includes/class-wc-cart.php:1160
17263
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:161
17264
  msgid "You cannot add &quot;%s&quot; to the cart because the product is out of stock."
17265
  msgstr ""
17266
 
17267
  #. translators: 1: product name 2: quantity in stock
17268
+ #: includes/class-wc-cart.php:1177
17269
  #: packages/woocommerce-blocks/src/StoreApi/Utilities/CartController.php:177
17270
  msgid "You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining)."
17271
  msgstr ""
17272
 
17273
  #. translators: 1: quantity in stock 2: current quantity
17274
+ #: includes/class-wc-cart.php:1203
17275
  msgid "You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart."
17276
  msgstr ""
17277
 
includes/class-wc-cart.php CHANGED
@@ -1032,7 +1032,6 @@ class WC_Cart extends WC_Legacy_Cart {
1032
 
1033
  // Gather posted attributes.
1034
  $posted_attributes = array();
1035
-
1036
  foreach ( $parent_data->get_attributes() as $attribute ) {
1037
  if ( ! $attribute['is_variation'] ) {
1038
  continue;
@@ -1048,7 +1047,7 @@ class WC_Cart extends WC_Legacy_Cart {
1048
  }
1049
 
1050
  // Don't include if it's empty.
1051
- if ( ! empty( $value ) ) {
1052
  $posted_attributes[ $attribute_key ] = $value;
1053
  }
1054
  }
1032
 
1033
  // Gather posted attributes.
1034
  $posted_attributes = array();
 
1035
  foreach ( $parent_data->get_attributes() as $attribute ) {
1036
  if ( ! $attribute['is_variation'] ) {
1037
  continue;
1047
  }
1048
 
1049
  // Don't include if it's empty.
1050
+ if ( ! empty( $value ) || '0' === $value ) {
1051
  $posted_attributes[ $attribute_key ] = $value;
1052
  }
1053
  }
includes/class-wc-product-variable.php CHANGED
@@ -593,94 +593,6 @@ class WC_Product_Variable extends WC_Product {
593
  return true;
594
  }
595
 
596
- /**
597
- * Returns whether or not the product is visible in the catalog (doesn't trigger filters).
598
- *
599
- * @return bool
600
- */
601
- protected function is_visible_core() {
602
- if ( ! $this->parent_is_visible_core() ) {
603
- return false;
604
- }
605
-
606
- $query_filters = $this->get_layered_nav_chosen_attributes();
607
- if ( empty( $query_filters ) ) {
608
- return true;
609
- }
610
-
611
- /**
612
- * If there are attribute filters in the request, a variable product will be visible
613
- * if at least one of the available variations matches the filters.
614
- */
615
-
616
- $attributes_with_terms = array();
617
- array_walk(
618
- $query_filters,
619
- function( $value, $key ) use ( &$attributes_with_terms ) {
620
- $attributes_with_terms[ $key ] = $value['terms'];
621
- }
622
- );
623
-
624
- $variations = $this->get_available_variations( 'objects' );
625
- foreach ( $variations as $variation ) {
626
- if ( $this->variation_matches_filters( $variation, $attributes_with_terms ) ) {
627
- return true;
628
- }
629
- }
630
-
631
- return false;
632
- }
633
-
634
- /**
635
- * Checks if a given variation matches the active attribute filters.
636
- *
637
- * @param WC_Product_Variation $variation The variation to check.
638
- * @param array $query_filters The active filters as an array of attribute_name => [term1, term2...].
639
- *
640
- * @return bool True if the variation matches the active attribute filters.
641
- */
642
- private function variation_matches_filters( WC_Product_Variation $variation, array $query_filters ) {
643
- // Get the variation attributes as an array of attribute_name => attribute_value.
644
- // The array_filter will filter out attributes having a value of '', these correspond
645
- // to "Any..." variations that don't participate in filtering.
646
- $variation_attributes = array_filter( $variation->get_variation_attributes( false ) );
647
-
648
- $variation_attribute_names_in_filters = array_intersect( array_keys( $query_filters ), array_keys( $variation_attributes ) );
649
- if ( empty( $variation_attribute_names_in_filters ) ) {
650
- // The variation doesn't have any attribute that participates in filtering so we consider it a match.
651
- return true;
652
- }
653
-
654
- foreach ( $variation_attribute_names_in_filters as $attribute_name ) {
655
- if ( ! in_array( $variation_attributes[ $attribute_name ], $query_filters[ $attribute_name ], true ) ) {
656
- // Multiple filters interact with AND logic, so as soon as one of them
657
- // doesn't match then the variation doesn't match.
658
- return false;
659
- }
660
- }
661
-
662
- return true;
663
- }
664
-
665
- /**
666
- * What does is_visible_core in the parent class say?
667
- * This method exists to ease unit testing.
668
- *
669
- * @return bool
670
- */
671
- protected function parent_is_visible_core() {
672
- return parent::is_visible_core();
673
- }
674
-
675
- /**
676
- * Get an array of attributes and terms selected with the layered nav widget.
677
- * This method exists to ease unit testing.
678
- *
679
- * @return array
680
- */
681
- protected function get_layered_nav_chosen_attributes() {
682
- return WC()->query::get_layered_nav_chosen_attributes();
683
- }
684
 
685
  /*
686
  |--------------------------------------------------------------------------
593
  return true;
594
  }
595
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
596
 
597
  /*
598
  |--------------------------------------------------------------------------
includes/class-wc-query.php CHANGED
@@ -362,23 +362,10 @@ class WC_Query {
362
  if ( 'product_query' !== $query->get( 'wc_query' ) ) {
363
  return $posts;
364
  }
365
- $this->adjust_total_pages();
366
  $this->remove_product_query_filters( $posts );
367
  return $posts;
368
  }
369
 
370
- /**
371
- * The 'adjust_posts_count' method that handles the 'found_posts' filter indirectly initializes
372
- * the loop properties with a call to 'wc_setup_loop'. This includes setting 'total_pages' to
373
- * '$GLOBALS['wp_query']->max_num_pages', which at that point has a value of zero.
374
- * Thus we need to set the real value from the 'the_posts' filter, where $GLOBALS['wp_query']->max_num_pages'
375
- * will aready have been initialized.
376
- */
377
- private function adjust_total_pages() {
378
- if ( 0 === wc_get_loop_prop( 'total_pages' ) ) {
379
- wc_set_loop_prop( 'total_pages', $GLOBALS['wp_query']->max_num_pages );
380
- }
381
- }
382
 
383
  /**
384
  * Pre_get_posts above may adjust the main query to add WooCommerce logic. When this query is done, we need to ensure
@@ -396,12 +383,9 @@ class WC_Query {
396
  }
397
 
398
  /**
399
- * When we are listing products and the request is filtering by attributes via layered nav plugin
400
- * we need to adjust the total posts count to account for variable products having stock
401
- * in some variations but not in others.
402
- * We do that by just checking if each product is visible.
403
- *
404
- * We also cache the post visibility so that it isn't checked again when displaying the posts list.
405
  *
406
  * @since 4.4.0
407
  * @param int $count Original posts count, as supplied by the found_posts filter.
@@ -410,35 +394,6 @@ class WC_Query {
410
  * @return int Adjusted posts count.
411
  */
412
  public function adjust_posts_count( $count, $query ) {
413
- if ( ! $query->get( 'wc_query' ) ) {
414
- return $count;
415
- }
416
-
417
- $posts = $this->get_current_posts();
418
- if ( is_null( $posts ) ) {
419
- return $count;
420
- }
421
-
422
- foreach ( $posts as $post ) {
423
- if ( is_object( $post ) && 'product' !== $post->post_type ) {
424
- continue;
425
- }
426
-
427
- $product_id = is_object( $post ) ? $post->ID : $post;
428
- $product = wc_get_product( $product_id );
429
- if ( ! is_object( $product ) ) {
430
- continue;
431
- }
432
-
433
- if ( $product->is_visible() ) {
434
- wc_set_loop_product_visibility( $product_id, true );
435
- } else {
436
- wc_set_loop_product_visibility( $product_id, false );
437
- $count--;
438
- }
439
- }
440
-
441
- wc_set_loop_prop( 'total', $count );
442
  return $count;
443
  }
444
 
@@ -514,7 +469,7 @@ class WC_Query {
514
  // Additonal hooks to change WP Query.
515
  add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
516
  add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
517
- add_filter( 'found_posts', array( $this, 'adjust_posts_count' ), 10, 2 );
518
  do_action( 'woocommerce_product_query', $q, $this );
519
  }
520
 
362
  if ( 'product_query' !== $query->get( 'wc_query' ) ) {
363
  return $posts;
364
  }
 
365
  $this->remove_product_query_filters( $posts );
366
  return $posts;
367
  }
368
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  /**
371
  * Pre_get_posts above may adjust the main query to add WooCommerce logic. When this query is done, we need to ensure
383
  }
384
 
385
  /**
386
+ * This function used to be hooked to found_posts and adjust the posts count when the filtering by attribute
387
+ * widget was used and variable products were present. Now it isn't hooked anymore and does nothing but return
388
+ * the input unchanged, since the pull request in which it was introduced has been reverted.
 
 
 
389
  *
390
  * @since 4.4.0
391
  * @param int $count Original posts count, as supplied by the found_posts filter.
394
  * @return int Adjusted posts count.
395
  */
396
  public function adjust_posts_count( $count, $query ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  return $count;
398
  }
399
 
469
  // Additonal hooks to change WP Query.
470
  add_filter( 'posts_clauses', array( $this, 'price_filter_post_clauses' ), 10, 2 );
471
  add_filter( 'the_posts', array( $this, 'handle_get_posts' ), 10, 2 );
472
+
473
  do_action( 'woocommerce_product_query', $q, $this );
474
  }
475
 
includes/class-woocommerce.php CHANGED
@@ -22,7 +22,7 @@ final class WooCommerce {
22
  *
23
  * @var string
24
  */
25
- public $version = '4.5.1';
26
 
27
  /**
28
  * WooCommerce Schema version.
22
  *
23
  * @var string
24
  */
25
+ public $version = '4.5.2';
26
 
27
  /**
28
  * WooCommerce Schema version.
includes/widgets/class-wc-widget-layered-nav.php CHANGED
@@ -344,93 +344,50 @@ class WC_Widget_Layered_Nav extends WC_Widget {
344
  protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
345
  global $wpdb;
346
 
347
- $main_tax_query = $this->get_main_tax_query();
348
- $meta_query = $this->get_main_meta_query();
349
 
350
- $non_variable_tax_query_sql = array( 'where' => '' );
351
- $is_and_query = 'and' === $query_type;
352
-
353
- foreach ( $main_tax_query as $key => $query ) {
354
- if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
355
- if ( $is_and_query ) {
356
- $non_variable_tax_query_sql = $this->convert_tax_query_to_sql( array( $query ) );
357
  }
358
- unset( $main_tax_query[ $key ] );
359
  }
360
  }
361
 
362
- $exclude_variable_products_tax_query_sql = $this->get_extra_tax_query_sql( 'product_type', array( 'variable' ), 'NOT IN' );
363
-
364
- $meta_query_sql = ( new WP_Meta_Query( $meta_query ) )->get_sql( 'post', $wpdb->posts, 'ID' );
365
- $main_tax_query_sql = $this->convert_tax_query_to_sql( $main_tax_query );
366
- $term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
367
 
368
- // Generate the first part of the query.
369
- // This one will return non-variable products and variable products with concrete values for the attributes.
370
  $query = array();
371
- $query['select'] = "SELECT IF({$wpdb->posts}.post_type='product_variation', {$wpdb->posts}.post_parent, {$wpdb->posts}.ID) AS product_id, terms.term_id AS term_count_id";
372
  $query['from'] = "FROM {$wpdb->posts}";
373
  $query['join'] = "
374
- INNER JOIN {$wpdb->term_relationships} AS tr ON {$wpdb->posts}.ID = tr.object_id
375
  INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
376
  INNER JOIN {$wpdb->terms} AS terms USING( term_id )
377
- {$main_tax_query_sql['join']} {$meta_query_sql['join']}"; // Not an omission, really no more JOINs required.
378
-
379
- $variable_where_part = "
380
- OR ({$wpdb->posts}.post_type = 'product_variation'
381
- AND NOT EXISTS (
382
- SELECT ID FROM {$wpdb->posts} AS parent
383
- WHERE parent.ID = {$wpdb->posts}.post_parent AND parent.post_status NOT IN ('publish')
384
- ))
385
- ";
386
-
387
- $search_sql = '';
388
- $search = $this->get_main_search_query_sql();
389
- if ( $search ) {
390
- $search_sql = ' AND ' . $search;
391
- }
392
 
393
  $query['where'] = "
394
- WHERE
395
- {$wpdb->posts}.post_status = 'publish'
396
- {$main_tax_query_sql['where']} {$meta_query_sql['where']}
397
- AND (
398
- (
399
- {$wpdb->posts}.post_type = 'product'
400
- {$exclude_variable_products_tax_query_sql['where']}
401
- {$non_variable_tax_query_sql['where']}
402
- )
403
- {$variable_where_part}
404
- )
405
- AND terms.term_id IN {$term_ids_sql}
406
- {$search_sql}";
407
 
408
  $search = $this->get_main_search_query_sql();
409
  if ( $search ) {
410
  $query['where'] .= ' AND ' . $search;
411
  }
412
 
413
- $query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
414
- $main_query_sql = implode( ' ', $query );
 
415
 
416
- // Generate the second part of the query.
417
- // This one will return products having "Any..." as the value of the attribute.
418
-
419
- $query_sql_for_attributes_with_any_value = "
420
- SELECT {$wpdb->posts}.ID AS product_id, {$wpdb->term_relationships}.term_taxonomy_id as term_count_id FROM {$wpdb->posts}
421
- JOIN {$wpdb->posts} variations ON variations.post_parent = {$wpdb->posts}.ID
422
- LEFT JOIN {$wpdb->postmeta} ON variations.ID = {$wpdb->postmeta}.post_id AND {$wpdb->postmeta}.meta_key = 'attribute_$taxonomy'
423
- JOIN {$wpdb->term_relationships} ON {$wpdb->term_relationships}.object_id = {$wpdb->posts}.ID
424
- WHERE ( {$wpdb->postmeta}.meta_key IS NULL OR {$wpdb->postmeta}.meta_value = '')
425
- AND {$wpdb->posts}.post_type = 'product'
426
- AND {$wpdb->posts}.post_status = 'publish'
427
- AND variations.post_status = 'publish'
428
- AND variations.post_type = 'product_variation'
429
- AND {$wpdb->term_relationships}.term_taxonomy_id in $term_ids_sql
430
- {$main_tax_query_sql['where']}";
431
-
432
- // We have two queries - let's see if cached results of this query already exist.
433
- $query_hash = md5( $main_query_sql . $query_sql_for_attributes_with_any_value );
434
 
435
  // Maybe store a transient of the count values.
436
  $cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
@@ -441,7 +398,9 @@ class WC_Widget_Layered_Nav extends WC_Widget {
441
  }
442
 
443
  if ( ! isset( $cached_counts[ $query_hash ] ) ) {
444
- $counts = $this->get_term_product_counts_from_queries( $main_query_sql, $query_sql_for_attributes_with_any_value );
 
 
445
  $cached_counts[ $query_hash ] = $counts;
446
  if ( true === $cache ) {
447
  set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
@@ -451,30 +410,6 @@ class WC_Widget_Layered_Nav extends WC_Widget {
451
  return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
452
  }
453
 
454
- /**
455
- * Get the count of terms for products, using a set of SQL queries that are return pairs of product id - term id.
456
- *
457
- * @param string $main_query_sql The SQL query to use in order to count products with concrete values for attributes, must return a "product_id" column and a "terms_count_id" column.
458
- * @param string $query_sql_for_attributes_with_any_value The SQL query to use in order to count products with "Any" values for attributes, must return a "product_id" column and a "terms_count_id" column.
459
- *
460
- * @return array An array where the keys are term ids, and the values are term counts.
461
- */
462
- private function get_term_product_counts_from_queries( $main_query_sql, $query_sql_for_attributes_with_any_value ) {
463
- global $wpdb;
464
-
465
- $total_counts = null;
466
-
467
- $query = "
468
- SELECT COUNT(DISTINCT(product_id)) AS term_count, term_count_id FROM (
469
- {$main_query_sql}
470
- UNION ALL
471
- {$query_sql_for_attributes_with_any_value}
472
- ) AS x GROUP BY term_count_id";
473
-
474
- $results = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
475
- return array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
476
- }
477
-
478
  /**
479
  * Wrapper for WC_Query::get_main_tax_query() to ease unit testing.
480
  *
@@ -505,45 +440,6 @@ class WC_Widget_Layered_Nav extends WC_Widget {
505
  return WC_Query::get_main_meta_query();
506
  }
507
 
508
- /**
509
- * Get a tax query SQL for a given set of taxonomy, terms and operator.
510
- * Uses an intermediate WP_Tax_Query object.
511
- *
512
- * @since 4.4.0
513
- * @param string $taxonomy Taxonomy name.
514
- * @param array $terms Terms to include in the query.
515
- * @param string $operator Query operator, as supported by WP_Tax_Query; e.g. "NOT IN".
516
- *
517
- * @return array
518
- */
519
- private function get_extra_tax_query_sql( $taxonomy, $terms, $operator ) {
520
- $query = array(
521
- array(
522
- 'taxonomy' => $taxonomy,
523
- 'field' => 'slug',
524
- 'terms' => $terms,
525
- 'operator' => $operator,
526
- 'include_children' => false,
527
- ),
528
- );
529
-
530
- return $this->convert_tax_query_to_sql( $query );
531
- }
532
-
533
- /**
534
- * Convert a tax query array to SQL using an intermediate WP_Tax_Query object.
535
- *
536
- * @since 4.4.0
537
- * @param array $query Query array in the same format accepted by WP_Tax_Query constructor.
538
- *
539
- * @return array Query SQL as returned by WP_Tax_Query->get_sql.
540
- */
541
- private function convert_tax_query_to_sql( $query ) {
542
- global $wpdb;
543
-
544
- return ( new WP_Tax_Query( $query ) )->get_sql( $wpdb->posts, 'ID' );
545
- }
546
-
547
  /**
548
  * Show list based layered nav.
549
  *
344
  protected function get_filtered_term_product_counts( $term_ids, $taxonomy, $query_type ) {
345
  global $wpdb;
346
 
347
+ $tax_query = $this->get_main_tax_query();
348
+ $meta_query = $this->get_main_meta_query();
349
 
350
+ if ( 'or' === $query_type ) {
351
+ foreach ( $tax_query as $key => $query ) {
352
+ if ( is_array( $query ) && $taxonomy === $query['taxonomy'] ) {
353
+ unset( $tax_query[ $key ] );
 
 
 
354
  }
 
355
  }
356
  }
357
 
358
+ $meta_query = new WP_Meta_Query( $meta_query );
359
+ $tax_query = new WP_Tax_Query( $tax_query );
360
+ $meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
361
+ $tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
362
+ $term_ids_sql = '(' . implode( ',', array_map( 'absint', $term_ids ) ) . ')';
363
 
364
+ // Generate query.
 
365
  $query = array();
366
+ $query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) AS term_count, terms.term_id AS term_count_id";
367
  $query['from'] = "FROM {$wpdb->posts}";
368
  $query['join'] = "
369
+ INNER JOIN {$wpdb->term_relationships} AS term_relationships ON {$wpdb->posts}.ID = term_relationships.object_id
370
  INNER JOIN {$wpdb->term_taxonomy} AS term_taxonomy USING( term_taxonomy_id )
371
  INNER JOIN {$wpdb->terms} AS terms USING( term_id )
372
+ " . $tax_query_sql['join'] . $meta_query_sql['join'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
 
374
  $query['where'] = "
375
+ WHERE {$wpdb->posts}.post_type IN ( 'product' )
376
+ AND {$wpdb->posts}.post_status = 'publish'
377
+ {$tax_query_sql['where']} {$meta_query_sql['where']}
378
+ AND terms.term_id IN $term_ids_sql";
 
 
 
 
 
 
 
 
 
379
 
380
  $search = $this->get_main_search_query_sql();
381
  if ( $search ) {
382
  $query['where'] .= ' AND ' . $search;
383
  }
384
 
385
+ $query['group_by'] = 'GROUP BY terms.term_id';
386
+ $query = apply_filters( 'woocommerce_get_filtered_term_product_counts_query', $query );
387
+ $query_sql = implode( ' ', $query );
388
 
389
+ // We have a query - let's see if cached results of this query already exist.
390
+ $query_hash = md5( $query_sql );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
 
392
  // Maybe store a transient of the count values.
393
  $cache = apply_filters( 'woocommerce_layered_nav_count_maybe_cache', true );
398
  }
399
 
400
  if ( ! isset( $cached_counts[ $query_hash ] ) ) {
401
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
402
+ $results = $wpdb->get_results( $query_sql, ARRAY_A );
403
+ $counts = array_map( 'absint', wp_list_pluck( $results, 'term_count', 'term_count_id' ) );
404
  $cached_counts[ $query_hash ] = $counts;
405
  if ( true === $cache ) {
406
  set_transient( 'wc_layered_nav_counts_' . sanitize_title( $taxonomy ), $cached_counts, DAY_IN_SECONDS );
410
  return array_map( 'absint', (array) $cached_counts[ $query_hash ] );
411
  }
412
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  /**
414
  * Wrapper for WC_Query::get_main_tax_query() to ease unit testing.
415
  *
440
  return WC_Query::get_main_meta_query();
441
  }
442
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
443
  /**
444
  * Show list based layered nav.
445
  *
readme.txt CHANGED
@@ -4,7 +4,7 @@ Tags: e-commerce, store, sales, sell, woo, shop, cart, checkout, downloadable, d
4
  Requires at least: 5.3
5
  Tested up to: 5.5
6
  Requires PHP: 7.0
7
- Stable tag: 4.5.1
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
@@ -160,6 +160,10 @@ WooCommerce comes with some sample data you can use to see how products look; im
160
 
161
  == Changelog ==
162
 
 
 
 
 
163
  = 4.5.1 - 2020-09-09 =
164
 
165
  **WooCommerce**
4
  Requires at least: 5.3
5
  Tested up to: 5.5
6
  Requires PHP: 7.0
7
+ Stable tag: 4.5.2
8
  License: GPLv3
9
  License URI: https://www.gnu.org/licenses/gpl-3.0.html
10
 
160
 
161
  == Changelog ==
162
 
163
+ = 4.5.2 - 2020-09-14 =
164
+ * Fix - Revert the changes in filtering by attribute that were introduced in WooCommerce 4.4. #27625
165
+ * Fix - Adjusted validation to allow for variations with "0" as an attribute value. #27633
166
+
167
  = 4.5.1 - 2020-09-09 =
168
 
169
  **WooCommerce**
templates/content-product.php CHANGED
@@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || exit;
20
  global $product;
21
 
22
  // Ensure visibility.
23
- if ( empty( $product ) || false === wc_get_loop_product_visibility( $product->get_id() ) || ! $product->is_visible() ) {
24
  return;
25
  }
26
  ?>
20
  global $product;
21
 
22
  // Ensure visibility.
23
+ if ( empty( $product ) || ! $product->is_visible() ) {
24
  return;
25
  }
26
  ?>
vendor/autoload.php CHANGED
@@ -4,4 +4,4 @@
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
- return ComposerAutoloaderInitc26b76c4bb2f02a9f28d9d7388d0189a::getLoader();
4
 
5
  require_once __DIR__ . '/composer/autoload_real.php';
6
 
7
+ return ComposerAutoloaderInit1e69adc4e12f3a0de649acee72d963eb::getLoader();
vendor/autoload_packages.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/class-autoloader-handler.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/class-classes-handler.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/class-files-handler.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/class-plugins-handler.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/class-version-selector.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
vendor/composer/autoload_real.php CHANGED
@@ -2,7 +2,7 @@
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
- class ComposerAutoloaderInitc26b76c4bb2f02a9f28d9d7388d0189a
6
  {
7
  private static $loader;
8
 
@@ -19,15 +19,15 @@ class ComposerAutoloaderInitc26b76c4bb2f02a9f28d9d7388d0189a
19
  return self::$loader;
20
  }
21
 
22
- spl_autoload_register(array('ComposerAutoloaderInitc26b76c4bb2f02a9f28d9d7388d0189a', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
- spl_autoload_unregister(array('ComposerAutoloaderInitc26b76c4bb2f02a9f28d9d7388d0189a', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
  require_once __DIR__ . '/autoload_static.php';
29
 
30
- call_user_func(\Composer\Autoload\ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a::getInitializer($loader));
31
  } else {
32
  $map = require __DIR__ . '/autoload_namespaces.php';
33
  foreach ($map as $namespace => $path) {
2
 
3
  // autoload_real.php @generated by Composer
4
 
5
+ class ComposerAutoloaderInit1e69adc4e12f3a0de649acee72d963eb
6
  {
7
  private static $loader;
8
 
19
  return self::$loader;
20
  }
21
 
22
+ spl_autoload_register(array('ComposerAutoloaderInit1e69adc4e12f3a0de649acee72d963eb', 'loadClassLoader'), true, true);
23
  self::$loader = $loader = new \Composer\Autoload\ClassLoader();
24
+ spl_autoload_unregister(array('ComposerAutoloaderInit1e69adc4e12f3a0de649acee72d963eb', 'loadClassLoader'));
25
 
26
  $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
27
  if ($useStaticLoader) {
28
  require_once __DIR__ . '/autoload_static.php';
29
 
30
+ call_user_func(\Composer\Autoload\ComposerStaticInit1e69adc4e12f3a0de649acee72d963eb::getInitializer($loader));
31
  } else {
32
  $map = require __DIR__ . '/autoload_namespaces.php';
33
  foreach ($map as $namespace => $path) {
vendor/composer/autoload_static.php CHANGED
@@ -4,7 +4,7 @@
4
 
5
  namespace Composer\Autoload;
6
 
7
- class ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'S' =>
@@ -196,9 +196,9 @@ class ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a
196
  public static function getInitializer(ClassLoader $loader)
197
  {
198
  return \Closure::bind(function () use ($loader) {
199
- $loader->prefixLengthsPsr4 = ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a::$prefixLengthsPsr4;
200
- $loader->prefixDirsPsr4 = ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a::$prefixDirsPsr4;
201
- $loader->classMap = ComposerStaticInitc26b76c4bb2f02a9f28d9d7388d0189a::$classMap;
202
 
203
  }, null, ClassLoader::class);
204
  }
4
 
5
  namespace Composer\Autoload;
6
 
7
+ class ComposerStaticInit1e69adc4e12f3a0de649acee72d963eb
8
  {
9
  public static $prefixLengthsPsr4 = array (
10
  'S' =>
196
  public static function getInitializer(ClassLoader $loader)
197
  {
198
  return \Closure::bind(function () use ($loader) {
199
+ $loader->prefixLengthsPsr4 = ComposerStaticInit1e69adc4e12f3a0de649acee72d963eb::$prefixLengthsPsr4;
200
+ $loader->prefixDirsPsr4 = ComposerStaticInit1e69adc4e12f3a0de649acee72d963eb::$prefixDirsPsr4;
201
+ $loader->classMap = ComposerStaticInit1e69adc4e12f3a0de649acee72d963eb::$classMap;
202
 
203
  }, null, ClassLoader::class);
204
  }
vendor/jetpack-autoloader/autoload_functions.php CHANGED
@@ -5,7 +5,7 @@
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
- namespace Automattic\Jetpack\Autoloader\jp6dc849ca558881de20020de51053018a;
9
 
10
  // phpcs:ignore
11
 
5
  * @package automattic/jetpack-autoloader
6
  */
7
 
8
+ namespace Automattic\Jetpack\Autoloader\jp7e249d49c3c8e711c213fd730049b3ec;
9
 
10
  // phpcs:ignore
11
 
woocommerce.php CHANGED
@@ -3,7 +3,7 @@
3
  * Plugin Name: WooCommerce
4
  * Plugin URI: https://woocommerce.com/
5
  * Description: An eCommerce toolkit that helps you sell anything. Beautifully.
6
- * Version: 4.5.1
7
  * Author: Automattic
8
  * Author URI: https://woocommerce.com
9
  * Text Domain: woocommerce
3
  * Plugin Name: WooCommerce
4
  * Plugin URI: https://woocommerce.com/
5
  * Description: An eCommerce toolkit that helps you sell anything. Beautifully.
6
+ * Version: 4.5.2
7
  * Author: Automattic
8
  * Author URI: https://woocommerce.com
9
  * Text Domain: woocommerce