WooCommerce: автоматическое удаление товаров без заказов за заданный период

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

В крупных интернет-магазинах на WooCommerce часто накапливается большое количество товаров, которые никогда не были проданы. Такие позиции занимают место в базе данных, снижают производительность и усложняют управление каталогом. Задача — автоматизировать удаление товаров, у которых нет заказов за определённый период (например, 6 месяцев) без риска случайного удаления актуальных товаров.

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

Для точной работы нам нужно получить все ID товаров, для которых отсутствуют связанные заказы. В базе WooCommerce связь заказов и товаров хранится в таблицах wp_woocommerce_order_items и wp_woocommerce_order_itemmeta.

Пример SQL-запроса для диагностики товаров без заказов:

SELECT p.ID, p.post_title FROM wp_posts p
WHERE p.post_type = 'product'
AND p.post_status = 'publish'
AND NOT EXISTS (
  SELECT 1 FROM wp_woocommerce_order_items oi
  JOIN wp_woocommerce_order_itemmeta oim ON oi.order_item_id = oim.order_item_id
  JOIN wp_posts o ON o.ID = oi.order_id
  WHERE oim.meta_key = '_product_id'
  AND oim.meta_value = p.ID
  AND o.post_status IN ('wc-completed', 'wc-processing')
  AND o.post_date > DATE_SUB(NOW(), INTERVAL 6 MONTH)
)

Этот запрос вернёт все опубликованные товары, которые не были заказаны за последние 6 месяцев в статусах wc-completed или wc-processing.

Пошаговое решение: автоматизация удаления через WP-Cron

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

function wpengine_delete_unsold_products() {
    global $wpdb;
    $interval_months = 6;

    $product_ids = $wpdb->get_col($wpdb->prepare(
        "SELECT p.ID FROM {$wpdb->posts} p
         WHERE p.post_type = 'product'
         AND p.post_status = 'publish'
         AND NOT EXISTS (
            SELECT 1 FROM {$wpdb->prefix}woocommerce_order_items oi
            JOIN {$wpdb->prefix}woocommerce_order_itemmeta oim ON oi.order_item_id = oim.order_item_id
            JOIN {$wpdb->posts} o ON o.ID = oi.order_id
            WHERE oim.meta_key = '_product_id'
            AND oim.meta_value = p.ID
            AND o.post_status IN ('wc-completed', 'wc-processing')
            AND o.post_date > DATE_SUB(NOW(), INTERVAL %d MONTH)
         )",
         $interval_months
    ));

    if (empty($product_ids)) {
        return 'No products to delete';
    }

    foreach ($product_ids as $product_id) {
        wp_delete_post($product_id, true); // true для полного удаления
    }

    return count($product_ids) . ' products deleted';
}

2. Добавляем событие WP-Cron для еженедельного запуска

add_action('wpengine_weekly_cleanup', 'wpengine_delete_unsold_products');

function wpengine_setup_weekly_cron() {
    if (!wp_next_scheduled('wpengine_weekly_cleanup')) {
        wp_schedule_event(time(), 'weekly', 'wpengine_weekly_cleanup');
    }
}
add_action('wp', 'wpengine_setup_weekly_cron');

3. Ручной запуск и логирование для проверки

// Для тестирования запускаем функцию напрямую
add_action('admin_init', function() {
    if (current_user_can('manage_options') && isset($_GET['run_cleanup'])) {
        $result = wpengine_delete_unsold_products();
        echo '<div class="notice notice-success is-dismissible"><p>' . esc_html($result) . '</p></div>';
    }
});

Для запуска вручную добавьте параметр ?run_cleanup=1 к URL админки WordPress. Так можно проверить, что функция работает и удаляет именно нужные товары.

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

  • Запустите функцию вручную через URL с параметром ?run_cleanup=1 в панели администратора.
  • Проверьте, что удалились только товары без заказов за последние 6 месяцев.
  • Запустите SQL-запрос из раздела диагностики до и после запуска для сравнения.
  • Отследите запись в логах сервера или активируйте WP_DEBUG для выявления ошибок.

Частые ошибки и способы исправления

  • Удаляются нужные товары: Проверьте, что фильтр по статусам заказа и дате корректен. Возможно, товары имеют заказы в других статусах (например, wc-on-hold), их нужно добавить в запрос.
  • Функция не запускается по расписанию: Убедитесь, что WP-Cron работает (есть трафик на сайт). Запустите вручную wp_schedule_event и проверьте расписание через плагин WP Crontrol.
  • Ошибка прав доступа при удалении товаров: Функция должна выполнять удаление от имени администратора. Запускайте в контексте, где есть нужные права.
  • Длительное выполнение и нагрузка на базу: Для больших магазинов разбейте удаление на части — удаляйте по 50-100 товаров за раз с помощью array_chunk и планируйте повторные вызовы.

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

  • Резервное копирование: Перед внедрением обязательно сделайте бэкап базы данных и файлов.
  • Тестирование на staging-сервере: Проверяйте работу скрипта в безопасной среде, чтобы избежать потери данных.
  • Оптимизация запросов: Индексы на wp_woocommerce_order_itemmeta.meta_key и meta_value ускорят выборку.
  • Логирование: Добавьте запись в лог с количеством удалённых товаров для мониторинга.
  • Безопасность кода: Не запускайте удаление вне контекста авторизованного администратора.

Сравнение способов автоматизации удаления товаров без заказов

МетодПлюсыМинусыКомпромисс
Плагин (готовые решения)Быстрая настройка, удобный интерфейсМожет быть тяжёлым, лишние функции, риск конфликтовИспользовать проверенные и лёгкие плагины
Кастомный код с WP-CronКонтроль, эффективность, минимальное влияниеТребует навыков, нужно тестироватьПошаговое тестирование и логирование
Ручное удаление через SQLМаксимальная скоростьРиск ошибок, потеря данных без возвратаИспользовать только для диагностики, не для автоматизации
Как использовать Redis в WordPress на WPengine для улучшения кэширования
23.03.2026
Как создать автоматический импорт CSV файлов в WordPress с примерами кода
27.03.2026
Как разделить базу данных WordPress по таблицам для улучшения производительности
09.04.2026
Как использовать WPengine для автоматизации хранения вариантов постов в WordPress
18.02.2026
Оптимизация изображений в WordPress на WPengine: практические советы и примеры кода
01.03.2026