WP-CLI: найти записи WordPress с битой миниатюрой

WP-CLI: найти записи WordPress с битой миниатюрой

На новостном или медийном сайте миниатюра записи это часть вёрстки, превью в соцсетях и карточки в выдаче. Файлы пропадают по разным причинам: почистили uploads, криво перенесли сайт, сломался CDN, удалили вложение, а в записи осталась мета _thumbnail_id. В ленте появляются дыры, в карточках пустые места.

Ниже своя команда WP-CLI, которая находит записи с битой миниатюрой. По умолчанию она только показывает список, а что с ним делать, решаете вы: снять миниатюру или отправить запись в корзину. Безвозвратного удаления в ней нет специально.

Что проверяет команда

  • Берёт записи нужного типа (по умолчанию post) с метой _thumbnail_id в любом статусе, кроме корзины.
  • Проверяет, что вложение существует и у него есть URL.
  • Проверяет, что файл лежит на диске.
  • С флагом --check-url ещё и делает HEAD-запрос к URL миниатюры: это ловит проблемы CDN, но на большом сайте заметно медленнее.
  • С флагом --unset снимает миниатюру через delete_post_thumbnail(), с флагом --trash переносит запись в корзину через wp_trash_post().

Код команды

Код можно положить в mu-plugin или в отдельный файл. Команда регистрируется через WP_CLI::add_command, как описано в руководстве по командам WP-CLI, а полоса прогресса берётся из WP_CLI\Utils\make_progress_bar.

<?php
/**
 * wp missing-thumbs [--check-url] [--unset] [--trash] [--post_type=<type>]
 * Без флагов только показывает записи с битой миниатюрой.
 */
if ( defined( 'WP_CLI' ) && WP_CLI ) {
	WP_CLI::add_command( 'missing-thumbs', 'krv_missing_thumbs' );
}

function krv_missing_thumbs( $args, $assoc_args ) {
	$check_url = WP_CLI\Utils\get_flag_value( $assoc_args, 'check-url', false );
	$unset     = WP_CLI\Utils\get_flag_value( $assoc_args, 'unset', false );
	$trash     = WP_CLI\Utils\get_flag_value( $assoc_args, 'trash', false );
	$post_type = isset( $assoc_args['post_type'] ) ? $assoc_args['post_type'] : 'post';

	$ids = get_posts( array(
		'post_type'      => $post_type,
		'post_status'    => 'any',
		'posts_per_page' => -1,
		'fields'         => 'ids',
		'meta_key'       => '_thumbnail_id',
	) );

	$progress = WP_CLI\Utils\make_progress_bar( 'Проверка миниатюр', count( $ids ) );
	$bad      = 0;

	foreach ( $ids as $post_id ) {
		$thumb_id = (int) get_post_thumbnail_id( $post_id );
		$url      = $thumb_id ? wp_get_attachment_url( $thumb_id ) : false;
		$reason   = '';

		if ( ! $url ) {
			$reason = 'вложения нет или у него пустой URL';
		} else {
			$file = get_attached_file( $thumb_id );
			if ( ! $file || ! file_exists( $file ) ) {
				$reason = 'файла нет на диске';
			} elseif ( $check_url ) {
				$res  = wp_remote_head( $url, array( 'timeout' => 10 ) );
				$code = (int) wp_remote_retrieve_response_code( $res );
				if ( is_wp_error( $res ) ) {
					$reason = 'URL не открылся: ' . $res->get_error_message();
				} elseif ( $code >= 400 ) {
					$reason = 'URL отвечает ' . $code;
				}
			}
		}

		if ( $reason ) {
			$bad++;
			WP_CLI::log( "#$post_id " . get_the_title( $post_id ) . ": $reason" );
			if ( $trash ) {
				wp_trash_post( $post_id );
			} elseif ( $unset ) {
				delete_post_thumbnail( $post_id );
			}
		}
		$progress->tick();
	}

	$progress->finish();
	WP_CLI::success( 'Проверено: ' . count( $ids ) . ", с битой миниатюрой: $bad" );
}

Почему корзина, а не удаление навсегда

В старой версии этого скрипта записи удалялись через wp_delete_post( $id, true ). По документации WordPress, второй аргумент true удаляет запись мимо корзины, вместе с метаданными, комментариями и терминами. Ошибка в проверке (например, временно недоступный CDN) тогда стоит статей. wp_trash_post() оставляет возможность всё вернуть.

Часто запись вообще не нужно трогать, достаточно снять сломанную миниатюру. Для этого есть delete_post_thumbnail(): она убирает связь записи с картинкой, а тема покажет заглушку или вёрстку без изображения.

Как запускать

Если файл лежит в mu-plugins, команда доступна сразу. Если это отдельный файл, его можно подключить глобальным параметром --require: WP-CLI загрузит его перед запуском команды.

# только отчёт
wp --require=missing-thumbs.php missing-thumbs --path=/var/www/example/htdocs

# с проверкой URL и снятием битых миниатюр
wp --require=missing-thumbs.php missing-thumbs --check-url --unset --path=/var/www/example/htdocs

# в корзину, для своего типа записей
wp --require=missing-thumbs.php missing-thumbs --trash --post_type=news --path=/var/www/example/htdocs

Раз в сутки по cron, только отчёт в лог:

0 3 * * * /usr/local/bin/wp --require=/opt/scripts/missing-thumbs.php missing-thumbs --path=/var/www/example/htdocs >> /var/log/wp-missing-thumbs.log 2>&1

Cron лучше запускать от пользователя, которому принадлежит сайт (например, www-data), а не от root с --allow-root. И не вешайте такую проверку на admin_init: каждый заход в админку будет гонять тяжёлый обход и легко упрётся в таймаут.

Перед запуском на рабочем сайте

  • Сделать бэкап базы и uploads.
  • Сначала прогнать команду без флагов и посмотреть список.
  • Проверить на копии сайта с тем же набором медиа.
  • Решить, что правильнее для вашего сайта: снять миниатюру, перезалить картинку или убрать запись.

В итоге WP-CLI удобен для такой уборки: прогресс, запуск по cron и никакой нагрузки на админку. Команда сначала показывает записи с битой миниатюрой, а дальше вы сами выбираете мягкий вариант (снять миниатюру) или жёсткий (в корзину). Удаление навсегда лучше делать руками и после проверки.

Источники и ссылки


Комментарии загружаются…