Skip to content

Alt om WordPress, webutvikling — og mer til

🗑 Programmatisk opprydding av WordPress mediebibliotek: PHP-skript for å fjerne søppelfiler

🗑 Programmatisk opprydding av WordPress mediebibliotek: PHP-skript for å fjerne søppelfiler

WordPress mediebibliotek fungerer som et loft: du sletter et innlegg, bildene blir værende. Bytt tema, gamle bildestørrelser ligger der som dødvekt. Migrer nettstedet, halvparten av miniatyrbildene returnerer 404.

Manuell opprydding via administrasjonspanelet på et nettsted med et par tusen filer er en øvelse i meditasjon. Men det finnes en raskere måte: fem PHP-funksjoner som finner og fjerner søppel i én enkelt gjennomkjøring. Ingen utvidelser, med klar kode og kontroll over hver slettet fil.

Før du kjører: full sikkerhetskopi. Disse funksjonene sletter filer permanent: ingen papirkurv, ingen angrefunksjon. Er du i tvil, kjør på en staging-kopi først.

💡 Rask oversikt:

  • Slette uforankrede vedlegg, filer som blir liggende igjen etter sletting av innlegg
  • Rydde opp mediefiler fra en spesifikk egendefinert innholdstype (CPT)
  • Tømme mediebiblioteket for ødelagte lenker, 404-vedlegg uten fil på serveren
  • Finne og slette filer i wp-content/uploads som ikke er registrert som WordPress-vedlegg
  • Scenario for nettsteder der bilder lagres i egendefinerte felt (ACF, Meta Box), ikke som vedlegg

Hva du trenger å vite før du kjører

Koden nedenfor sletter filer fysisk, fra disk og database. Tre ting som vil redde nettstedet ditt.

For det første: bilder på tag-arkivsider eller i SEO-beskrivelser er ofte ikke knyttet til noe innlegg. De henger som «foreldreløse», men nettstedet trenger dem. Har du slike bilder, ekskluder dem fra omfanget av disse funksjonene eller juster betingelsene.

For det andre: WordPress oppretter flere størrelser av hvert bilde. Miniatyrbilder arver post_parent fra originalen, så funksjonen delete_unattached_attachments() rører dem ikke, den filtrerer strengt på post_parent = 0. Problemet oppstår bare hvis originalen selv mistet sin tilknytning til innlegget.

For det tredje: hvis en lenke til den slettede filen finnes i innholdsinnlegg, vil den bli ødelagt etter opprydding. Før du kjører, gjennomsøk nettstedet med Screaming Frog eller lignende og kartlegg lenkene.

1. Slette uforankrede vedlegg

Vanligste scenario: du slettet et innlegg, vedlegg ble værende i databasen med post_type = 'attachment' og post_parent = 0. De tar opp plass på disk og i sikkerhetskopier.

Funksjonen nedenfor finner alle slike poster og sletter dem. Plasser den i functions.php i et child theme eller via en kodesnuttutvidelse som WPCode. Den kjører ikke av seg selv, det er en definisjon som trenger et kall.

1function delete_unattached_attachments() {
2 $attachments = get_posts( array(
3 'post_type' => 'attachment',
4 'numberposts' => -1,
5 'fields' => 'ids',
6 'post_parent' => 0,
7 ) );
8
9 if ( $attachments ) {
10 foreach ( $attachments as $attachment_id ) {
11 $attachment_path = get_attached_file( $attachment_id );
12 wp_delete_attachment( $attachment_id, true );
13 unlink( $attachment_path );
14 }
15 }
16}

get_posts() velger alle vedlegg uten et overordnet innlegg. wp_delete_attachment() med parameteren true sletter både databaseposten og filen med miniatyrbilder. Ekstra unlink() er en forsikring: hvis filen av en eller annen grunn ble liggende igjen på disk, slettes den tvangsmessig.

Merk: utvalgte bilder har også post_parent = 0 i noen konfigurasjoner. Før produksjonskjøring, erstatt wp_delete_attachment med echo $attachment_id . '<br>', så ser du listen over ID-er som vil bli slettet. Når du har bekreftet at alt er riktig, gå tilbake til produksjonsversjonen.

Etter en enkelt kjøring, fjern funksjonen fra functions.php. Ingen grunn til å ha den liggende på hver init.

2. Slette vedlegg fra en spesifikk CPT

Tidligere WooCommerce-butikk, gammel porteføljedel, slettet egendefinert innholdstype, alle bildene deres fortsetter å ligge på serveren. Funksjonen nedenfor rydder opp vedlegg knyttet til innlegg av en spesifisert type.

1function delete_cpt_attachments( $cpt = 'card' ) {
2 $attachments = get_posts( array(
3 'post_type' => 'attachment',
4 'numberposts' => -1,
5 ) );
6
7 if ( $attachments ) {
8 foreach ( $attachments as $attachment ) {
9 $parent_id = $attachment->post_parent;
10
11 if ( $cpt === get_post_type( $parent_id ) ) {
12 $attachment_path = get_attached_file( $attachment->ID );
13 wp_delete_attachment( $attachment->ID, true );
14 unlink( $attachment_path );
15 }
16 }
17 }
18}

Erstatt 'card' med din CPT-slug. For WooCommerce-produkter, 'product'. Hvis CPT-en allerede er slettet, vil get_post_type() returnere false, vedlegg av den typen vil ikke bli berørt. For slettede CPT-er må logikken justeres: sjekk ikke overordnet type, men medlemskap i en taksonomi eller et metafelt.

På store databaser, vær forsiktig: 'numberposts' => -1 uten 'fields' => 'ids' laster fulle WP_Post-objekter. På 10 000+ vedlegg kan dette treffe memory_limit. For produksjonsvolumer, legg til 'fields' => 'ids' og hent bare ID-er, get_post_type() vil også fungere med overordnede ID-er.

3. Tømme mediebiblioteket for 404-vedlegg

Ødelagte miniatyrbilder i mediebiblioteket er et symptom på at filen på disk ble slettet (manuelt, ved hostingkrasj eller en buggete utvidelse), men databaseposten ble værende. WordPress viser et grått rektangel, men ved klikk, 404.

Funksjonen spør URL-en til hvert vedlegg og sletter de som returnerer 404.

1function delete_404_attachments() {
2 $attachments = get_posts( array(
3 'post_type' => 'attachment',
4 'numberposts' => -1,
5 'fields' => 'ids',
6 ) );
7
8 if ( $attachments ) {
9 foreach ( $attachments as $attachment_id ) {
10 $file_url = wp_get_attachment_url( $attachment_id );
11 $file_headers = @get_headers( $file_url );
12
13 if ( $file_headers && strpos( $file_headers[0], '404' ) !== false ) {
14 wp_delete_attachment( $attachment_id, true );
15 }
16 }
17 }
18}

Viktig: denne funksjonen er ressurskrevende. Hvert get_headers()-kall er en HTTP-forespørsel til din egen server. På tusen vedlegg gjør du tusen HTTP-forespørsler i én gjennomkjøring. Resultat: tregt, serverbelastning, noen hostingleverandører dreper prosessen ved tidsavbrudd.

For store mediebiblioteker, del opp i biter med 'offset' og 'numberposts' eller kjør via WP-CLI med en batch-grense. Hvis nettstedet ligger bak CDN eller proxy, erstatt sjekken med wp_remote_head() med en tidsavbrudd, get_headers() håndterer ikke alltid videresendinger korrekt og støtter ikke autentisering.

4. Omvendt sjekk: filer i uploads uten databasepost

De tre foregående funksjonene rydder databasen, sletter vedleggsposter. Men wp-content/uploads kan inneholde filer som ikke er registrert som vedlegg i det hele tatt: lastet opp via FTP, etterlatt av utvidelser, generert av cache.

Denne funksjonen går motsatt vei: ikke fra database til filer, men fra filer til database. Skanner wp-content/uploads rekursivt og sjekker for hver fil via attachment_url_to_postid() om den er et vedlegg. Hvis ikke, sletter den den.

1function clean_uploads_from_nonattachments() {
2 $uploads_dir = wp_upload_dir();
3 $search = $uploads_dir['basedir'];
4 $replace = $uploads_dir['baseurl'];
5 $root = $uploads_dir['basedir'];
6
7 $iter = new RecursiveIteratorIterator(
8 new RecursiveDirectoryIterator( $root, RecursiveDirectoryIterator::SKIP_DOTS ),
9 RecursiveIteratorIterator::SELF_FIRST,
10 RecursiveIteratorIterator::CATCH_GET_CHILD
11 );
12
13 foreach ( $iter as $fileinfo ) {
14 if ( $fileinfo->isFile() ) {
15 $image_path = $fileinfo->getPathname();
16 $image_url = str_replace( $search, $replace, $image_path );
17 $attachment_id = attachment_url_to_postid( $image_url );
18
19 if ( ! $attachment_id ) {
20 unlink( $image_path );
21 }
22 }
23 }
24}

På en testserver med 1 GB opplastinger kjørte funksjonen på 15 sekunder og frigjorde 700 MB, og etterlot 300 MB med faktisk brukte filer. For mapper større enn 5 GB, bryt skanningen etter år: erstatt $root med $uploads_dir['basedir'] . '/2025/', deretter '/2024/' og så videre.

Kjør først versjonen uten sletting, erstatt unlink( $image_path ) med echo $image_path . PHP_EOL. Du vil se hele listen over filer funksjonen anser som søppel. Sjekk visuelt, og gå deretter tilbake til unlink().

5. Scenario med egendefinerte felt: når bilder ikke er vedlegg

Det mest komplekse tilfellet: et nettsted der bilder lagres ikke som WordPress-vedlegg, men som URL-er i egendefinerte felt (ACF, Meta Box, egendefinerte temafelt). Typisk eksempel, en bokhandel: bokomslag i feltet bookcover, forfatterbilde i bookauthor_picture, listebilde i book_list_pictrue.

I denne arkitekturen vil attachment_url_to_postid() returnere 0 for alle filer i uploads. Den forrige funksjonen vil slette alt, inkludert faktisk brukte bilder. En annen tilnærming er nødvendig.

5.1. Bygge en hviteliste

Samle først URL-er for alle bilder fra alle nødvendige egendefinerte felt. I eksemplet nedenfor, tre CPT-er og tre felt:

1$all_good_pictures = array();
2
3// Book covers (CPT 'post', field 'bookcover')
4$posts = get_posts( array(
5 'post_type' => 'post',
6 'posts_per_page' => -1,
7 'post_status' => 'any',
8 'fields' => 'ids',
9) );
10foreach ( $posts as $post_id ) {
11 $cover = get_field( 'bookcover', $post_id );
12 if ( $cover ) {
13 $all_good_pictures[] = $cover;
14 }
15}
16
17// List images (CPT 'book_list', field 'book_list_pictrue')
18$lists = get_posts( array(
19 'post_type' => 'book_list',
20 'posts_per_page' => -1,
21 'post_status' => 'any',
22 'fields' => 'ids',
23) );
24foreach ( $lists as $list_id ) {
25 $pic = get_field( 'book_list_pictrue', $list_id );
26 if ( $pic ) {
27 $all_good_pictures[] = $pic;
28 }
29}
30
31// Author photos (CPT 'bookauthor', field 'bookauthor_picture')
32$authors = get_posts( array(
33 'post_type' => 'bookauthor',
34 'posts_per_page' => -1,
35 'post_status' => 'any',
36 'fields' => 'ids',
37) );
38foreach ( $authors as $author_id ) {
39 $pic = get_field( 'bookauthor_picture', $author_id );
40 if ( $pic ) {
41 $all_good_pictures[] = $pic;
42 }
43}
44
45$all_good_pictures = array_filter( $all_good_pictures );

På et reelt prosjekt, en bokhandel, gjorde denne tilnærmingen det mulig å beregne de fleste søppelfiler og frigjøre en betydelig del av diskplassen.

5.2. Slett alt som ikke er i hvitelisten

Gå nå gjennom wp-content/uploads og slett hver fil som ikke er i $all_good_pictures:

1$uploads_dir = wp_upload_dir();
2$search = $uploads_dir['basedir'];
3$replace = $uploads_dir['baseurl'];
4$root = $uploads_dir['basedir'];
5
6$iter = new RecursiveIteratorIterator(
7 new RecursiveDirectoryIterator( $root, RecursiveDirectoryIterator::SKIP_DOTS ),
8 RecursiveIteratorIterator::SELF_FIRST,
9 RecursiveIteratorIterator::CATCH_GET_CHILD
10);
11
12foreach ( $iter as $fileinfo ) {
13 if ( $fileinfo->isFile() ) {
14 $image_path = $fileinfo->getPathname();
15 $image_url = str_replace( $search, $replace, $image_path );
16
17 if ( ! in_array( $image_url, $all_good_pictures, true ) ) {
18 unlink( $image_path );
19 }
20 }
21}

in_array()-metoden med streng sammenligning på en matrise med 10 000+ elementer er ikke den raskeste. For produksjonsvolumer, erstatt den vanlige matrisen med en assosiativ en: $all_good_pictures = array_fill_keys( $all_good_pictures, true ) og sjekk via isset(). Forskjellen på 40 000 elementer, fra titalls sekunder til brøkdeler av et sekund.

Hvordan kjøre disse funksjonene

Alle kodesnuttene over er funksjonsdefinisjoner. De gjør ingenting før du kaller dem. Tre trygge måter å kjøre på:

Metode

Når du skal bruke

Tilbakerulling

WP-CLI wp eval-file

Engangsopprydding, konsolltilgang

Nei, kun sikkerhetskopi

Hook admin_init + URL-parameter

Ingen konsoll, trenger å kjøre fra admin

Nei, kun sikkerhetskopi

Kodesnuttutvidelse (WPCode)

Praktisk lagring og aktiver/deaktiver

Deaktiver kodesnutt, funksjon inaktiv

Eksempel på engangskjøring via admin:

1add_action( 'admin_init', 'run_cleanup_once' );
2function run_cleanup_once() {
3 if ( isset( $_GET['cleanup'] ) && 'confirmed' === $_GET['cleanup'] ) {
4 delete_unattached_attachments();
5 }
6}

Besøk https://yoursite.com/wp-admin/?cleanup=confirmed, funksjonen kjører én gang. Etter utførelse, fjern kodesnutten.

For WP-CLI, den anbefalte metoden for produksjon, lagre funksjonskoden til en midlertidig fil og kjør:

1wp eval-file cleanup.php

Før opprydding er det nyttig å se prosessen visuelt. I videoen nedenfor, en trinnvis gjennomgang av opprydding av WordPress-mediebiblioteket med manuelle og automatiske metoder.

⁉️🤔 Ofte stilte spørsmål

Kan filer gjenopprettes etter sletting?

Nei. Funksjoner bruker wp_delete_attachment() med true og unlink(), filer slettes fysisk, utenom papirkurven. Eneste forsikring: full sikkerhetskopi av filer og database før kjøring. Sjekk om hostingleverandøren din har automatiske daglige sikkerhetskopier, hos Kinsta, WP Engine og SiteGround er de aktivert som standard. Dette gir et ekstra gjenopprettingspunkt i tillegg til din manuelle sikkerhetskopi.

Hvorfor virket ikke funksjonen, filene ble liggende?

Vanligste årsak: du la til funksjonsdefinisjonen i functions.php, men kalte den ikke. En function ... { }-blokk er bare en instruksjon. For at koden skal utføres, må funksjonen kobles til en hook via add_action() eller kjøres manuelt via WP-CLI. I «Hvordan kjøre»-delen, tre metoder, velg basert på ditt servertilgangsnivå.

Vil funksjonen slette miniatyrbilder av brukte bilder hvis de er uforankrede vedlegg?

Nei. Miniatyrbilder (thumbnail, medium, large) har samme post_parent som det opprinnelige vedlegget. Funksjonen filtrerer strengt på post_parent = 0, bare poster uten noe overordnet innlegg i det hele tatt. Størrelser av originaler arver post_parent og havner ikke i utvalget. Problemet oppstår bare hvis originalen selv mistet sitt vedlegg, da vil funksjonen slette den sammen med alle størrelser.

Hva om noen bilder er i egendefinerte felt og noen er vanlige vedlegg?

Kombiner tilnærminger fra seksjon 4 og 5. Samle først en hviteliste fra egendefinerte felt (seksjon 5.1). Så når du skanner uploads (seksjon 4) for hver fil, sjekk begge betingelsene: er filen et WordPress-vedlegg via attachment_url_to_postid() OG er den til stede i hvitelisten. En fil slettes bare hvis ingen av betingelsene er oppfylt: if ( ! $attachment_id && ! isset( $good_pictures[ $image_url ] ) ) { unlink( $image_path ); }.

Hvor trygt er dette for et WooCommerce-nettsted?

WooCommerce lagrer produktbilder som standard WordPress-vedlegg, de er knyttet til innholdstypen product. Slettefunksjonen for uforankrede vedlegg (seksjon 1) vil ikke røre dem. Men funksjonen for en spesifikk CPT (seksjon 2), jo, hvis du sender inn 'product'. For WooCommerce er det tryggeste metoden fra seksjon 5 (hviteliste): den opererer på hva som faktisk er i bruk, ikke hva som er knyttet. Før kjøring, eksporter ID-er for alle produktvedlegg for kryssjekking.

Hva du skal bruke på ditt prosjekt: endelig oversikt

Metodevalg avhenger av nettstedets arkitektur:

  • Standard blogg eller nyhetsnettsted, funksjoner fra seksjon 1 (uforankrede vedlegg) og seksjon 3 (404-vedlegg) er nok. Kjør en gang hver sjette måned, mediebiblioteket vil være i orden.
  • Nettsted med gamle CPT-er (portefølje, katalog, rubrikkannonser), legg til seksjon 2. Rydd presist opp søppel fra slettede eller forlatte innholdstyper.
  • Prosjekt på ACF/Meta Box med egendefinerte felt for bilder, ditt valg: seksjon 5. Samle en hviteliste, slett resten. Sett opp én gang, og gjenta deretter etter behov.
  • Alt sammen og uklart, start med rekursiv skanning av uploads (seksjon 4). Se hvor mye søppel som ligger på disk. Bruk deretter seksjon 1-3 og 5 selektivt etter situasjon.

Ingen av disse skriptene erstatter regelmessig netthygiene. Men når du først har skrevet den nødvendige funksjonen og lagret den i prosjektdokumentasjonen, vil du spare timer med manuelt arbeid ved neste revisjon.

Og ja, du har allerede tatt en sikkerhetskopi.

Opprydding av WordPress mediebibliotek for søppelfiler