-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Fix filter by attribute widget now working for "Any..." attributes #27508
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
Conversation
When a variation product has an attribute with a value of "Any...", and there's a filter by attribute widget for that attribute, then that product won't be included in the counts displayed in the widget (and if the count ends up being zero, the attribute won't be shown in the widget). This happens before since Woo 4.4, this widget works by looking at entries in the term relationships table for varitions too (used to do so for simple products and for "main" variable products only), see PR #26260; but there are no such entries for "Any..." attributes. This commit fixes that by extending the SQL query used by the widget to look for variations that have empty attribute values in the meta table too.
…r removed. When a new variation attribute is added, the corresponding 'attribute_' meta entries are added for all variations with an empty value; and when an existing variation attribute is removed, the existing 'attribute_' meta entries are deleted for all variations. This is necessary for the filter by attribute widget to work properly when variations exist with a value of "Any..." for attributes.
The previous commit fixes a bug that causes the "attribute_" metadata with an empty value to not be created when a new variation attribute is added to the product (so that all variations have the attribute with a value of "Any..."). This commit adds a data migration to backfill the missing metadata for existing variations.
|
@Konamiman You've got a unit test failure in PHP 7.4. |
|
@Konamiman In the screenshot below the |
|
Hi @rrennick. After you add a new term you need to manually add that new term to the list of values used for variations here: "Any..." value in the context of variations means "any of the values that are used for variations in this product", not "any of all the existing values for the attribute", if I understood correctly how that works. If what you mean is that adding a new term should automatically add the term to the list of attributes used for variations on all variable products using that attribute: maybe. But I think that's a separate discussion, not really related to this PR as that's the way it worked before. |
Right now the filter by attributes widget counts available variations (for variation products). This is confusing since the counter shows numbers that are higher than the actual count of products displayed. This commit changes the query used by the widget so that instead of counting variations it returns the parent product ids, and then counts the distinct values. This also covers the case of products where some of the variations have concrete values and some have "Any..." values.
When set_attributes is used on WC_Product to remove existing attributes the wc_product_attribute_uasort_comparison ends up being called with a null argument, and this breaks tests in PHP 7.4 since null is used as an array. This commit modifies the function so that if null is passed no array access is attempted.
vedanshujain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am still reviewing and testing the PR, but adding some initial comments that will be helpful.
| // This one will return non-variable products and variable products with concrete values for the attributes. | ||
| $query = array(); | ||
| $query['select'] = "SELECT COUNT( DISTINCT {$wpdb->posts}.ID ) as term_count, terms.term_id as term_count_id"; | ||
| $query['select'] = "SELECT {$wpdb->posts}.post_parent as product_id, terms.term_id as term_count_id"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are we sure about this change? post_parent is set to 0 for normal products, i think this is applicable only for variations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this @Konamiman, I've left some comments I think we should address.
Regarding the PR itself however I don't think this is the route that we should take. I'm worried that automatically synchronizing the attributes to variations is a little too destructive and might be considered backwards incompatible. Right now (without this PR), when you remove an attribute from a variable product, none of the terms are removed from the variations. This allows you to add back the attribute without needing to restore a backup.
It also appears that the behavior of the widget isn’t exactly the same as without the PR when things are handled correctly, but that may just be a misunderstanding on my part.
From what I can tell it seems like mostly everything other than this widget is resilient to the missing entries and so perhaps we should just correct the count query and leave the rest as it is for now?
|
|
||
| if ( $this->get_id() ) { | ||
| $previous_product = wc_get_product( $this ); | ||
| $previous_variation_attributes = array_keys( $this->data_store->read_variation_attributes( $previous_product ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the WC_Data class maintains a dirty list in the $data array, could we just pull the current attributes from there instead of loading them like this?
| $this->data_store->sync_managed_variation_stock_status( $this ); | ||
|
|
||
| // Get variation attributes after save. | ||
| $current_attributes = array_filter( array_values( $this->get_attributes() ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a small nitpick but given how we're iterating over $current_attributes how would you feel about performing these transformations there instead?
| * @param array $current_variation_attributes Names of the variation attributes the product has now. | ||
| */ | ||
| public function sync_meta_for_variation_attributes( $product, $previous_variation_attributes, $current_variation_attributes ) { | ||
| $added_attributes = array_diff( $current_variation_attributes, $previous_variation_attributes ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another small nitpick but what about just iterating over the input arrays and checking for existence in the other?
| * @param array $previous_variation_attributes Names of the variation attributes the product had before saving. | ||
| * @param array $current_variation_attributes Names of the variation attributes the product has now. | ||
| */ | ||
| public function sync_meta_for_variation_attributes( $product, $previous_variation_attributes, $current_variation_attributes ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at WC_Product_Variation_Data_Store_CPT::update_attributes it looks like the attributes are located in other places too. Do you think we should replicate some of that behavior as well?
| * Layered nav widget | ||
| * | ||
| * @package WooCommerce\Widgets | ||
| * @package WooCommerce/Widgets |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to leave this as a backslash. If some style rule is telling us to change it then we should address that!
…ring widget The new query doesn't need empty attribute entries in the meta table, therefore the code that generates them and the migration to backfill the missing existing ones have been removed.
ObliviousHarmony
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested this out with various product types and it seems to do what it says on the tin!
One nitpick is that we're now counting variable products using the post_parent instead of their own ID. This behavior is different than what it was doing before as it was previously considering each in-stock variation in the count. This might lead to strange behavior when combining multiple widgets and I think we should use the ID for variations as well.
Originally it did count variations, but as @rrennick pointed out, that resulted in the widget displaying a number that was higher than the actual amount of products being displayed, which was quite confusing. So it now looks at which variations are supposed to be visible, keeps its parent id, and counts how many different parent ids are in the result; this also blends nicely with the counts for simple products. |
vedanshujain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have left a comment about splitting the query, I found out a couple more issues but let's tackle them in different PR:
- Looks like we have introduced performance regressions in
get_available_variationsand then calling it inis_visible_corefor every product. This makes the whole page slow if there are many products with a large number of variations. - I am not sure if using
IFin select statement and selectingpost_parentis the best way because we are applying the rest of the checks to post.ID. to explain, if a variable product is marked out of stock, then the variations are still counted and shown if the particular variations are not marked out of stock. This is because the checking is done on the post.ID but we are bubbling up post.post_parent in the SELECT statement.
Let's deal with these two changes later on, but I think we should still split this query into two and run them separately.
| $query_sql = " | ||
| SELECT COUNT(DISTINCT(product_id)) AS term_count, term_count_id FROM ( | ||
| {$main_query_sql} | ||
| UNION ALL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not unionize this as mentioned in https://github.com/woocommerce/woocommerce/pull/27508/files#r480269210 and use two separate queries. There are multiple reasons to not go this way:
- Performance and locking implications as mentioned in the previous comment.
- Easier to kill performance if an extension or theme adds a
LIMITorORDER BYin any of the union subquery.
Please then cache these queries separately.
This is the point of the whole thing. Take in account only variations that are in stock, but in the end, get the main product id so that counters are consistent with the list of products shown (which are always the main products). E.g. assuming one single product, if two variations are in stock for the same attribute, say "blue-big" and "blue-small", then count only one for "blue". Otherwise we would be displaying a count of 2 but there would be only one product in the displayed list, which would be confusing. Or am I misunderstanding you? |
So this becomes inconsistent with the main query when the parent product is out of stock but variations are in stock. Then we will count it in the widget, but main query won't as the product is tagged out of stock. That said, I mentioned that we will deal with this later because currently the only way to repro this scenario is to convert the product into simple, then make it out of stock, and then convert it back to the variable. This is definitely an edge case, let's not block the whole PR for this. (You can also manually add |
For performance reasons the query is split in two: one for simple products and variations with a concrete attribute value, and another one for variations having "Any..." as the attribute value.
vedanshujain
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have left a comment, but pre-approving since its minor. Note that this whole widget needs perf improvements, but I think we can merge it for now.
| {$query} | ||
| ) AS x GROUP BY term_count_id"; | ||
|
|
||
| $results = $wpdb->get_results( $query, ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets cache here instead of in callee function, so that both the queries can be cached separately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need to cache the queries separately? We have split the original query in two for performance reasons, but conceptually they are still a single one; they will always be used together and with the same set of parameters.
|
Thanks for the reviews and work to get this in to 4.5 |
This commit reverts the functionality introduced in PR #26260 (later refined by #27175, #27190, #27508) in which filtering by attribute using the layered nav widget was improved to handle the cases of variations out of stock. The revert is a response to the numerous problems reported by users in Woo 4.4 and 4.5 Not all the code has been reverted, only the code that resulted in visible functionality changes. Thus, the code that generates term relationships for variations is still in place to keep database consistency and to keep the reverting changes to the minimum needed.
This commit reverts the functionality introduced in PR #26260 (later refined by #27175, #27190, #27508) in which filtering by attribute using the layered nav widget was improved to handle the cases of variations out of stock. The revert is a response to the numerous problems reported by users in Woo 4.4 and 4.5 Not all the code has been reverted, only the code that resulted in visible functionality changes. Thus, the code that generates term relationships for variations is still in place to keep database consistency and to keep the reverting changes to the minimum needed.
This commit reverts the functionality introduced in PR #26260 (later refined by #27175, #27190, #27508) in which filtering by attribute using the layered nav widget was improved to handle the cases of variations out of stock. The revert is a response to the numerous problems reported by users in Woo 4.4 and 4.5 Not all the code has been reverted, only the code that resulted in visible functionality changes. Thus, the code that generates term relationships for variations is still in place to keep database consistency and to keep the reverting changes to the minimum needed.
|
This is still not working on our side. The filter doesn't show up on the page. |
|
Thanks for the report @Gasper90. This PR hasn't been included in a release yet. We're planning on pushing a release with this fix early next week. Are you using a WooCommerce release version? If so, can I ask you to add your report to #27419 so that we can be sure that we're tracking it appropriately? |
|
Ok, because I saw this in the changelog:
|
|
It works now! 👍 |





All Submissions:
Changes proposed in this Pull Request:
This pull requests fixes two bugs related to attribute filtering for variable products:
When a variation has a value of "Any..." for a given attribute, then that variation isn't counted by the "Filter by attribute" widget corresponding to that attribute.
attribute_metadata entries with empty values not handled correctly when adding or removing variation attributesThis is an old bug (present in pre-4.4 versions). When a new variation attribute is added to the main product, then all the already existing variations will display the attribute with a value of "Any...", but the corresponding
attribute_metadata entry for the variation won't be created. (If an existing variation is manually set to "Any..." and saved then it works as expected and the entry is created)Conversely, when removing the attribute from the main product the corresponding
attribute_metadata entries for the variations won't be removed.A data migration is added too to backfill possible missing values for these metadata entries.
Closes #27419.
How to test the changes in this Pull Request:
Start with an empty shop (no products).
Other information:
Changelog entry