Skip to content

Kaikki WordPressistä, web-kehityksestä — ja paljon muuta

🗑 WordPress-medialkirjaston ohjelmallinen siivous: PHP-skriptejä roskatiedostojen poistoon

🗑 WordPress-medialkirjaston ohjelmallinen siivous: PHP-skriptejä roskatiedostojen poistoon

WordPressin mediakirjasto toimii kuin vintti: poistat artikkelin, kuvat jäävät. Vaihdat teemaa, vanhat kuvakoot lojuvat siellä kuolleena painona. Siirrät sivuston, puolet pikkukuvista palauttaa 404:n.

Manuaalinen siivous hallintapaneelin kautta sivustolla, jolla on pari tuhatta tiedostoa, on meditaatioharjoitus. Mutta on nopeampikin tapa: viisi PHP-funktiota, jotka etsivät ja poistavat roskan yhdellä ajolla. Ei lisäosia, selkeää koodia ja täysi hallinta jokaisesta poistetusta tiedostosta.

Ennen ajamista ota täysi varmuuskopio. Nämä funktiot poistavat tiedostot pysyvästi: ei roskakoria, ei kumoa-nappia. Jos epäröit, aja ensin staging-kopiolla.

💡 Pikakatsaus:

  • Kiinnittymättömien liitetiedostojen poisto, artikkelin poiston jälkeen jääneet tiedostot
  • Tietyn custom post typen (CPT) mediatiedostojen siivous
  • Mediakiirjaston puhdistus rikkinäisistä linkeistä, 404-liitetiedostoista ilman tiedostoa palvelimella
  • Sellaisten tiedostojen etsintä ja poisto hakemistosta wp-content/uploads, joita ei ole rekisteröity WordPress-liitetiedostoiksi
  • Skenaario sivustoille, joissa kuvat tallennetaan omiin kenttiin (ACF, Meta Box), ei liitetiedostoina

Mitä sinun tulee tietää ennen ajamista

Alla oleva koodi poistaa tiedostot fyysisesti, levyltä ja tietokannasta. Kolme asiaa, jotka pelastavat sivustosi.

Ensinnäkin: tunnistearkistosivuilla tai SEO-kuvauksissa olevia kuvia ei usein ole liitetty mihinkään artikkeliin. Ne roikkuvat "orpoina", mutta sivusto tarvitsee niitä. Jos sinulla on tällaisia kuvia, sulje ne pois näiden funktioiden piiristä tai säädä ehtoja.

Toiseksi: WordPress luo jokaisesta kuvasta useita kokoja. Pikkukuvat perivät alkuperäisen kuvan post_parent-arvon, joten delete_unattached_attachments()-funktio ei koske niihin, se suodattaa tiukasti ehdolla post_parent = 0. Ongelma syntyy vain, jos alkuperäinen kuva itse on menettänyt liitoksensa artikkeliin.

Kolmanneksi: jos artikkelin sisällössä on linkki poistettuun tiedostoon, se rikkoutuu siivouksen jälkeen. Ennen ajamista käy sivusto läpi Screaming Frogilla tai vastaavalla ja kartoita linkit.

1. Kiinnittymättömien liitetiedostojen poistaminen

Yleisin skenaario: poistit artikkelin, liitetiedostot jäivät tietokantaan tyypillä post_type = 'attachment' ja post_parent = 0. Ne vievät tilaa levyllä ja varmuuskopioissa.

Alla oleva funktio etsii kaikki tällaiset tietueet ja poistaa ne. Sijoita se child-teeman functions.php-tiedostoon tai koodinpätkälisäosan, kuten WPCode, kautta. Se ei ajaudu itsestään, se on määritelmä, joka tarvitsee kutsun.

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() valitsee kaikki liitetiedostot, joilla ei ole ylätason artikkelia. wp_delete_attachment() true-parametrilla poistaa sekä tietokantatietueen että tiedoston pikkukuvineen. Ylimääräinen unlink() on varmistus: jos tiedosto jostain syystä jäi levylle, se poistetaan väkisin.

Huom: artikkelikuvilla on myös post_parent = 0 joissain konfiguraatioissa. Ennen tuotantoajoa korvaa wp_delete_attachment komennolla echo $attachment_id . '<br>', näet listan poistettavista ID:istä. Kun olet varmistanut, että kaikki on oikein, palauta tuotantoversio.

Yhden ajon jälkeen poista funktio functions.php-tiedostosta. Sitä ei tarvitse pitää jokaisella init-kerralla.

2. Tietyn CPT:n liitetiedostojen poistaminen

Entinen WooCommerce-kauppa, vanha portfoli-osio, poistettu custom post type, kaikki niiden kuvat jatkavat olemistaan palvelimella. Alla oleva funktio siivoaa tiettyyn tyyppiin kuuluviin artikkeleihin liitetyt liitetiedostot.

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}

Korvaa 'card' oman CPT:si slugilla. WooCommerce-tuotteille 'product'. Jos CPT on jo poistettu, get_post_type() palauttaa false, jolloin kyseisen tyypin liitetiedostoihin ei kosketa. Poistettujen CPT:iden kohdalla logiikka vaatii säädön: älä tarkista ylätason tyyppiä, vaan kuuluminen taksonomiaan tai metakenttään.

Suurissa tietokannoissa ole varovainen: 'numberposts' => -1 ilman 'fields' => 'ids'-määritystä lataa täydet WP_Post-objektit. Yli 10 000 liitetiedostolla tämä voi osua memory_limit-rajaan. Tuotantomäärille lisää 'fields' => 'ids' ja hae vain ID:t, get_post_type() toimii myös ylätason ID:illä.

3. Mediakiirjaston puhdistus 404-liitetiedostoista

Rikkinäiset pikkukuvat mediakiirjastossa ovat oire siitä, että tiedosto levyllä on poistettu (manuaalisesti, hosting-palvelun kaatumisen tai bugisen lisäosan toimesta), mutta tietokantatietue jäi. WordPress näyttää harmaan suorakulmion, mutta klikatessa tulee 404.

Funktio kysyy jokaisen liitetiedoston URL:n ja poistaa ne, jotka palauttavat 404:n.

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}

Tärkeää: tämä funktio on raskas. Jokainen get_headers()-kutsu on HTTP-pyyntö omalle palvelimellesi. Tuhannella liitetiedostolla teet tuhat HTTP-pyyntöä yhdellä ajolla. Seuraus: hidas, palvelimen kuormitus, jotkut hosting-palveluntarjoajat katkaisevat prosessin aikakatkaisun vuoksi.

Suurille mediakiirjastoille pilko osiin käyttäen 'offset'- ja 'numberposts'-parametreja tai aja WP-CLI:n kautta erärajalla. Jos sivusto on CDN:n tai välityspalvelimen takana, korvaa tarkistus funktiolla wp_remote_head() aikakatkaisulla, get_headers() ei aina käsittele uudelleenohjauksia oikein eikä tue autentikointia.

4. Käänteinen tarkistus: tiedostot uploadsissa ilman tietokantatietuetta

Kolme edellistä funktiota siivoavat tietokantaa, poistavat liitetiedostojen tietueita. Mutta wp-content/uploads saattaa sisältää tiedostoja, joita ei ole rekisteröity liitetiedostoiksi lainkaan: ladattu FTP:llä, lisäosien jättämiä, välimuistin tuottamia.

Tämä funktio kulkee päinvastaiseen suuntaan: ei tietokannasta tiedostoihin, vaan tiedostoista tietokantaan. Se skannaa rekursiivisesti wp-content/uploads-hakemiston ja tarkistaa jokaisen tiedoston kohdalla funktiolla attachment_url_to_postid(), onko se liitetiedosto. Jos ei ole, se poistaa sen.

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}

Testipalvelimella, jossa oli 1 Gt uploads-tiedostoja, funktio ajautui 15 sekunnissa ja vapautti 700 Mt, jättäen 300 Mt tosiasiallisesti käytössä olevia tiedostoja. Yli 5 Gt:n kansioille pilko skannaus vuosittain: korvaa $root polulla $uploads_dir['basedir'] . '/2025/', sitten '/2024/' ja niin edelleen.

Aja ensin versio ilman poistoa, korvaa unlink( $image_path ) komennolla echo $image_path . PHP_EOL. Näet täyden listan tiedostoista, joita funktio pitää roskana. Tarkista visuaalisesti ja palauta sitten unlink().

5. Skenaario omilla kentillä: kun kuvat eivät ole liitetiedostoja

Monimutkaisin tapaus: sivusto, jossa kuvia ei tallenneta WordPress-liitetiedostoina, vaan URL-osoitteina omiin kenttiin (ACF, Meta Box, teeman omat kentät). Tyypillinen esimerkki, kirjakauppa: kirjan kansi kentässä bookcover, kirjailijan kuva kentässä bookauthor_picture, listakuva kentässä book_list_pictrue.

Tässä arkkitehtuurissa attachment_url_to_postid() palauttaa 0 kaikille uploads-tiedostoille. Edellinen funktio poistaa kaiken, mukaan lukien tosiasiallisesti käytössä olevat kuvat. Tarvitaan erilainen lähestymistapa.

5.1. Sallittujen listan rakentaminen

Kerää ensin kaikkien tarvittavien omien kenttien kuvien URL-osoitteet. Alla olevassa esimerkissä kolme CPT:tä ja kolme kenttää:

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 );

Oikeassa projektissa, kirjakaupassa, tämä lähestymistapa mahdollisti suurimman osan roskatiedostoista laskemisen ja merkittävän osan levytilasta vapauttamisen.

5.2. Poista kaikki, mikä ei ole sallittujen listalla

Käy nyt läpi wp-content/uploads ja poista jokainen tiedosto, joka ei ole $all_good_pictures-listalla:

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()-metodi tiukalla vertailulla yli 10 000 alkion taulukossa ei ole nopein. Tuotantomäärille korvaa tavallinen taulukko assosiatiivisella: $all_good_pictures = array_fill_keys( $all_good_pictures, true ) ja tarkista isset()-funktiolla. Ero 40 000 alkiolla on kymmenistä sekunneista sekunnin murto-osaan.

Kuinka näitä funktioita ajetaan

Kaikki yllä olevat koodinpätkät ovat funktioiden määritelmiä. Ne eivät tee mitään ennen kuin kutsut niitä. Kolme turvallista tapaa ajaa:

Menetelmä

Milloin käyttää

Peruutus

WP-CLI wp eval-file

Kertaluonteinen siivous, konsoliyhteys

Ei, vain varmuuskopio

Koukku admin_init + URL-parametri

Ei konsolia, täytyy ajaa hallintapaneelista

Ei, vain varmuuskopio

Koodinpätkälisäosa (WPCode)

Kätevä säilytys ja käyttöönotto/poiskytkentä

Poista koodinpätkä käytöstä, funktio ei ole aktiivinen

Esimerkki kertaluonteisesta ajosta hallintapaneelin kautta:

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}

Vieraile osoitteessa https://yoursite.com/wp-admin/?cleanup=confirmed, funktio ajetaan kerran. Suorituksen jälkeen poista koodinpätkä.

WP-CLI:lle, suositeltu menetelmä tuotantoon, tallenna funktion koodi väliaikaiseen tiedostoon ja aja:

1wp eval-file cleanup.php

Ennen siivousta on hyödyllistä nähdä prosessi visuaalisesti. Alla olevalla videolla vaiheittainen erittely WordPress-mediakiirjaston siivouksesta manuaalisin ja automaattisin menetelmin.

⁉️🤔 Usein kysytyt kysymykset

Voiko tiedostoja palauttaa poiston jälkeen?

Ei. Funktiot käyttävät wp_delete_attachment()-funktiota true-parametrilla ja unlink()-funktiota, tiedostot poistetaan fyysisesti, roskakorin ohi. Ainoa vakuutus: täysi varmuuskopio tiedostoista ja tietokannasta ennen ajamista. Tarkista, onko hosting-palveluntarjoajallasi automaattiset päivittäiset varmuuskopiot, Kinstassa, WP Enginessä ja SiteGroundissa ne ovat oletuksena päällä. Tämä antaa ylimääräisen palautuspisteen manuaalisen varmuuskopiosi lisäksi.

Miksi funktio ei toiminut, tiedostot jäivät paikoilleen?

Yleisin syy: lisäsit funktion määritelmän functions.php-tiedostoon, mutta et kutsunut sitä. function ... { } -lohko on vain ohje. Jotta koodi suoritetaan, funktio täytyy kiinnittää koukkuun add_action()-funktiolla tai ajaa manuaalisesti WP-CLI:n kautta. "Kuinka ajaa" -osiossa on kolme menetelmää, valitse palvelimen käyttöoikeustasosi mukaan.

Poistaako funktio käytössä olevien kuvien pikkukuvat, jos ne ovat kiinnittymättömiä liitetiedostoja?

Ei. Pikkukuvilla (thumbnail, medium, large) on sama post_parent kuin alkuperäisellä liitetiedostolla. Funktio suodattaa tiukasti ehdolla post_parent = 0, vain tietueet, joilla ei ole lainkaan ylätason artikkelia. Alkuperäisten kuvien koot perivät post_parent-arvon eivätkä päädy valintaan. Ongelma syntyy vain, jos alkuperäinen kuva itse on menettänyt liitoksensa, jolloin funktio poistaa sen kaikkine kokoineen.

Entä jos jotkut kuvat ovat omissa kentissä ja jotkut tavallisia liitetiedostoja?

Yhdistä osioiden 4 ja 5 lähestymistavat. Kerää ensin sallittujen lista omista kentistä (osio 5.1). Sitten, kun skannaat uploads-hakemistoa (osio 4), tarkista jokaisen tiedoston kohdalla molemmat ehdot: onko tiedosto WordPress-liitetiedosto attachment_url_to_postid()-funktion mukaan JA onko se sallittujen listalla. Tiedosto poistetaan vain, jos kumpikaan ehto ei täyty: if ( ! $attachment_id && ! isset( $good_pictures[ $image_url ] ) ) { unlink( $image_path ); }.

Kuinka turvallista tämä on WooCommerce-sivustolle?

WooCommerce tallentaa tuotekuvat tavallisina WordPress-liitetiedostoina, ne on liitetty artikkelityyppiin product. Kiinnittymättömien liitetiedostojen poistofunktio (osio 1) ei koske niihin. Mutta tietyn CPT:n funktio (osio 2), kyllä, jos annat sille 'product'. WooCommercelle turvallisin on osion 5 menetelmä (sallittujen lista): se perustuu siihen, mikä on tosiasiallisesti käytössä, ei siihen, mikä on liitetty. Ennen ajamista vie kaikkien tuoteliitetiedostojen ID:t ristiintarkistusta varten.

Mitä käyttää projektissasi: lopullinen erittely

Menetelmän valinta riippuu sivuston arkkitehtuurista:

  • Tavallinen blogi tai uutissivusto, osioiden 1 (kiinnittymättömät liitetiedostot) ja 3 (404-liitetiedostot) funktiot riittävät. Aja kerran puolessa vuodessa, mediakiirjasto voi hyvin.
  • Sivusto, jossa on vanhoja CPT:itä (portfolio, luettelo, ilmoitukset), lisää osio 2. Siivoa tarkasti roska poistetuista tai hylätyistä artikkelityypeistä.
  • Projekti ACF:llä/Meta Boxilla, jossa on omat kentät kuville, sinun vaihtoehtosi: osio 5. Kerää sallittujen lista, poista loput. Määritä kerran ja toista sitten tarpeen mukaan.
  • Kaikki yhdessä ja epäselvää, aloita uploads-hakemiston rekursiivisella skannauksella (osio 4). Katso, kuinka paljon roskaa levyllä on. Sovella sitten osioita 1-3 ja 5 valikoivasti tilanteen mukaan.

Mikään näistä skripteistä ei korvaa säännöllistä sivuston hygieniaa. Mutta kun kirjoitat tarvitsemasi funktion ja tallennat sen projektidokumentaatioon, säästät tunteja manuaalista työtä seuraavassa auditoinnissa.

Ja kyllä, otithan jo varmuuskopion.

WordPress-media-kirjaston siivous roskatiedostoista