Slutsålda produkter ska inte försvinna, men de ska inte heller ligga överst när kunden landar i en kategori. Jag löser det med ett filter på posts_clauses som sorterar på lagerstatus först och därefter på det kunden själv valt. Filtret gäller butikssidan, kategorier och sökresultat, kortkoden [products] och Product Collection-blocket i blockteman. Det fungerar också i teman som saknar WooCommerce-stöd.
Koden ligger i ett eget litet plugin eller i child-temats functions.php. Den kräver ingen inställning.
<?php
/**
* Plugin Name: WPU Out of Stock Last
* Description: Sorts out-of-stock products last in shop, category, search, [products] shortcode and Product Collection queries.
* Requires Plugins: woocommerce
*/
defined( 'ABSPATH' ) || exit;
// Themes without WooCommerce support, and the [products] shortcode, list products through
// WC_Shortcode_Products. Its query has no wc_query flag, so we add our own.
add_filter( 'woocommerce_shortcode_products_query', 'wpu_out_of_stock_last_flag_shortcode' );
function wpu_out_of_stock_last_flag_shortcode( $query_args ) {
// Keep a hand-picked order (ids="…" with orderby="post__in").
if ( 'post__in' !== ( $query_args['orderby'] ?? '' ) ) {
$query_args['wpu_stock_last'] = true;
}
return $query_args;
}
// Priority 20 so it runs after WooCommerce's own orderby clauses (price, popularity) and keeps them as secondary sort.
add_filter( 'posts_clauses', 'wpu_out_of_stock_last', 20, 2 );
function wpu_out_of_stock_last( $clauses, $query ) {
global $wpdb;
if ( is_admin() ) {
return $clauses;
}
// WooCommerce marks its catalog main query with wc_query, and the Product Collection block with isProductCollection.
$is_catalog = 'product_query' === $query->get( 'wc_query' );
$is_collection = (bool) $query->get( 'isProductCollection' );
$is_shortcode = (bool) $query->get( 'wpu_stock_last' );
if ( ! $is_catalog && ! $is_collection && ! $is_shortcode ) {
return $clauses;
}
// The lookup table is kept in sync by WooCommerce and is far cheaper to join than postmeta.
$lookup = $wpdb->prefix . 'wc_product_meta_lookup';
if ( false === strpos( $clauses['join'], " {$lookup} wpu_stock " ) ) {
$clauses['join'] .= " LEFT JOIN {$lookup} wpu_stock ON {$wpdb->posts}.ID = wpu_stock.product_id ";
}
$stock_first = "( wpu_stock.stock_status = 'outofstock' ) ASC";
$clauses['orderby'] = '' === trim( (string) $clauses['orderby'] )
? $stock_first
: $stock_first . ', ' . $clauses['orderby'];
return $clauses;
}
Varför uppslagstabellen och inte meta_query
Det vanliga rådet är en meta_query på _stock_status. Det fungerar, men varje produkt får då en extra koppling mot wp_postmeta, en tabell som i en butik med några tusen produkter har hundratusentals rader. WooCommerce håller sedan länge en egen tabell, wc_product_meta_lookup, med en rad per produkt och kolumnen stock_status. Den är gjord för exakt den här typen av sortering och filtrering, och WooCommerce använder den själv för prissortering.
Filtret känner igen tre sorters frågor. Den klassiska butiken, kategorisidorna och produktsökningen kör genom WooCommerce huvudfråga, som är märkt med wc_query. Product Collection-blocket i blockteman bygger egna frågor och märker dem med isProductCollection. Kortkoden [products] bygger också en egen fråga, utan någon märkning. Därför sätter pluginet en egen flagga, wpu_stock_last, via filtret woocommerce_shortcode_products_query. Alla tre får samma sortering.
Kortkoden är viktigare än den ser ut. Saknar temat stöd för WooCommerce (add_theme_support( 'woocommerce' )) visar WooCommerce butikssidan och kategorierna genom just kortkoden, i stället för huvudfrågan. Utan den tredje flaggan skulle sorteringen då inte göra något alls i många äldre och egenbyggda teman. Har du skrivit [products ids="…" orderby="post__in"] för att välja ordningen själv rör pluginet inte den listan. Det äldre blocket All Products hämtar produkter via Store API i webbläsaren och påverkas inte.
Lagerstatusen kommer först i ORDER BY, och sedan följer det WooCommerce redan har bestämt: kundens val i sorteringsmenyn eller butikens standardsortering. Produkter i lager sorteras alltså fortfarande på pris eller namn inom sin grupp, och de slutsålda hamnar sist i samma ordning.
Variabla produkter
En variabel produkt har egen lagerstatus i uppslagstabellen. WooCommerce sätter den till slutsåld först när ingen variant går att köpa, och uppdaterar den när en variant sparas. Är en av fem storlekar slut ligger produkten alltså kvar bland de köpbara, vilket är rätt beteende i de flesta butiker. Vill du visa den sist redan när huvudstorlekarna är slut behöver du egen logik, och det är ett annat inlägg.
Fallgropar
Sidcache är den vanligaste orsaken till att en slutsåld produkt ändå ligger överst. Lagerstatusen ändras i databasen när ordern går igenom, men kategorisidan ligger kvar i cachen tills den rensas. De flesta cacheplugin har en regel för att rensa produktsidor vid lageruppdatering. Kontrollera att kategorisidorna ingår i den regeln.
Uppslagstabellen kan hamna ur fas efter en import eller en direkt databasändring. Då visar butiken fel ordning fast lagersaldot stämmer. Regenerera tabellen under WooCommerce, Status, Verktyg, Produktuppslagstabeller.
Filtret gäller inte i admin och inte för andra frågor än WooCommerce egna. Bygger temat sin egen WP_Query för produkter behöver den sätta wpu_stock_last till true i frågans argument, eller så lägger du till temats egna villkor i filtret.
När du ska välja något annat
Har butiken bara några dussin produkter och sällan slut i lager räcker inställningen under WooCommerce, Inställningar, Produkter, Lager: ”Dölj slutsålda produkter i katalogen”. Den tar bort produkterna helt, vilket är enklare men också gör att produktsidorna försvinner ur butiken och ur Google. Säljer du säsongsvaror som kommer tillbaka är det ett dåligt byte. Då är sortering det rätta.
Vanliga frågor
Fungerar det i Product Collection-blocket?
Ja. Blocket markerar sina frågor med isProductCollection, och filtret känner igen det. Det äldre blocket All Products hämtar produkter via Store API och påverkas inte.
Fungerar det om temat saknar WooCommerce-stöd?
Ja, från version 1.1.0. WooCommerce visar då butiken och kategorierna via kortkoden [products], och pluginet sorterar den frågan också.
Varför inte bara dölja slutsålda produkter?
Inställningen under WooCommerce, Inställningar, Produkter, Lager döljer dem helt. Då försvinner produktsidorna ur butiken och ur sökmotorerna. Vill du behålla sidorna men visa det som går att köpa först är sortering rätt väg.
Ändrar filtret kundens valda sortering?
Nej. Slutsålt sist läggs före den sortering kunden valt, så inom varje grupp gäller fortfarande pris, namn eller popularitet.
Varför visas en produkt ändå överst fast den är slut?
Oftast en cachad sida. Rensa sidcachen efter att lagerstatusen ändrats. Fungerar det ändå inte, regenerera produktuppslagstabellerna under WooCommerce, Status, Verktyg.
Källor
- WC_Query::product_query() sätter wc_query på huvudfrågan (class-wc-query.php)
- Product Collection-blockets QueryBuilder markerar sina frågor med isProductCollection
- Tabellen wc_product_meta_lookup och kolumnen stock_status (class-wc-product-data-store-cpt.php)
- posts_clauses, WordPress Developer Resources
- Inställningen för att dölja slutsålda produkter, WooCommerce-dokumentationen
