class WC_Deduplicator { // πŸ§ͺ Π£Π½Ρ–Ρ„Ρ–ΠΊΠΎΠ²Π°Π½Π° ΠΏΠ΅Ρ€Π΅Π²Ρ–Ρ€ΠΊΠ° dry-run public static function is_dry_run(): bool { return isset($_POST['dry_run']) && $_POST['dry_run'] === '1'; } // πŸ“ Логування Π΄Ρ–ΠΉ public static function log_action($lines) { if (self::is_protected() && !self::is_dry_run()) return; $upload_dir = wp_upload_dir(); $log_file = $upload_dir['basedir'] . '/deduplicator-log.txt'; try { $content = implode(PHP_EOL, $lines) . PHP_EOL; if (is_writable($upload_dir['basedir'])) { file_put_contents($log_file, $content, FILE_APPEND); } else { $fallback = ABSPATH . 'deduplicator-fallback-log.txt'; file_put_contents($fallback, $content, FILE_APPEND); error_log("Deduplicator fallback log записано Π² $fallback"); } } catch (Throwable $e) { self::log_error('log_action', $e); } } // πŸ› οΈ Логування ΠΏΠΎΠΌΠΈΠ»ΠΎΠΊ public static function log_error($context, Throwable $e) { $timestamp = current_time('mysql'); error_log("[$timestamp] ❌ [$context] " . $e->getMessage()); } // πŸ” Π’ΠΈΠ²Ρ–Π΄ ΠΊΡ–Π»ΡŒΠΊΠΎΡΡ‚Ρ– public static function log_and_display_count($label, $count, $type = 'info') { $timestamp = current_time('mysql'); if (!self::is_protected() || self::is_dry_run()) { self::log_action(["[$timestamp] πŸ” $label: $count"]); } $class = $type === 'error' ? 'notice-error' : ($type === 'warning' ? 'notice-warning' : 'notice-info'); echo "

πŸ” $label: $count

"; return $count; // βœ… Π”ΠΎΠ΄Π°Π½ΠΎ для ΠΏΠ΅Ρ€Π΅Π΄Π°Ρ‡Ρ– Ρ€Π΅Π·ΡƒΠ»ΡŒΡ‚Π°Ρ‚Ρƒ Π² UI } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² SKU public static function get_duplicate_skus() { global $wpdb; if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² SKU Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return []; } 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; if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² Π½Π°Π·Π² Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return []; } 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 "); } // ⚑ ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² (Π°Π±ΠΎ dry-run) public static function delete_fast($ids, $preview = false) { global $wpdb; if (empty($ids)) return; $safe_ids = implode(',', array_map('intval', $ids)); if ($preview || self::is_dry_run()) { self::log_and_display_count('ΠŸΠΎΠΏΠ΅Ρ€Π΅Π΄Π½Ρ–ΠΉ пСрСгляд Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² для видалСння', count($ids), 'warning'); echo "

πŸ§ͺ Dry-run: " . count($ids) . " Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±ΡƒΠ΄Π΅ Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ.
ID: $safe_ids

"; return; } if (self::is_protected()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ВидалСння Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } $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'); self::log_action(["❌ Rollback Ρ‡Π΅Ρ€Π΅Π· ΠΏΠΎΠΌΠΈΠ»ΠΊΡƒ ΠΏΡ€ΠΈ Π²ΠΈΠ΄Π°Π»Π΅Π½Π½Ρ– ID: $safe_ids"]); echo "

🚫 Помилка ΠΏΡ€ΠΈ SQL-Π²ΠΈΠ΄Π°Π»Π΅Π½Π½Ρ–. ΠžΠΏΠ΅Ρ€Π°Ρ†Ρ–ΡŽ скасовано.

"; return; } $wpdb->query('COMMIT'); $timestamp = current_time('mysql'); self::log_action(["[$timestamp] βœ… SQL-видалСння: " . count($ids) . " Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π²", "ID: $safe_ids"]); echo "

βœ… Π’ΠΈΠ΄Π°Π»Π΅Π½ΠΎ " . count($ids) . " Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Ρ‡Π΅Ρ€Π΅Π· SQL.

"; } } // ⚑ ВидалСння всіх Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² 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); } if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. Π—ΠΌΡ–Π½ΠΈ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π²-Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² для видалСння', count($ids)); self::delete_fast($ids, self::is_dry_run()); } // 🧹 ВидалСння ΠΏΠΎΡ€ΠΎΠΆΠ½Ρ–Ρ… ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ 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)); if (self::is_dry_run()) { echo "

πŸ§ͺ Dry-run: Π±ΡƒΠ΄Π΅ Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ " . count($empty) . " ΠΏΠΎΡ€ΠΎΠΆΠ½Ρ–Ρ… ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ.
ID: " . implode(', ', $empty) . "

"; $names = $wpdb->get_results(" SELECT term_id, name FROM {$wpdb->terms} WHERE term_id IN (" . implode(',', array_map('intval', $empty)) . ") "); if ($names) { echo ""; } echo "
"; return; } // πŸ”’ Захист ΠΏΠ΅Ρ€Π΅Π΄ Ρ€Π΅Π°Π»ΡŒΠ½ΠΈΠΌ видалСнням if (self::is_protected()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ВидалСння ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } $deleted = 0; foreach ($empty as $id) { if (term_exists($id, 'product_cat')) { wp_delete_term($id, 'product_cat'); $deleted++; } } $timestamp = current_time('mysql'); self::log_action(["[$timestamp] 🧹 Π’ΠΈΠ΄Π°Π»Π΅Π½ΠΎ ΠΏΠΎΡ€ΠΎΠΆΠ½Ρ–Ρ… ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ: $deleted"]); echo "

🧹 Π’ΠΈΠ΄Π°Π»Π΅Π½ΠΎ $deleted ΠΏΠΎΡ€ΠΎΠΆΠ½Ρ–Ρ… ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ.

"; } // 🧠 ΠžΠ±ΚΌΡ”Π΄Π½Π°Π½Π½Ρ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ 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 FROM {$wpdb->terms} t JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'product_cat' GROUP BY t.name HAVING COUNT(*) > 1 "); self::log_and_display_count('Π“Ρ€ΡƒΠΏ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ Π· ΠΎΠ΄Π½Π°ΠΊΠΎΠ²ΠΈΠΌΠΈ Π½Π°Π·Π²Π°ΠΌΠΈ для обʼєднання', count($groups)); $moved = 0; $deleted = 0; $preview = self::is_dry_run(); // πŸ”’ Захист ΠΏΠ΅Ρ€Π΅Π΄ Ρ€Π΅Π°Π»ΡŒΠ½ΠΈΠΌ обʼєднанням if (self::is_protected() && !$preview) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠžΠ±ΚΌΡ”Π΄Π½Π°Π½Π½Ρ ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } 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); $to_merge = array_diff($ids, [$main_id]); if ($preview) { $names = $wpdb->get_results(" SELECT term_id, name FROM {$wpdb->terms} WHERE term_id IN (" . implode(',', array_map('intval', $to_merge)) . ") "); echo "

πŸ§ͺ Dry-run: катСгорія $main_id ← ΠΎΠ±ΚΌΡ”Π΄Π½Π°Ρ”:

"; $moved += count($to_merge); $deleted += count($to_merge); continue; } foreach ($to_merge as $id) { $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($to_merge); } $timestamp = current_time('mysql'); self::log_action(["[$timestamp] 🧠 ΠšΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–Ρ— ΠΎΠ±ΚΌΡ”Π΄Π½Π°Π½ΠΎ: $moved, Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ: $deleted"]); if ($preview) { echo "

πŸ§ͺ Dry-run Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½ΠΎ: $moved ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ Π±ΡƒΠ΄Π΅ пСрСнСсСно, $deleted Π±ΡƒΠ΄Π΅ Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ.

"; } else { echo "

🧠 ΠŸΠ΅Ρ€Π΅Π½Π΅ΡΠ΅Π½ΠΎ $moved Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Ρƒ Π³ΠΎΠ»ΠΎΠ²Π½Ρ– ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–Ρ—, Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ $deleted Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ.

"; } } // ⚠️ ВидалСння записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ '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'); if (self::is_dry_run()) { echo "

πŸ§ͺ Dry-run: $count записів Π±ΡƒΠ΄Π΅ Π²ΠΈΠ΄Π°Π»Π΅Π½ΠΎ.
ID: $safe_ids

"; $names = $wpdb->get_results(" SELECT ID, post_title, post_type FROM {$wpdb->prefix}posts WHERE ID IN ($safe_ids) "); if ($names) { echo ""; } echo "
"; self::log_action(["[$timestamp] πŸ§ͺ Dry-run: $count записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ product", "ID: $safe_ids"]); return; } // πŸ”’ Захист ΠΏΠ΅Ρ€Π΅Π΄ Ρ€Π΅Π°Π»ΡŒΠ½ΠΈΠΌ видалСнням if (self::is_protected()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ВидалСння записів Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } $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 "

⚠️ Π’ΠΈΠ΄Π°Π»Π΅Π½ΠΎ $count записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ product.

"; } catch (Throwable $e) { $wpdb->query('ROLLBACK'); self::log_error('handle_delete', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ Π²ΠΈΠ΄Π°Π»Π΅Π½Π½Ρ–: " . esc_html($e->getMessage()) . "

"; } } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· зобраТСння public static function handle_count_no_image() { if (!isset($_POST['count_no_image'])) return; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· зобраТСння Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; 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()) . "

"; } } // πŸ”’ ΠŸΠ΅Ρ€Π΅Π²Ρ–Ρ€ΠΊΠ° Ρ€Π΅ΠΆΠΈΠΌΡƒ захисту public static function is_protected(): bool { return defined('WC_DEDUPLICATOR_SAFE_MODE') && WC_DEDUPLICATOR_SAFE_MODE === true; } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· наявності public static function handle_count_outofstock() { // βœ… ΠŸΡ–Π΄Ρ‚Ρ€ΠΈΠΌΠΊΠ° ΠΎΠ±ΠΎΡ… ΠΊΠ½ΠΎΠΏΠΎΠΊ: Π·Π²ΠΈΡ‡Π°ΠΉΠ½ΠΎΡ— Ρ‚Π° dry-run if (!isset($_POST['count_outofstock']) && !isset($_POST['count_outofstock_dry'])) return; // βœ… ВстановлСння dry_run Π²Ρ€ΡƒΡ‡Π½Ρƒ $_POST['dry_run'] = isset($_POST['count_outofstock_dry']) ? '1' : '0'; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· наявності Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } try { global $wpdb; $count = (int) $wpdb->get_var(" SELECT COUNT(*) FROM {$wpdb->prefix}postmeta pm JOIN {$wpdb->prefix}posts p ON pm.post_id = p.ID WHERE pm.meta_key = '_stock_status' AND pm.meta_value = 'outofstock' AND p.post_type = 'product' AND p.post_status IN ('publish', 'draft') "); self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· наявності', $count); } catch (Throwable $e) { self::log_error('handle_count_outofstock', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ ΠΏΠΎΡˆΡƒΠΊΡƒ: " . esc_html($e->getMessage()) . "

"; } } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ public static function handle_count_missing_sku() { if (!isset($_POST['count_missing_sku'])) return; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } try { global $wpdb; $count = $wpdb->get_var(" SELECT COUNT(*) FROM {$wpdb->prefix}posts p LEFT JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_sku' WHERE p.post_type = 'product' AND p.post_status IN ('publish', 'draft') AND (pm.meta_value IS NULL OR pm.meta_value = '') "); self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ', $count); } catch (Throwable $e) { self::log_error('handle_count_missing_sku', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ ΠΏΠΎΡˆΡƒΠΊΡƒ: " . esc_html($e->getMessage()) . "

"; } } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² public static function handle_count_duplicates() { if (!isset($_POST['count_duplicates'])) return; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } try { $sku = self::get_duplicate_skus(); $title = self::get_duplicate_titles(); $count = count($sku) + count($title); self::log_and_display_count('Π“Ρ€ΡƒΠΏ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π²', $count); } catch (Throwable $e) { self::log_error('handle_count_duplicates', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ ΠΏΠΎΡˆΡƒΠΊΡƒ: " . esc_html($e->getMessage()) . "

"; } } // πŸ” ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ public static function handle_count_duplicate_categories() { global $wpdb; if (!isset($_POST['count_duplicate_categories'])) return; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } try { $groups = $wpdb->get_results(" SELECT t.name, COUNT(*) AS total FROM {$wpdb->terms} t JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'product_cat' GROUP BY t.name HAVING total > 1 "); self::log_and_display_count('Π“Ρ€ΡƒΠΏ Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² ΠΊΠ°Ρ‚Π΅Π³ΠΎΡ€Ρ–ΠΉ', count($groups)); } catch (Throwable $e) { self::log_error('handle_count_duplicate_categories', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ ΠΏΠΎΡˆΡƒΠΊΡƒ: " . esc_html($e->getMessage()) . "

"; } } // πŸ” ΠŸΠΎΡˆΡƒΠΊ записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ product public static function handle_count_non_products() { global $wpdb; if (!isset($_POST['count_non_products'])) return; // πŸ”’ Захист: Π΄ΠΎΠ·Π²ΠΎΠ»ΠΈΡ‚ΠΈ Ρ‚Ρ–Π»ΡŒΠΊΠΈ dry-run Ρƒ SAFE_MODE if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ΠŸΠΎΡˆΡƒΠΊ записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ product Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } try { $count = $wpdb->get_var(" SELECT COUNT(*) FROM {$wpdb->prefix}posts WHERE post_type != 'product' "); self::log_and_display_count('Записів Π½Π΅ Ρ‚ΠΈΠΏΡƒ product', $count); } catch (Throwable $e) { self::log_error('handle_count_non_products', $e); echo "

🚫 Помилка ΠΏΡ€ΠΈ ΠΏΠΎΡˆΡƒΠΊΡƒ: " . esc_html($e->getMessage()) . "

"; } } // ⚑ ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ public static function handle_missing_sku_fast_deletion() { global $wpdb; if (!isset($_POST['delete_missing_sku_fast'])) return; try { $ids = $wpdb->get_col(" SELECT p.ID FROM {$wpdb->prefix}posts p LEFT JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_sku' WHERE p.post_type = 'product' AND p.post_status IN ('publish', 'draft') AND (pm.meta_value IS NULL OR pm.meta_value = '') "); self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ для видалСння', count($ids)); // πŸ”’ Захист ΠΏΠ΅Ρ€Π΅Π΄ Ρ€Π΅Π°Π»ΡŒΠ½ΠΈΠΌ видалСнням if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Π°Ρ€Ρ‚ΠΈΠΊΡƒΠ»Ρƒ Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } self::delete_fast($ids, self::is_dry_run()); } catch (Throwable $e) { self::log_error('handle_missing_sku_fast_deletion', $e); echo "

🚫 Помилка: " . esc_html($e->getMessage()) . "

"; } } // ⚑ ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· Ρ„ΠΎΡ‚ΠΎ / Π±Π΅Π· наявності public static function handle_fast_deletions() { global $wpdb; $key = isset($_POST['delete_no_image_fast']) ? 'image' : (isset($_POST['delete_outofstock_fast']) ? 'stock' : null); if (!$key) return; try { $ids = []; if ($key === 'image') { $ids = $wpdb->get_col(" SELECT p.ID FROM {$wpdb->prefix}posts p LEFT JOIN {$wpdb->prefix}postmeta pm ON p.ID = pm.post_id AND pm.meta_key = '_thumbnail_id' WHERE p.post_type = 'product' AND p.post_status IN ('publish', 'draft') AND (pm.meta_value IS NULL OR pm.meta_value = '') "); self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· зобраТСння для видалСння', count($ids)); } else { $ids = $wpdb->get_col(" SELECT p.ID FROM {$wpdb->prefix}postmeta pm JOIN {$wpdb->prefix}posts p ON pm.post_id = p.ID WHERE pm.meta_key = '_stock_status' AND pm.meta_value = 'outofstock' AND p.post_type = 'product' AND p.post_status IN ('publish', 'draft') "); self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π±Π΅Π· наявності для видалСння', count($ids)); } // πŸ”’ Захист ΠΏΠ΅Ρ€Π΅Π΄ Ρ€Π΅Π°Π»ΡŒΠ½ΠΈΠΌ видалСнням if (self::is_protected() && !self::is_dry_run()) { echo "

πŸ”’ Π Π΅ΠΆΠΈΠΌ захисту Π°ΠΊΡ‚ΠΈΠ²Π½ΠΈΠΉ. ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π·Π°Π±Π»ΠΎΠΊΠΎΠ²Π°Π½ΠΎ. ВикористовуйтС dry-run.

"; return; } self::delete_fast($ids, self::is_dry_run()); } catch (Throwable $e) { self::log_error('handle_fast_deletions', $e); echo "

🚫 Помилка: " . esc_html($e->getMessage()) . "

"; } } // ⚑ ВидалСння Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² Π· наймСншою Ρ†Ρ–Π½ΠΎΡŽ сСрСд Π΄ΡƒΠ±Π»Ρ–ΠΊΠ°Ρ‚Ρ–Π² public static function handle_low_price_cleanup() { global $wpdb; if (!isset($_POST['delete_low_price_duplicates'])) return; $sku_groups = $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 "); $ids_to_delete = []; foreach ($sku_groups as $group) { $ids = explode(',', $group->product_ids); $prices = []; foreach ($ids as $id) { $price = $wpdb->get_var($wpdb->prepare(" SELECT meta_value FROM {$wpdb->prefix}postmeta WHERE post_id = %d AND meta_key = '_price' ", $id)); $prices[$id] = floatval($price); } if (count($prices) <= 1) continue; asort($prices); // lowest first $to_delete = array_keys($prices); array_shift($to_delete); // keep one $ids_to_delete = array_merge($ids_to_delete, $to_delete); } self::log_and_display_count('Π’ΠΎΠ²Π°Ρ€Ρ–Π² Π· наймСншою Ρ†Ρ–Π½ΠΎΡŽ для видалСння', count($ids_to_delete)); self::delete_fast($ids_to_delete, self::is_dry_run()); } } TAY Π’ΠΎΠ²Π°Ρ€ΠΈ для Π΄Ρ–Ρ‚Π΅ΠΉ – Π‘Ρ‚ΠΎΡ€Ρ–Π½ΠΊΠ° 11

Магазин Ρ‚ΠΎΠ²Π°Ρ€Ρ–Π² для Π΄Ρ–Ρ‚Π΅ΠΉ

Π£Π²Π°Π³Π°!

πŸ› Tay.com.ua β€” ΠΊΡƒΠΏΡƒΠΉ Π²ΠΈΠ³Ρ–Π΄Π½ΠΎ!
πŸ”” ΠœΡ–Π½Ρ–ΠΌΠ°Π»ΡŒΠ½Π΅ замовлСння β€” 600 Π³Ρ€Π½
πŸ“¦ Π’Ρ–Π΄ΠΏΡ€Π°Π²ΠΊΠ° протягом 1–3 Π΄Π½Ρ–Π² Β Π΄ΠΎ 19:00 (пʼятниця β€” Π²ΠΈΡ…Ρ–Π΄Π½ΠΈΠΉ)
🎁 Π—Π½ΠΈΠΆΠΊΠ° Β ΠΏΡ€ΠΈ Π·Π°ΠΌΠΎΠ²Π»Π΅Π½Π½Ρ– Π²Ρ–Π΄ 1000 Π³Ρ€Π½Β 
🚚 КоТнС Ρ‚Ρ€Π΅Ρ‚Ρ” замовлСння β€” Π±Π΅Π·ΠΊΠΎΡˆΡ‚ΠΎΠ²Π½Π° доставка (ΠΊΡ€Ρ–ΠΌ Π³Π°Π±Π°Ρ€ΠΈΡ‚Π½ΠΈΡ… ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ)
🌟 ΠŸΠΎΡΡ‚Ρ–ΠΉΠ½ΠΈΠΌ ΠΊΠ»Ρ–Ρ”Π½Ρ‚Π°ΠΌ β€” Ρ–Π½Π΄ΠΈΠ²Ρ–Π΄ΡƒΠ°Π»ΡŒΠ½Ρ– Π·Π½ΠΈΠΆΠΊΠΈ Ρ‚Π° ΠΏΡ€ΠΈΡ”ΠΌΠ½Ρ– бонуси
πŸ“² ЗамовляйтС Π½Π° tay.com.ua β€” Π»ΠΎΠ²Ρ–Ρ‚ΡŒ Π²ΠΈΠ³ΠΎΠ΄Ρƒ!

АсортимСнт