true, 'deleted' => 0, 'started' => time(), 'finished' => 0], false); as_enqueue_async_action(self::HOOK, [], self::GROUP); return true; } /** * @return array{running:bool, deleted:int, started:int, finished:int} */ public static function status(): array { $data = get_option(self::OPTION, []); return array_merge(['running' => false, 'deleted' => 0, 'started' => 0, 'finished' => 0], is_array($data) ? $data : []); } public static function run(): void { $status = self::status(); if (!$status['running']) { return; } $ids = self::candidates(self::BATCH); foreach ($ids as $id) { if (wp_delete_attachment($id, true)) { $status['deleted']++; } } if (count($ids) === self::BATCH) { update_option(self::OPTION, $status, false); as_enqueue_async_action(self::HOOK, [], self::GROUP); return; } $status['running'] = false; $status['finished'] = time(); update_option(self::OPTION, $status, false); Log::warning(sprintf('Очистка картинок обмена: удалено %d неиспользуемых', $status['deleted'])); } /** * Сколько картинок будет удалено (для страницы настроек). */ public static function count(): int { return count(self::candidates(PHP_INT_MAX)); } /** * @return list */ private static function candidates(int $limit): array { global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- поиск по служебным полям во время обмена: нужен свежий результат без кеша; значения через prepare() $ours = array_map('intval', $wpdb->get_col($wpdb->prepare( "SELECT DISTINCT p.ID FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} m ON m.post_id = p.ID AND m.meta_key IN (%s, %s) WHERE p.post_type = 'attachment' AND p.post_date_gmt < %s ORDER BY p.ID ASC", Images::HASH_META, Images::LEGACY_HASH_META, gmdate('Y-m-d H:i:s', time() - self::MIN_AGE) ))); if (!$ours) { return []; } // Все картинки, которые где-то используются: главная у товара/вариации и галереи. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- поиск по служебным полям во время обмена: нужен свежий результат без кеша; значения через prepare() $used = array_fill_keys(array_map('intval', $wpdb->get_col( "SELECT DISTINCT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_thumbnail_id'" )), true); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- поиск по служебным полям во время обмена: нужен свежий результат без кеша; значения через prepare() foreach ($wpdb->get_col("SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_product_image_gallery' AND meta_value <> ''") as $list) { foreach (explode(',', (string) $list) as $id) { $used[(int) $id] = true; } } $result = []; foreach ($ours as $id) { if (!isset($used[$id])) { $result[] = $id; if (count($result) >= $limit) { break; } } } return $result; } }