Waarom on_sale en include in WooCommerce niet samengingen
WooCommerce heeft meerdere REST-endpoints die producten opsommen, zoals /wc/v3/products en de Store API op /wc/store/v1/products die de productblokken gebruiken. Ze accepteren on_sale om alleen producten in de aanbieding te tonen of juist alleen producten die dat niet zijn, en include en exclude om het resultaat te beperken tot bepaalde product-ID's. Samen gaven die filters de verkeerde producten terug. Ik loste het op in woocommerce/woocommerce#69432.
Wat er misging
Met producten 10 tot en met 12 niet in de aanbieding en 13 tot en met 18 wel, gaf on_sale=true&include=10,13 de producten 10, 13, 15, 16, 17 en 18 terug. Product 10 is niet in de aanbieding, en 15 tot en met 18 waren nooit opgenomen. on_sale=false&include=10,13 gaf beide producten terug, en on_sale=true&exclude=13 gaf 13 nog steeds terug.
Oorzaak één, array-unie
include wordt post__in en exclude wordt post__not_in. Het on_sale-filter voegde daarna de ID's van alle producten in de aanbieding toe.
$on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
$on_sale_ids = wc_get_product_ids_on_sale();
$args[ $on_sale_key ] += $on_sale_ids;In PHP is += op arrays een unie op sleutel. Beide arrays zijn lijsten met sleutels 0, 1, 2 enzovoort, dus de ID's op sleutels die al bestonden vielen weg en de rest werd toegevoegd. Bij include=10,13 bleven 10 en 13 staan en kwamen de aanbiedingsproducten vanaf sleutel 2 erbij.
Oorzaak twee, WP_Query
Alleen de += oplossen was niet genoeg, dat vond de reviewer tijdens het testen. WP_Query gebruikt post__not_in alleen als post__in leeg is.
if ( $query_vars['post__in'] ) {
// WHERE ID IN (...)
} elseif ( $query_vars['post__not_in'] ) {
// WHERE ID NOT IN (...)
}De aanbiedings-ID's toevoegen aan post__not_in deed dus niets zodra include gezet was, en door on_sale=true werkte exclude niet meer.
De oplossing
Als on_sale true is of include gezet is, berekent het filter nu zelf de uiteindelijke lijst ID's en zet die in post__in. Zonder include voegt on_sale=false de aanbiedings-ID's nog steeds toe aan post__not_in.
if ( $request['on_sale'] || ! empty( $args['post__in'] ) ) {
$post_in = empty( $args['post__in'] ) ? $on_sale_ids : $args['post__in'];
if ( $request['on_sale'] ) {
$post_in = array_intersect( $post_in, $on_sale_ids );
} else {
$post_in = array_diff( $post_in, $on_sale_ids );
}
$post_in = array_diff( $post_in, (array) $args['post__not_in'] );
$args['post__in'] = empty( $post_in ) ? array( 0 ) : array_values( $post_in );
} else {
$args['post__not_in'] = array_merge( (array) $args['post__not_in'], $on_sale_ids );
}Dezelfde code stond op vijf plekken, de product-endpoints v2, v3 en v4, het variaties-endpoint en de Store API, dus alle vijf gebruiken dit nu. Tests dekken on_sale met include en met exclude, en de reviewer voegde er voor de merge nog een paar toe. De fix staat gepland voor WooCommerce 11.3.
Wat je ervan meeneemt
Gebruik += op arrays alleen als je bestaande sleutels wilt houden. Voor lijsten met ID's gebruik je array_merge, array_intersect of array_diff. En bouw je argumenten voor WP_Query, reken dan niet op post__not_in zolang post__in gezet is.