class WC_Deduplicator {
// 🔍 Уніфікований лог і вивід кількості
public static function log_and_display_count($label, $count, $type = 'info') {
$timestamp = current_time('mysql');
self::log_action(["[$timestamp] 🔍 $label: $count"]);
$class = $type === 'error' ? 'notice-error' : ($type === 'warning' ? 'notice-warning' : 'notice-info');
echo "
🔍 $label: $count
";
}
// 🔍 Пошук дублікатів SKU
public static function get_duplicate_skus() {
global $wpdb;
return $wpdb->get_results("
SELECT meta_value AS sku,
GROUP_CONCAT(DISTINCT post_id ORDER BY post_id SEPARATOR ',') AS product_ids
FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_sku'
AND meta_value != ''
AND post_id IN (
SELECT ID FROM {$wpdb->prefix}posts
WHERE post_type = 'product' AND post_status IN ('publish', 'draft')
)
GROUP BY meta_value
HAVING COUNT(*) > 1
");
}
// 🔍 Пошук дублікатів назв
public static function get_duplicate_titles() {
global $wpdb;
return $wpdb->get_results("
SELECT post_title,
GROUP_CONCAT(ID ORDER BY ID SEPARATOR ',') AS product_ids
FROM {$wpdb->prefix}posts
WHERE post_type = 'product'
AND post_status IN ('publish', 'draft')
AND post_title != ''
GROUP BY post_title
HAVING COUNT(*) > 1
");
}
// ⚡ Видалення товарів
public static function delete_fast($ids) {
global $wpdb;
if (empty($ids)) return;
$safe_ids = implode(',', array_map('intval', $ids));
$wpdb->query("DELETE FROM {$wpdb->prefix}posts WHERE ID IN ($safe_ids)");
$wpdb->query("DELETE FROM {$wpdb->prefix}postmeta WHERE post_id IN ($safe_ids)");
$timestamp = current_time('mysql');
self::log_action(["[$timestamp] SQL-видалення: " . count($ids) . " товарів", "ID: $safe_ids"]);
}
// ⚡ Обробка швидких запитів
public static function handle_fast_deletions() {
global $wpdb;
if (isset($_POST['delete_no_image_fast'])) {
$ids = $wpdb->get_col("
SELECT p.ID FROM {$wpdb->prefix}posts p
LEFT JOIN {$wpdb->prefix}postmeta m ON p.ID = m.post_id AND m.meta_key = '_thumbnail_id'
WHERE p.post_type = 'product' AND p.post_status IN ('publish','draft') AND m.post_id IS NULL
");
self::log_and_display_count('Товарів без зображення для видалення', count($ids));
if (!empty($ids)) {
self::delete_fast($ids);
echo "
⚡ Видалено " . count($ids) . " товарів без зображення.
";
}
}
if (isset($_POST['delete_outofstock_fast'])) {
$ids = $wpdb->get_col("
SELECT p.ID FROM {$wpdb->prefix}posts p
INNER JOIN {$wpdb->prefix}postmeta m ON p.ID = m.post_id
WHERE p.post_type = 'product' AND p.post_status IN ('publish','draft')
AND m.meta_key = '_stock_status' AND m.meta_value = 'outofstock'
");
self::log_and_display_count('Товарів без наявності для видалення', count($ids));
if (!empty($ids)) {
self::delete_fast($ids);
echo "
⚡ Видалено " . count($ids) . " товарів без наявності.
";
}
}
}
// ⚡ Видалення товарів без артикулу
public static function handle_missing_sku_fast_deletion() {
global $wpdb;
if (!isset($_POST['delete_missing_sku_fast'])) return;
$whitelist = ['100037', 'ABC123', 'SKU-EXCLUDE'];
$escaped = implode("','", array_map([$wpdb, 'esc_like'], $whitelist));
$ids = $wpdb->get_col("
SELECT DISTINCT p.ID
FROM {$wpdb->prefix}posts p
LEFT JOIN {$wpdb->prefix}postmeta m ON p.ID = m.post_id AND m.meta_key = '_sku'
WHERE p.post_type = 'product'
AND p.post_status IN ('publish','draft')
AND (m.meta_value IS NULL OR m.meta_value = '')
AND p.ID NOT IN (
SELECT post_id FROM {$wpdb->prefix}postmeta
WHERE meta_key = '_sku' AND meta_value IN ('$escaped')
)
");
self::log_and_display_count('Товарів без артикулу (крім whitelist)', count($ids));
if (!empty($ids)) {
self::delete_fast($ids);
echo "
⚡ Видалено " . count($ids) . " товарів без артикулу (крім whitelist).
";
} else {
echo "
🚫 Не знайдено товарів для видалення.
";
}
}
// 📉 Видалення товарів з найменшою ціною
public static function handle_low_price_cleanup() {
if (!isset($_POST['delete_low_price_duplicates'])) return;
$sku_groups = self::get_duplicate_skus();
$title_groups = self::get_duplicate_titles();
$deleted = [];
foreach (array_merge($sku_groups, $title_groups) as $group) {
$ids = explode(',', $group->product_ids);
$prices = [];
foreach ($ids as $id) {
$price = get_post_meta($id, '_price', true);
$prices[$id] = is_numeric($price) ? floatval($price) : 0;
}
arsort($prices);
array_shift($prices);
$deleted = array_merge($deleted, array_keys($prices));
}
self::log_and_display_count('Товарів з найменшою ціною для видалення', count($deleted));
self::delete_fast($deleted);
echo "
📉 Видалено " . count($deleted) . " товарів з найменшою ціною у групах-дублікатах.
";
}
// ⚡ Видалення всіх дублікатів
public static function handle_duplicate_fast_cleanup() {
if (!isset($_POST['delete_all_fast'])) return;
$sku = self::get_duplicate_skus();
$title = self::get_duplicate_titles();
$ids = [];
foreach (array_merge($sku, $title) as $row) {
$group = explode(',', $row->product_ids);
array_shift($group);
$ids = array_merge($ids, $group);
}
self::log_and_display_count('Товарів-дублікатів для видалення', count($ids));
self::delete_fast($ids);
echo "
⚡ Видалено " . count($ids) . " товарів-дублікатів через SQL.
";
}
// 🧹 Видалення порожніх категорій
public static function handle_empty_category_deletion() {
global $wpdb;
if (!isset($_POST['delete_empty_categories'])) return;
$empty = $wpdb->get_col("
SELECT term_id FROM {$wpdb->term_taxonomy}
WHERE taxonomy = 'product_cat' AND count = 0
");
self::log_and_display_count('Порожніх категорій для видалення', count($empty));
foreach ($empty as $id) {
if (term_exists($id, 'product_cat')) {
wp_delete_term($id, 'product_cat');
}
}
self::log_action(["🧹 Видалено порожніх категорій: " . count($empty)]);
echo "
";
}
// 🧠 Обʼєднання категорій
public static function handle_category_merging() {
global $wpdb;
if (!isset($_POST['merge_duplicate_categories'])) return;
$groups = $wpdb->get_results("
SELECT t.name, GROUP_CONCAT(t.term_id) AS ids
");
self::log_and_display_count('Груп категорій з однаковими назвами для обʼєднання', count($groups));
$moved = 0;
$deleted = 0;
foreach ($groups as $group) {
$ids = array_map('intval', explode(',', $group->ids));
$counts = [];
foreach ($ids as $id) {
$count = $wpdb->get_var("
SELECT count FROM {$wpdb->term_taxonomy}
WHERE term_id = $id AND taxonomy = 'product_cat'
");
$counts[$id] = intval($count);
}
arsort($counts);
$main_id = array_key_first($counts);
foreach ($ids as $id) {
if ($id === $main_id) continue;
// Перенесення звʼязків товарів
$wpdb->query("
UPDATE {$wpdb->term_relationships} tr
JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
SET tr.term_taxonomy_id = (
SELECT term_taxonomy_id FROM {$wpdb->term_taxonomy}
WHERE term_id = $main_id AND taxonomy = 'product_cat' LIMIT 1
)
WHERE tt.term_id = $id AND tt.taxonomy = 'product_cat'
");
// Видалення дубліката категорії
if (term_exists($id, 'product_cat')) {
wp_delete_term($id, 'product_cat');
}
$deleted++;
}
$moved += count($ids) - 1;
}
$timestamp = current_time('mysql');
self::log_action([
"[$timestamp] 🧠 Категорії обʼєднано: $moved, видалено: $deleted"
]);
echo "
🧠 Перенесено $moved товарів у головні категорії, видалено $deleted дублікатів категорій.
";
}
}
// 🔍 Пошук товарів без зображення
public static function handle_count_no_image() {
if (!isset($_POST['count_no_image'])) return;
try {
$query = new WP_Query([
'post_type' => 'product',
'posts_per_page' => -1,
'meta_query' => [
[
'key' => '_thumbnail_id',
'value' => '',
'compare' => '='
]
]
]);
$count = $query->found_posts;
self::log_and_display_count('Знайдено товарів без зображення', $count);
} catch (Throwable $e) {
self::log_error('handle_count_no_image', $e);
echo "
🚫 Помилка при пошуку: " . esc_html($e->getMessage()) . "
";
}
}
// ⚠️ Видалення записів не типу 'product' з транзакцією
public static function handle_delete() {
global $wpdb;
if (!isset($_POST['delete_non_products'])) return;
try {
$ids = $wpdb->get_col("
SELECT ID FROM {$wpdb->prefix}posts
WHERE post_type != 'product'
");
$count = count($ids);
self::log_and_display_count('Записів не типу product для видалення', $count);
if ($count === 0) {
echo "
🚫 Не знайдено записів для видалення.
";
return;
}
$safe_ids = implode(',', array_map('intval', $ids));
$timestamp = current_time('mysql');
$wpdb->query('START TRANSACTION');
$deleted_posts = $wpdb->query("DELETE FROM {$wpdb->prefix}posts WHERE ID IN ($safe_ids)");
$deleted_meta = $wpdb->query("DELETE FROM {$wpdb->prefix}postmeta WHERE post_id IN ($safe_ids)");
if ($deleted_posts === false || $deleted_meta === false) {
$wpdb->query('ROLLBACK');
throw new Exception('Помилка при видаленні записів або метаданих');
}
$wpdb->query('COMMIT');
self::log_action([
"[$timestamp] ⚠️ Видалено записів: $count",
"ID: $safe_ids"
]);
echo "