Як безпечно видалити неприкріплені зображення з медіатеки WordPress

Привіт:) З часом у медіатеці WordPress накопичуються сотні або навіть тисячі зображень, частина з яких більше не використовується. Це можуть бути старі картинки, завантажені під час редагування записів, або неприкріплені зображення — файли, що колись були додані, але тепер не прив’язані до жодної сторінки чи публікації.

На перший погляд, це не проблема. Але насправді надлишок таких файлів створює серйозне навантаження на сервер, займає місце в резервних копіях і уповільнює роботу сайту. Кожен непотрібний файл — це додаткові мегабайти, які зберігаються і обробляються щоразу, коли ви робите бекап, оновлюєте сайт чи переносите його на інший хостинг.

Регулярне видалення неприкріплених зображень у WordPress допомагає підтримувати порядок у медіатеці, зменшити розмір бази даних і прискорити завантаження сайту. У цій статті розгляну, як безпечно знайти та видалити такі файли — як за допомогою плагінів, так і власним кодом.

Перед подальшими діями створіть повну резервну копію сайту у разі виникнення неочікуваних результатів.

Видалення неприкріплених зображень за допомогою плагіну

  1. Встановіть та активуйте плагін Media Cleaner (https://wordpress.org/plugins/media-cleaner/).
  2. Перейдіть в адмін-меню Медіафайли -> Cleaner та клацніть кнопку Scan.
  3. Виділіть знайдені зображення та натисніть Delete All.

Видалення неприкріплених зображень через власний код

Додайте наступний код у файл functions.php.

/**
 * Automatically Remove Unused Images from the WordPress Media Library
 * 
 * Mykhailo Petrov
 * https://petrov.net.ua/
 *
 * v.1.0 
 *
 */
if ( ! function_exists( 'mp_get_used_image_urls_and_ids' ) ) {
    // Збирає всі використані URL і ID зображень
    function mp_get_used_image_urls_and_ids() {
        global $wpdb;

        $results = $wpdb->get_col( "
            SELECT post_content FROM {$wpdb->posts}
            WHERE post_status IN ('publish', 'draft', 'pending', 'future')
            AND post_type IN ('post', 'page')
        " );

        $used_urls = array();
        $used_ids  = array();

        if ( empty( $results ) ) {
            return array(
                'urls' => array(), 
                'ids' => array()
            );
        }

        foreach ( $results as $content ) {
            if ( $content === null ) continue;
            $content = ( string ) $content;

            // <img src="...">
            if ( preg_match_all( '/<img[^>]+src=[\'"]([^\'"]+)[\'"]/', $content, $m ) && ! empty( $m[1] ) ) {
                $used_urls = array_merge( $used_urls, $m[1] );
            }

            // srcset="url1 1x, url2 2x"
            if ( preg_match_all( '/<img[^>]+srcset=[\'"]([^\'"]+)[\'"]/', $content, $m2 ) && ! empty( $m2[1] ) ) {
                foreach ( $m2[1] as $srcset ) {
                    $parts = explode( ',', $srcset );
                    foreach ( $parts as $part ) {
                        $url = trim( preg_replace( '/\s+\d+(w|x)$/', '', $part ) );
                        if ( $url ) $used_urls[] = $url;
                    }
                }
            }

            // wp-image-123
            if ( preg_match_all( '/wp-image-([0-9]+)/', $content, $idm ) && ! empty( $idm[1] ) ) {
                foreach ( $idm[1] as $id ) {
                    $id = ( int ) $id;
                    if ( $id ) {
                        $used_ids[] = $id;
                        $u = wp_get_attachment_url( $id );
                        if ( $u ) $used_urls[] = $u;
                    }
                }
            }

            // [gallery ids="1,2,3"]
            if ( preg_match_all( '/\[gallery[^\]]*ids=[\'"]?([0-9,\s]+)[\'"]?/', $content, $gm ) && ! empty( $gm[1] ) ) {
                foreach ( $gm[1] as $ids ) {
                    foreach ( explode( ',', $ids ) as $id ) {
                        $id = ( int ) trim( $id );
                        if ( $id ) {
                            $used_ids[] = $id;
                            $u = wp_get_attachment_url( $id );
                            if ( $u ) $used_urls[] = $u;
                        }
                    }
                }
            }
        }

        // Featured images
        $post_ids = $wpdb->get_col( "
            SELECT ID FROM {$wpdb->posts}
            WHERE post_status IN ('publish', 'draft', 'pending', 'future')
            AND post_type IN ('post', 'page')
        " );

        if ( ! empty( $post_ids ) ) {
            foreach ( $post_ids as $pid ) {
                $thumb_id = get_post_thumbnail_id( $pid );
                if ( $thumb_id ) {
                    $used_ids[] = ( int ) $thumb_id;
                    $u = wp_get_attachment_url( $thumb_id );
                    if ( $u ) $used_urls[] = $u;
                }
            }
        }

        $used_urls = array_unique( array_filter( $used_urls ) );
        $used_ids  = array_unique( array_filter( $used_ids ) );

        return array(
            'urls' => $used_urls,
            'ids' => $used_ids
        );
    }
}

if ( ! function_exists( 'mp_find_unused_images' ) ) {
    // Знаходить невикористані зображення
    function mp_find_unused_images() {
        global $wpdb;

        $used = mp_get_used_image_urls_and_ids();
        $used_urls = $used['urls'];
        $used_ids  = $used['ids'];
        $unused    = array();

        $attachments = get_posts( 
            array(
                'post_type'      => 'attachment',
                'post_mime_type' => 'image',
                'numberposts'    => -1,
                'post_status'    => 'inherit',
            ) 
        );

        foreach ( $attachments as $attachment ) {
            $att_id  = ( int ) $attachment->ID;
            $att_url = wp_get_attachment_url( $att_id );
            if ( ! $att_url ) continue;

            // Якщо ID вже використовується
            if ( in_array( $att_id, $used_ids, true ) ) continue;

            $found = false;

            // Прямий збіг URL
            foreach ( $used_urls as $u ) {
                if ( strpos( $u, $att_url ) !== false ) {
                    $found = true;
                    break;
                }
            }
            if ( $found ) continue;

            // Перевірка за базовим іменем (photo-150x150.jpg ~ photo.jpg)
            $att_basename = pathinfo( $att_url, PATHINFO_FILENAME );
            foreach ( $used_urls as $u ) {
                if ( strpos( $u, $att_basename ) !== false ) {
                    $found = true;
                    break;
                }
            }
            if ( $found ) continue;

            // Перевірка у postmeta (ID або URL)
            $meta_like = '%' . $wpdb->esc_like( ( string )$att_id ) . '%';
            $meta_exists = $wpdb->get_var( $wpdb->prepare(
                "SELECT meta_id FROM {$wpdb->postmeta} WHERE meta_value LIKE %s LIMIT 1",
                $meta_like
            ) );
            if ( $meta_exists ) continue;

            // Якщо нічого не знайдено — вважаємо невикористаним
            $unused[] = $attachment;
        }

        return $unused;
    }
}

if ( ! function_exists( 'mp_cleanup_unused_images_admin_page' ) ) {
    // Сторінка в адмінці
    function mp_cleanup_unused_images_admin_page() {
        if ( ! current_user_can( 'manage_options' ) ) return;

        echo '<div class="wrap"><h1>Unused Images</h1>';

        $unused = mp_find_unused_images();

        // Видалення
        if ( isset( $_POST['delete_unused'] ) && check_admin_referer( 'delete_unused_images' ) ) {
            $deleted = 0;
            foreach ( $unused as $image ) {
                if ( wp_delete_attachment( $image->ID, true ) ) {
                    $deleted++;
                }
            }
            echo '<div class="updated notice"><p>Deleted ' . esc_html( $deleted ) . ' unused images.</p></div>';
            $unused = mp_find_unused_images(); // оновити список
        }

        if ( empty( $unused ) ) {
            echo '<p><strong>No unused images found.</strong></p>';
        } else {
            echo '<p><strong>' . count( $unused ) . ' unused images found.</strong></p>';
            echo '<form method="post">';
            wp_nonce_field( 'delete_unused_images' );
            echo '<input type="submit" name="delete_unused" value="Delete All Unused Images" class="button button-primary" style="background:#d63638;border-color:#d63638;">';
            echo '</form>';
            echo '<ul style="margin-top:20px;">';
            foreach ( $unused as $img ) {
                $url = esc_url( wp_get_attachment_url( $img->ID ) );
                $thumb = wp_get_attachment_image( $img->ID, array( 60, 60 ), false, array( 'style' => 'vertical-align:middle;margin-right:10px;' ) );
                echo '<li>' . $thumb . esc_html( $img->post_title ) . ' – <code>' . $url . '</code></li>';
            }
            echo '</ul>';
        }

        echo '</div>';
    }
}

if ( ! function_exists( 'mp_register_unused_images_menu' ) ) {
    add_action( 'admin_menu', 'mp_register_unused_images_menu' );
    // Реєстрація сторінки в меню
    function mp_register_unused_images_menu() {
        add_management_page(
            'Unused Images Cleanup',
            'Unused Images',
            'manage_options',
            'unused-images',
            'mp_cleanup_unused_images_admin_page'
        );
    }
}

Далі відкрийте адмін-меню Інструменти -> Unused Images. Якщо були знайдені неприкріплені зображення, натисніть кнопку Delete All Unused Images.

Михайло Петров
Михайло Петров

Мене звати Михайло. Я — WordPress-розробник. Створюю візитки, корпоративні сайти, інтернет-магазини, блоги на WordPress. Надаю консультації з WordPress.

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *