Когда в каталоге много позиций, которых сейчас нет на складе, они неизбежно перемешиваются с доступными: сортировка по названию или цене ничего не знает о наличии. Покупатель листает первый экран, видит серые плашки «Нет в наличии» и уходит с ощущением, что магазин не работает. Решений два: либо скрыть отсутствующие товары совсем, либо оставить их в каталоге, но опустить в конец списка. Второй вариант обычно выгоднее — страницы товаров продолжают собирать трафик из поиска, а покупатель может оформить предзаказ или подписаться на уведомление о поступлении.
Без кода: просто скрыть отсутствующие
Если товар, которого нет, не вернётся в продажу, его проще убрать из каталога штатной настройкой: WooCommerce → Настройки → Товары → Запасы → «Скрывать товары не в наличии из каталога». Страницы самих товаров при этом остаются доступны по прямой ссылке, но из списков и поиска по магазину исчезают. Для сезонного ассортимента или позиций, которые регулярно закупаются заново, такой вариант не подходит — их лучше показывать, но в конце. Дальше речь именно об этом.
Как это работает
Статус наличия WooCommerce хранит в мета-поле _stock_status с тремя значениями: instock (в наличии), onbackorder (под заказ) и outofstock (нет в наличии). Начиная с версии 3.6 эти же данные дублируются в служебную таблицу wc_product_meta_lookup — она создана специально для быстрой сортировки и фильтрации и, в отличие от postmeta, имеет индекс по колонке stock_status.
Основной запрос каталога — это обычный WP_Query, а значит, его SQL можно доработать фильтром posts_clauses: подключить таблицу lookup через JOIN и поставить сортировку по наличию перед той, что уже есть в запросе. Тогда выбранная покупателем сортировка (по цене, по названию, по популярности) не пропадает, а применяется внутри каждой группы: сначала товары в наличии по цене, затем под заказ по цене, затем отсутствующие по цене.
Код для functions.php дочерней темы
add_filter( 'posts_clauses', 'shop_order_by_stock_status', 2000, 2 );
function shop_order_by_stock_status( $clauses, $query ) {
global $wpdb;
// Только фронтенд и только основной запрос страницы
if ( is_admin() || ! $query->is_main_query() ) {
return $clauses;
}
// Только списки каталога: магазин, категория, метка
if ( ! ( is_shop() || is_product_category() || is_product_tag() ) ) {
return $clauses;
}
$lookup = $wpdb->prefix . 'wc_product_meta_lookup';
$clauses['join'] .= " INNER JOIN {$lookup} AS stock_lookup ON {$wpdb->posts}.ID = stock_lookup.product_id ";
// Явный порядок групп: в наличии → под заказ → нет в наличии
$stock_order = " FIELD( stock_lookup.stock_status, 'instock', 'onbackorder', 'outofstock' ) ";
$clauses['orderby'] = $stock_order . ( $clauses['orderby'] ? ', ' . $clauses['orderby'] : '' );
return $clauses;
}
Что здесь важно. Проверка is_main_query() ограничивает фильтр основным запросом страницы: похожие товары, виджеты и шорткоды делают собственные запросы, и трогать их не нужно. Приоритет 2000 ставит нашу функцию после собственных обработчиков WooCommerce, которые через тот же posts_clauses собирают сортировку по цене и популярности — иначе они переписали бы наш ORDER BY. Функция FIELD() задаёт порядок групп явно; полагаться на алфавитный порядок значений было бы хрупко. И, наконец, условие с пустым orderby: в обычном каталоге он всегда заполнен, но лишняя запятая в SQL стоила бы белого экрана.
Почему таблица lookup, а не postmeta
Присоединить можно и postmeta по ключу _stock_status — результат будет тот же. Но postmeta в магазине с тысячами товаров — самая тяжёлая таблица, а колонка meta_value не индексирована. Таблица wc_product_meta_lookup хранит по одной строке на товар, индексирована по нужным колонкам и обновляется самим WooCommerce при каждом сохранении товара и изменении остатков. Если после переноса магазина или ручных правок в базе кажется, что данные в ней устарели, её можно перестроить: WooCommerce → Статус → Инструменты, пункт про обновление таблиц поиска (lookup tables).
Вариативные товары
У товара с вариациями статус родительской записи вычисляется автоматически: он «в наличии», пока в наличии хотя бы одна вариация. Поэтому футболка, у которой закончился самый ходовой размер, но остались другие, по-прежнему попадёт в первую группу — и это правильно, ведь купить её можно. В конец уйдут только те вариативные товары, у которых распроданы все вариации.
Шорткоды и блочные темы
Шорткод [products] и блок «Коллекция товаров» (Product Collection) выполняют собственные запросы, поэтому проверка is_main_query() их отсекает. Для подборок на главной или в статьях это, как правило, и нужно: там порядок задаётся вручную. Если же у вас блочная тема и сам каталог выводится блоком «Коллекция товаров», у его запроса в актуальных версиях WooCommerce есть служебный признак isProductCollectionBlock. Расширьте условие так, чтобы фильтр срабатывал и для него:
if ( is_admin() || ! ( $query->is_main_query() || $query->get( 'isProductCollectionBlock' ) ) ) {
return $clauses;
}
Остальной код не меняется. После правки проверьте, что порядок изменился именно на странице каталога, а подборки в других местах сайта остались как были.
Что проверить после установки
- Выпадающий список сортировки в каталоге по-прежнему работает — но внутри групп по наличию.
- Количество товаров и число страниц пагинации не изменилось. Если товаров стало меньше, у части позиций нет строки в таблице lookup — перестройте её инструментом из раздела «Статус».
- Кеш страниц (плагин кеширования или CDN) сброшен, иначе старый порядок будет виден ещё какое-то время.
Коротко
Чтобы товары в наличии шли первыми, достаточно одного фильтра posts_clauses: присоединяем wc_product_meta_lookup, ставим FIELD() по статусу впереди существующей сортировки и ограничиваем всё основным запросом каталога. Покупатель по-прежнему сортирует по цене или названию, но отсутствующие товары больше не мешают на первом экране.
Частые вопросы
Да, если отсутствующие товары не нужно показывать вовсе: WooCommerce → Настройки → Товары → Запасы → «Скрывать товары не в наличии из каталога». Если их нужно оставить в каталоге, но опустить в конец, без фильтра не обойтись.
Сортировка по meta_value заменяет собой ту, что выбрал покупатель: пропадает порядок по цене или названию. Фильтр posts_clauses добавляет сортировку по наличию перед существующей, и она продолжает работать внутри групп.
Да. WooCommerce собирает эти сортировки тем же фильтром posts_clauses, поэтому наш обработчик подключён с приоритетом 2000 — после них. Товары делятся на группы по наличию, а внутри групп сортируются по цене или популярности.
INNER JOIN с таблицей wc_product_meta_lookup отбрасывает товары, у которых нет строки в ней. Такое бывает после переноса базы или прямых правок в ней. Перестройте таблицы поиска в WooCommerce → Статус → Инструменты.
Источники
- 1.posts_clauses — WordPress Developer Referencehttps://developer.wordpress.org/reference/hooks/posts_clauses/
- 2.Performance improvements in WooCommerce 3.6 — таблица wc_product_meta_lookuphttps://developer.woocommerce.com/2019/04/01/performance-improvements-in-3-6/
- 3.Managing products — Inventory (документация WooCommerce)https://woocommerce.com/document/managing-products/



