На новостном или медийном сайте миниатюра записи это часть вёрстки, превью в соцсетях и карточки в выдаче. Файлы пропадают по разным причинам: почистили 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
--require подключает PHP-файл перед выполнением команды, так своя команда работает без отдельного плагина. Источник: make.wordpress.org, WP-CLI config.Раз в сутки по 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 и никакой нагрузки на админку. Команда сначала показывает записи с битой миниатюрой, а дальше вы сами выбираете мягкий вариант (снять миниатюру) или жёсткий (в корзину). Удаление навсегда лучше делать руками и после проверки.
Источники и ссылки
--require и --path.
WP-CLI Commands Cookbook
Как написать и зарегистрировать свою команду WP-CLI.