Code xoá ảnh không sử dụng trong WordPress Media Library — NguyenThaiMMO
WordPress

Xóa Ảnh Không Sử Dụng Trong WordPress An Toàn

Thẩm định nội dung

Nội dung trên nguyenthaimmo.com được trực tiếp triển khai, kiểm thử và biên soạn bởi NguyenThaiMMO – người có nhiều năm kinh nghiệm thực chiến trong WordPress, SEO kỹ thuật, Automation, MMO & Affiliate.

Mỗi bài viết đều được thẩm định dựa trên trải nghiệm thực tế, ưu tiên hướng dẫn từng bước, chỉ ra lỗi thường gặp và giải pháp cụ thể, nhằm giúp người đọc áp dụng được ngay, hạn chế rủi ro và tối ưu hiệu quả lâu dài.

Xóa ảnh không sử dụng trong WordPress có thể giúp giảm dung lượng hosting, nhưng đây cũng là một trong những việc dễ làm hỏng website nhất nếu chỉ dựa vào trạng thái “Chưa đính kèm”. Một ảnh có post_parent = 0 vẫn có thể đang được dùng làm ảnh đại diện, banner, ảnh sản phẩm, background CSS hoặc nằm trong một trình dựng trang.

Có lần khách bên NguyenThaiMMO cần dọn Media Library nên mình đã chia sẻ code xử lý tự động. Khi xem lại kỹ hơn, mình thấy cách chạy hàm xóa ở hook wp_loaded quá rủi ro: code có thể quét ở mọi lượt tải trang và gọi wp_delete_attachment( $id, true ), tức là xóa vĩnh viễn cả attachment lẫn file trên hosting. Vì vậy, bài này chuyển sang cách an toàn hơn: dùng code để lập danh sách cần kiểm tra, còn quyết định xóa vẫn do bạn thực hiện.

Cảnh báo code tự động xóa ảnh không sử dụng trong WordPress — NguyenThaiMMO
Giao diện minh họa: không chạy lệnh xóa media vĩnh viễn ở hook wp_loaded.

Nếu đang dùng code tự động xóa ảnh, cần làm gì ngay?

Nếu file functions.php của bạn đang có hàm xóa attachment chạy bằng wp_loaded, hãy gỡ dòng hook trước:

add_action( 'wp_loaded', 'delete_unused_images' );

Sau khi gỡ, lưu file và kiểm tra lại trang chủ, một bài viết, trang sản phẩm và Media Library. Mục tiêu của bước này là dừng tác vụ tự động; nó không khôi phục được những file đã bị xóa trước đó.

Góc nhìn NguyenThaiMMO: dọn media nên là một quy trình kiểm tra rồi mới xóa, không phải một hàm chạy âm thầm theo lượt truy cập. Tiết kiệm vài trăm MB không đáng để đổi lấy nguy cơ mất ảnh sản phẩm, ảnh đại diện hoặc banner đang chạy.

Vì sao “Chưa đính kèm” không đồng nghĩa với “Không sử dụng”?

WordPress lưu media dưới dạng attachment. Trường post_parent cho biết attachment được gắn với bài nào khi tải lên, nhưng nó không phải bản đồ đầy đủ về nơi file đang xuất hiện. Tài liệu WordPress cũng ghi rõ: ảnh tải từ Media Library có thể ở trạng thái unattached, sau đó vẫn được chèn và sử dụng trong nội dung.

Một ảnh có thể đang được gọi theo nhiều cách:

  • Ảnh đại diện qua meta _thumbnail_id.
  • URL ảnh nằm trong nội dung bài viết hoặc trang.
  • Gallery của WooCommerce hoặc plugin thư viện ảnh.
  • Logo, favicon, banner hoặc background lưu trong Customizer và options.
  • Elementor, UX Builder, Gutenberg block hoặc shortcode.
  • CSS, JavaScript, email template hoặc dữ liệu bên ngoài WordPress.

Vì vậy, truy vấn chỉ dựa vào post_parent sẽ tạo ra kết quả sai. Nguy hiểm hơn, tham số true trong wp_delete_attachment() buộc WordPress xóa vĩnh viễn file, metadata và các kích thước ảnh liên quan.

Bước 1: Sao lưu đúng file trước khi sửa code

Trong cPanel, mở File Manager và đi theo đường dẫn:

public_html/wp-content/themes/ten-child-theme/functions.php

Chọn functions.php rồi nhấn Download để lưu một bản trên máy. Nếu theme không có child theme, bạn không nên tiếp tục chèn code vào parent theme vì lần cập nhật tiếp theo có thể ghi đè thay đổi.

Ngoài file PHP, hãy sao lưu cả database và thư mục wp-content/uploads. Database chỉ giữ thông tin attachment; ảnh thật nằm trong uploads nên một bản sao database không đủ để khôi phục file đã bị xóa.

Sao lưu file functions.php trước khi sửa code WordPress — NguyenThaiMMO
Giao diện minh họa đường dẫn child theme và thao tác sao lưu functions.php.

Bước 2: Lọc ảnh Chưa đính kèm trong Media Library

  1. Trong WordPress, vào Media → Library.
  2. Chuyển sang chế độ danh sách để nhìn thấy cột “Uploaded to”.
  3. Chọn bộ lọc Unattached/Chưa đính kèm.
  4. Mở từng attachment, sao chép URL và ghi lại ID.
  5. Tìm URL hoặc ID đó trong bài viết, trang, sản phẩm và trình dựng giao diện.

Dấu hiệu hoàn thành bước này không phải là “đã thấy nhiều ảnh unattached”, mà là bạn đã có một danh sách media cần kiểm tra. Chưa xóa gì ở giai đoạn này.

Lọc media chưa đính kèm trong WordPress để rà soát — NguyenThaiMMO
Giao diện minh họa bộ lọc Unattached trong Media Library.

Bước 3: Tạo trang báo cáo media bằng code

Đoạn code dưới đây thêm mục Tools → Rà soát Media. Nó chỉ liệt kê tối đa 100 ảnh chưa đính kèm và kiểm tra hai tín hiệu phổ biến: URL/lớp wp-image-ID trong nội dung và attachment đang được dùng làm ảnh đại diện. Code không tự xóa file.

add_action( 'admin_menu', 'ntmmo_register_media_audit_page' );

function ntmmo_register_media_audit_page() {
    add_management_page(
        'Rà soát Media',
        'Rà soát Media',
        'manage_options',
        'ntmmo-media-audit',
        'ntmmo_render_media_audit_page'
    );
}

function ntmmo_render_media_audit_page() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    global $wpdb;

    $attachment_ids = get_posts(
        array(
            'post_type'      => 'attachment',
            'post_status'    => 'inherit',
            'post_mime_type' => 'image',
            'post_parent'    => 0,
            'posts_per_page' => 100,
            'orderby'        => 'date',
            'order'          => 'ASC',
            'fields'         => 'ids',
        )
    );

    echo '<div class="wrap">';
    echo '<h1>Rà soát Media chưa đính kèm</h1>';
    echo '<p>Danh sách này chỉ để kiểm tra, không phải danh sách có thể xóa ngay.</p>';
    echo '<table class="widefat striped">';
    echo '<thead><tr><th>ID</th><th>Ảnh</th><th>Tín hiệu sử dụng</th><th>Thao tác</th></tr></thead><tbody>';

    foreach ( $attachment_ids as $attachment_id ) {
        $url = wp_get_attachment_url( $attachment_id );
        $class_marker = '%wp-image-' . absint( $attachment_id ) . '%';
        $url_marker   = '%' . $wpdb->esc_like( $url ) . '%';

        $content_post_id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT ID FROM {$wpdb->posts}
                 WHERE post_status NOT IN ('trash', 'auto-draft')
                 AND (post_content LIKE %s OR post_content LIKE %s)
                 LIMIT 1",
                $class_marker,
                $url_marker
            )
        );

        $thumbnail_post_id = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT post_id FROM {$wpdb->postmeta}
                 WHERE meta_key = '_thumbnail_id'
                 AND meta_value = %d
                 LIMIT 1",
                $attachment_id
            )
        );

        $signals = array();
        if ( $content_post_id ) {
            $signals[] = 'Có trong nội dung bài #' . absint( $content_post_id );
        }
        if ( $thumbnail_post_id ) {
            $signals[] = 'Đang là ảnh đại diện bài #' . absint( $thumbnail_post_id );
        }
        if ( empty( $signals ) ) {
            $signals[] = 'Chưa thấy trong 2 phép kiểm tra cơ bản';
        }

        echo '<tr>';
        echo '<td>' . absint( $attachment_id ) . '</td>';
        echo '<td>' . wp_get_attachment_image( $attachment_id, array( 80, 80 ) ) . '</td>';
        echo '<td>' . esc_html( implode( '; ', $signals ) ) . '</td>';
        echo '<td><a href="' . esc_url( get_edit_post_link( $attachment_id ) ) . '">Mở attachment</a></td>';
        echo '</tr>';
    }

    echo '</tbody></table></div>';
}

Chèn code và lưu ở đâu?

  1. Mở file functions.php của child theme.
  2. Dán code ở cuối file; không thêm thẻ <?php lần thứ hai nếu file đã có.
  3. Nhấn Save Changes.
  4. Đăng nhập lại WordPress, vào Tools → Rà soát Media.

Nếu website báo lỗi sau khi lưu, tải lại bản functions.php đã sao lưu. Nếu trang báo cáo xuất hiện và không có nút xóa hàng loạt, bạn đã cài đúng.

Dùng code tạo báo cáo media cần kiểm tra thay vì tự động xóa — NguyenThaiMMO
Giao diện minh họa trang Tools dùng để rà soát media, không tự động xóa.

Bước 4: Kiểm tra từng ảnh trước khi xóa

Với mỗi ảnh chưa có tín hiệu sử dụng, hãy kiểm tra thêm:

  • Ảnh có nằm trong Elementor, UX Builder hoặc block tái sử dụng không?
  • Ảnh có phải logo, favicon hoặc banner được lưu trong theme options không?
  • Ảnh có nằm trong gallery sản phẩm WooCommerce không?
  • URL ảnh có xuất hiện trong CSS, email template hoặc popup không?
  • File có được một website hoặc landing page khác gọi trực tiếp không?

Code báo cáo chỉ kiểm tra hai tín hiệu cơ bản nên kết quả “chưa thấy” không phải bằng chứng chắc chắn rằng ảnh không được dùng. Đây là lý do mình không đặt nút xóa tự động vào trang Tools.

Checklist kiểm tra trước khi xóa ảnh WordPress — NguyenThaiMMO
Giao diện minh họa các điểm cần kiểm tra trước khi xóa ảnh WordPress.

Bước 5: Xóa theo lô nhỏ và kiểm tra website

Sau khi xác nhận một ảnh không còn được sử dụng, mở attachment từ báo cáo rồi xóa trong Media Library. Chỉ nên xử lý một lô nhỏ, ví dụ 5–10 ảnh, sau đó kiểm tra:

  1. Trang chủ và menu.
  2. Các bài viết liên quan.
  3. Trang sản phẩm, giỏ hàng và thanh toán nếu là website bán hàng.
  4. Header, footer, popup và phiên bản mobile.
  5. Console trình duyệt và log 404 nếu website có công cụ theo dõi.

Nếu phát hiện ảnh hỏng, khôi phục file từ backup rồi đưa lại vào đúng đường dẫn. Đừng tiếp tục xóa lô tiếp theo khi chưa tìm ra nguyên nhân.

Có nên tự động xóa media khi xóa bài viết không?

Mình không khuyến nghị dùng hook before_delete_post để xóa toàn bộ attachment của bài. Một ảnh có thể được tải lên từ bài A nhưng sau đó tái sử dụng trong bài B, landing page hoặc sản phẩm. Xóa theo quan hệ parent sẽ làm bài B mất ảnh dù bài B vẫn còn hoạt động.

Theo tài liệu WordPress, before_delete_post chạy khi bài bị xóa vĩnh viễn, không chạy lúc bài chỉ được chuyển vào thùng rác. Đây là hook phù hợp để thực hiện logic liên quan trước khi xóa bài, nhưng không tự chứng minh rằng attachment con không còn được dùng ở nơi khác.

Ai nên và không nên áp dụng?

Phù hợp: website nhỏ hoặc vừa, có backup đầy đủ và người quản trị có thể kiểm tra thủ công từng media.

Không phù hợp: website thương mại điện tử lớn, multisite, website dùng page builder phức tạp hoặc có nhiều hệ thống gọi ảnh ngoài nội dung WordPress. Với các trường hợp này, nên quét staging và log URL 404 trước khi xóa production.

Câu hỏi thường gặp

Ảnh “Chưa đính kèm” có xóa được ngay không?

Không. Trạng thái này chỉ nói attachment không có post parent; ảnh vẫn có thể đang được dùng ở ảnh đại diện, widget, theme options hoặc page builder.

wp_delete_attachment với tham số true có tác dụng gì?

Tham số true buộc WordPress xóa attachment vĩnh viễn thay vì chuyển vào thùng rác, đồng thời xóa file và dữ liệu liên quan.

Code trong bài có tự động xóa ảnh không?

Không. Code chỉ tạo trang báo cáo trong Tools và đưa ra một số tín hiệu để bạn kiểm tra thủ công.

Xóa code báo cáo như thế nào?

Mở functions.php của child theme, xóa từ dòng add_action( 'admin_menu'... đến hết hàm ntmmo_render_media_audit_page(), lưu file rồi kiểm tra menu Tools.

Nguồn tham khảo

Nếu bạn muốn dọn Media Library, hãy bắt đầu bằng việc gỡ mọi hook xóa tự động, tạo backup và chạy trang báo cáo. Chỉ khi đã xác nhận một ảnh không còn xuất hiện ở bất kỳ vị trí nào, bạn mới xóa ảnh đó trong Media Library.

Đánh giá bài viết

5/5 - (58 votes)

Đánh giá độ hữu ích của bài viết

Tag Website:

0 GÓP Ý

Đang tải bình luận...
Messenger Zalo YouTube Bình luận