WooCommerce: как автоматически отключать товары без продаж через код

Диагностика проблемы: зачем отключать товары без продаж

В интернет-магазинах на WooCommerce иногда накапливаются товары, которые длительное время не продаются. Это негативно влияет на пользовательский опыт и SEO, а также усложняет управление каталогом. Автоматическое отключение таких товаров позволяет поддерживать актуальность ассортимента без ручного труда.

Как определить товары без продаж

WooCommerce хранит данные о продажах в таблицах базы данных, в частности через статус заказов и связь с товарами. Чтобы найти товары без продаж за определённый период, стоит ориентироваться на мета-данные заказов и дату публикации товаров.

Пример запроса для выявления товаров без продаж за 90 дней

SELECT p.ID FROM wp_posts p
LEFT JOIN wp_woocommerce_order_items oi ON p.ID = oi.order_item_id
LEFT JOIN wp_posts o ON oi.order_id = o.ID AND o.post_status IN ('wc-completed', 'wc-processing')
WHERE p.post_type = 'product' AND (o.ID IS NULL OR o.post_date < DATE_SUB(NOW(), INTERVAL 90 DAY))
AND p.post_date < DATE_SUB(NOW(), INTERVAL 90 DAY);

Однако для автоматизации лучше использовать WP-функции и хуки.

Пошаговое решение: автоматическое отключение товаров без продаж

1. Создаём функцию для проверки продаж товара

function has_sales_in_period( $product_id, $days = 90 ) {
    global $wpdb;
    $date_threshold = date('Y-m-d H:i:s', strtotime("-{$days} days"));
    $count = $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT( DISTINCT oi.order_item_id ) FROM {$wpdb->prefix}woocommerce_order_items oi
         INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim ON oi.order_item_id = oim.order_item_id
         INNER JOIN {$wpdb->prefix}posts o ON o.ID = oi.order_id
         WHERE o.post_status IN ('wc-completed', 'wc-processing')
         AND o.post_date > %s
         AND oim.meta_key = '_product_id' AND oim.meta_value = %d",
        $date_threshold, $product_id
    ) );
    return (int) $count > 0;
}

2. Функция для отключения товаров без продаж

function disable_products_without_sales() {
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => -1,
        'post_status' => 'publish',
        'date_query' => array(
            array(
                'column' => 'post_date',
                'before' => '90 days ago',
            ),
        ),
        'fields' => 'ids',
    );
    $products = get_posts($args);
    foreach ( $products as $product_id ) {
        if ( !has_sales_in_period( $product_id, 90 ) ) {
            // Отключаем товар
            wp_update_post(array(
                'ID' => $product_id,
                'post_status' => 'draft',
            ));
        }
    }
}

3. Автоматизация через WP-Cron

Добавьте задачу в расписание WordPress, чтобы запускать проверку регулярно:

if ( ! wp_next_scheduled( 'disable_products_without_sales_daily' ) ) {
    wp_schedule_event( time(), 'daily', 'disable_products_without_sales_daily' );
}
add_action( 'disable_products_without_sales_daily', 'disable_products_without_sales' );

Проверка результата после внедрения

  • Проверьте, что после запуска скрипта товары без продаж за последние 90 дней изменили статус на "Черновик".
  • Для теста можно снизить период с 90 дней до 1 дня и создать тестовый товар без продаж.
  • Используйте панель администратора WooCommerce — вкладка "Товары" — фильтр по статусу, чтобы убедиться в изменениях.

Частые ошибки и как их исправить

  • Неверный период проверки: если товары не отключаются, проверьте дату публикации товара и период проверки продаж.
  • Отсутствие заказов с нужным статусом: функция учитывает только заказы со статусом wc-completed и wc-processing. Если у вас другие статусы, добавьте их.
  • Проблемы с WP-Cron: при отсутствии реального посещения сайта задачи могут не запускаться. Настройте системный cron для wp-cron.php или запускайте вручную.
  • Права пользователя: убедитесь, что скрипт выполняется с правами, позволяющими изменять статус товаров.

Практические советы по производительности и безопасности

  • Для магазинов с большим каталогом товаров разбивайте обновление на партии, чтобы избежать превышения лимита памяти или времени выполнения:
function disable_products_batch( $batch_size = 50 ) {
    static $offset = 0;
    $args = array(
        'post_type' => 'product',
        'posts_per_page' => $batch_size,
        'post_status' => 'publish',
        'date_query' => array(
            array(
                'column' => 'post_date',
                'before' => '90 days ago',
            ),
        ),
        'fields' => 'ids',
        'offset' => $offset,
    );
    $products = get_posts($args);
    foreach ( $products as $product_id ) {
        if ( !has_sales_in_period( $product_id, 90 ) ) {
            wp_update_post(array(
                'ID' => $product_id,
                'post_status' => 'draft',
            ));
        }
    }
    $offset += $batch_size;
    if ( count($products) === $batch_size ) {
        wp_schedule_single_event( time() + 10, 'disable_products_batch_hook' );
    } else {
        $offset = 0; // Сброс после окончания
    }
}
add_action( 'disable_products_batch_hook', 'disable_products_batch' );
  • Для повышения безопасности избегайте запуска функций напрямую в шаблонах или без проверки nonce, используйте WP-Cron.
  • Резервное копирование базы данных перед автоматическим изменением статусов — обязательный шаг.

Сравнение подходов отключения товаров без продаж

МетодПлюсыМинусы
Ручное отключение через панельПросто, без кодаТрудозатратно при большом каталоге
SQL-запросы для массового обновленияБыстрое обновлениеРиск ошибок, требует доступа к БД
Автоматизация через WP-Cron + PHPПолная автоматизация, безопасноНужно писать и тестировать код
Как удалить удалённый плагин WordPress без доступа к phpMyAdmin
10.11.2025
Как автоматизировать удаление старых записей в WordPress через код
09.03.2026
WooCommerce: диагностика и решение проблем с обновлением стоимости товаров
05.05.2026
Как исправить проблемы с изменением slug в WooCommerce после редактирования товара
17.04.2026
Как удалить старые версии записей в WordPress через код
21.02.2026