
Een paar dagen geleden heb ik een taak afgerond die al lang had moeten gebeuren, maar die ik al een eeuwigheid voor me uit had geschoven. Ikheb alleontbrekende uitgelichte afbeeldingenteruggehaalden alle ontbrekende alt-tekst aan de afbeeldingen toegevoegd. Ook heb ik alle ontbrekende metabeschrijvingen geschreven. In totaal heb ik meer dan 15.000 records toegevoegd of bewerkt met behulp van drie van mijn eigen eenmalige snippets. Vandaag deel ik hier de snippet voor het ophalen van de ontbrekende uitgelichte afbeeldingen, die ik ook heb gebruikt om deze taak op andere sites uit te voeren.
Als je al een tijdje blogt, is de kans groot dat je nogal wat oudere berichten hebt waar nog geen uitgelichte afbeelding aan is toegewezen. Dit kwam vroeger vaak voor in de tijd van de klassieke editor, toen we de afbeelding rechtstreeks in de tekst van het bericht invoegden, maar vergaten deze te selecteren in het vakje ‘Uitgelichte afbeelding’.
Het plaatsen van berichten zonder uitgelichte afbeelding is niet alleen een esthetische kwestie, maar heeft onder andere ook directe gevolgen voor de zoekmachineoptimalisatie van je website en de gebruikerservaring:
- Social SEO en Open Graph: Wanneer het bericht wordt gedeeld op sociale media, in berichtenapps of via contentaggregators, kunnen de Open Graph-tags (
og:image) geen duidelijke bron vinden om weer te geven, waardoor de CTR (click-through rate) drastisch daalt. - Visuele consistentie in het thema: Moderne sjablonen (zoals die welke zijn ontworpen met GeneratePress of GenerateBlocks) zijn afhankelijk van de uitgelichte afbeelding voor het opbouwen van archiefrasters, lijsten met gerelateerde berichten of kopteksten. Zonder deze afbeelding raakt de lay-out verstoord of ontstaan er ongelijkmatige tussenruimtes.
- Ontdekking en feeds (RSS / Google Discover): Tools zoals Google Discover of RSS-readers geven voorrang aan of vereisen inhoud met een bijbehorende miniatuurafbeelding in hoge resolutie om de artikelkaart te verfraaien.
Om dit op te lossen zonder gebruik te maken van plug-ins (zoals deze) of trage processen, heb ik dit eenvoudige, lichtgewicht PHP-fragment gemaakt dat de eerste afbeelding in elk bericht automatisch instelt als uitgelichte afbeelding, rechtstreeks vanuit het WordPress-dashboard. Als je bericht geen afbeeldingen bevat, kun je een standaardafbeelding toewijzen door de URL ervan aan de code toe te voegen.
Wat doet dit stukje code precies?
De code voegt een handige, ingebouwde tool voor batchverwerking toe aan de lijst met WordPress-berichten (wp-admin/edit.php):
- Een aangepaste kolom toevoegen: Maak de kolom ‘Ontbrekende uitgelichte afbeelding’ aan in de lijst met berichten, zodat je in één oogopslag kunt zien bij welke berichten een miniatuurafbeelding ontbreekt.
- Verwerking in drie stappen: wanneer je de toewijzing voor een bericht inschakelt, wordt er in precies deze volgorde naar de afbeelding gezocht:
- Controleer of er afbeeldingen zijn die rechtstreeks aan het bericht zijn toegevoegd in de mediabibliotheek.
- Als er geen directe bijlagen zijn (bijvoorbeeld als ze via een afbeeldingsblok of HTML zijn ingevoegd), doorzoek dan de inhoud naar de eerste
<img>-tag om de bijbehorende ID op te halen met behulp vanattachment_url_to_postid(). - Als het bericht helemaal geen afbeeldingen bevat, wordt er een standaardafbeelding (fallback) toegewezen die in de configuratie is opgegeven.
- AJAX-bulkverwerking (batchverwerking): Bevat een knop in de koptekst (“⚡ Alles op deze pagina toewijzen”) waarmee alle op het scherm zichtbare vermeldingen achtereenvolgens worden verwerkt via opeenvolgende asynchrone verzoeken. Je kunt dit ook handmatig doen via de afzonderlijke knop bij elk bericht in de lijst.
- Conceptberichten filteren: Sluit automatisch berichten met de status ‘concept’ uit, zodat je je volledig kunt concentreren op gepubliceerde inhoud.
- Sorteerbare kolom: Hiermee kun je de lijst zo sorteren dat alle vermeldingen zonder uitgelichte afbeelding bovenaan bij elkaar worden gegroepeerd.
Waarom is het zo licht van gewicht en heeft dit geen invloed op de prestaties van de server?
Een veelvoorkomende zorg bij het gebruik van dit soort geautomatiseerde tools is de vrees dat de database vastloopt of dat het PHP-geheugen overbelast raakt op websites met duizenden artikelen. Dit codefragment is ontworpen volgens de volgende optimalisatiecriteria:
1. Het werkt alleen in het beheergedeelte (is_admin())
De code is uitsluitend bedoeld voor het invoerscherm van het beheerderspaneel (edit.php). Het voegt geen enkele regel extra code toe aan verzoeken op de openbare website. Dit heeft op geen enkele manier invloed op uw gebruikers of de snelheidsstatistieken.
2. Verzoeken rechtstreeks naar de Meta Cache sturen
Om te bepalen of een bericht al dan niet een afbeelding bevat, voert het fragment niet voor elke rij ingewikkelde databasezoekopdrachten uit. Het maakt gebruik van de metagegevenssleutel _thumbnail_id, die WordPress bij het weergeven van de lijst standaard via de metagegevenscache in het geheugen laadt. De tabel wordt vrijwel onmiddellijk geladen.
3. Cascaderende asynchrone verwerking (sequentiële AJAX-lus)
In plaats van een omvangrijke query op de database uit te voeren om 700 of 1.000 records in één keer bij te werken (wat een time-out zou veroorzaken of al het geheugen van de server zou opgebruiken), verwerkt de knop ‘Alles toewijzen’ de records één voor één met behulp van JavaScript-promises:
POST 1 (AJAX)⟶OK-antwoord⟶POST 2 (AJAX)⟶OK-antwoord…
Dit zorgt voor een stabiel en consistent gebruik van middelen, waardoor je de voortgang in realtime kunt volgen, rij voor rij.
4. Geen dubbele bestanden
Het fragment downloadt, combineert of dupliceert geen afbeeldingen op je harde schijf. Het haalt simpelweg de ID op van de afbeelding die al in je mediabibliotheek staat en koppelt deze aan het bericht door de bijbehorende meta_value toe te wijzen in de tabel wp_postmeta.
De code (PHP + JavaScript)
Je kunt deze code toevoegen aan het bestand functions.php van je child-thema, aan een zelfgemaakte plug-in, of via een plug-in voor aangepaste functies, zoals Code Snippets of iets dergelijks.
Opmerking: Vergeet niet om de variabele
$default_image_urlop regel 56 in te stellen op de volledige URL van de standaardafbeelding die je wilt toewijzen aan berichten die geen afbeeldingen in de tekst bevatten.
PHP
// 1. Añadir columna personalizada en el listado de entradas
add_filter( 'manage_posts_columns', 'custom_featured_status_column' );
function custom_featured_status_column( $columns )
$columns['featured_missing'] = 'Destacada Faltante';
return $columns;
}
// 2. Hacer la columna ordenable al hacer clic en el encabezado
add_filter( 'manage_edit-post_sortable_columns', 'custom_featured_sortable_column' );
function custom_featured_sortable_column( $columns ) {
$columns['featured_missing'] = 'featured_missing';
return $columns;
}
// 3. Modificar la consulta para ordenar por presencia de _thumbnail_id
add_action( 'pre_get_posts', 'custom_featured_column_orderby' );
function custom_featured_column_orderby( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) return;
if ( $query->get( 'orderby' ) === 'featured_missing' ) {
$query->set( 'meta_key', '_thumbnail_id' );
$query->set( 'orderby', 'meta_value_num' );
$query->set( 'meta_compare', 'NOT EXISTS' );
}
}
// 3.1. Añadir desplegable de filtro nativo en el listado de entradas
add_action( 'restrict_manage_posts', 'custom_featured_filter_dropdown' );
function custom_featured_filter_dropdown( $post_type ) {
if ( $post_type !== 'post' ) return;
$selected = isset( $_GET['filter_featured_status'] ) ? sanitize_text_field( $_GET['filter_featured_status'] ) : '';
?>
<select name="filter_featured_status" id="filter_featured_status">
<option value=""><?php _e( 'Todas las destacadas', 'textdomain' ); ?></option>
<option value="has_img" <?php selected( $selected, 'has_img' ); ?>>Tiene</option>
<option value="missing_img" <?php selected( $selected, 'missing_img' ); ?>>Falta</option>
</select>
<?php
}
// 3.2. Aplicar el filtro del desplegable a la consulta de WP_Query
add_filter( 'parse_query', 'custom_featured_filter_apply' );
function custom_featured_filter_apply( $query ) {
global $pagenow;
if ( ! is_admin() || $pagenow !== 'edit.php' || ! $query->is_main_query() ) return;
if ( isset( $_GET['filter_featured_status'] ) && $_GET['filter_featured_status'] !== '' ) {
$filter_value = sanitize_text_field( $_GET['filter_featured_status'] );
$meta_query = (array) $query->get( 'meta_query' );
if ( $filter_value === 'has_img' ) {
$meta_query[] = array(
'key' => '_thumbnail_id',
'compare' => 'EXISTS',
);
} elseif ( $filter_value === 'missing_img' ) {
$meta_query[] = array(
'key' => '_thumbnail_id',
'compare' => 'NOT EXISTS',
);
}
$query->set( 'meta_query', $meta_query );
}
}
// 4. Mostrar el estado y el botón rápido en la columna (Excluyendo borradores)
add_action( 'manage_posts_custom_column', 'custom_featured_status_column_content', 10, 2 );
function custom_featured_status_column_content( $column, $post_id ) {
if ( $column === 'featured_missing' ) {
if ( get_post_type( $post_id ) !== 'post' || get_post_status( $post_id ) === 'draft' ) {
echo '<span style="color: #999;">—</span>';
return;
}
$has_thumbnail = get_post_meta( $post_id, '_thumbnail_id', true );
if ( $has_thumbnail ) {
echo '<span style="color: #46b450; font-weight: bold;">✔ Tiene</span>';
} else {
echo '<span style="color: #dc3232; font-weight: bold;">❌ Falta</span> ';
echo '<button type="button" class="button button-small set-first-img-btn" data-postid="' . esc_attr( $post_id ) . '">Asignar imagen</button>';
echo '<span class="set-img-status" style="margin-left:5px;"></span>';
}
}
}
// 5. Procesar la asignación por AJAX (con fallback a la imagen por defecto)
add_action( 'wp_ajax_set_first_image_as_featured', 'ajax_set_first_image_as_featured' );
function ajax_set_first_image_as_featured() {
check_ajax_referer( 'set_first_image_nonce', 'nonce' );
$post_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
wp_send_json_error( 'Sin permisos o ID inválido' );
}
if ( get_post_status( $post_id ) === 'draft' ) {
wp_send_json_error( 'La entrada es un borrador' );
}
// =========================================================================
// CONFIGURACIÓN: Reemplaza esta URL por la URL completa de tu imagen por defecto
// =========================================================================
$default_image_url = 'https://tu-dominio.com/wp-content/uploads/imagen-por-defecto.jpg';
// A. Buscar adjuntos directos al post
$attachments = get_posts( array(
'post_type' => 'attachment',
'posts_per_page' => 1,
'post_parent' => $post_id,
'post_mime_type' => 'image',
'orderby' => 'menu_order',
'order' => 'ASC',
'fields' => 'ids'
) );
if ( ! empty( $attachments ) ) {
set_post_thumbnail( $post_id, $attachments[0] );
wp_send_json_success( '¡Asignada primera!' );
}
// B. Si no hay adjuntos, buscar <img> en el contenido
$post = get_post( $post_id );
preg_match_all( '/<img.+?src=[\'"]([^\'"]+)[\'"]/i', $post->post_content, $matches );
if ( isset( $matches[1][0] ) ) {
$attachment_id = attachment_url_to_postid( $matches[1][0] );
if ( $attachment_id ) {
set_post_thumbnail( $post_id, $attachment_id );
wp_send_json_success( '¡Asignada primera!' );
}
}
// C. Si no hay ninguna imagen en el post, asignar la imagen por defecto
if ( ! empty( $default_image_url ) ) {
$default_id = attachment_url_to_postid( $default_image_url );
if ( $default_id ) {
set_post_thumbnail( $post_id, $default_id );
wp_send_json_success( '¡Asignada por defecto!' );
}
}
wp_send_json_error( 'No se encontró ninguna imagen en el post ni ID de fallback' );
}
// 6. Cargar JS y botón masivo en el admin (edit.php)
add_action( 'admin_footer-edit.php', 'custom_featured_ajax_script' );
function custom_featured_ajax_script() {
$screen = get_current_screen();
if ( $screen->post_type !== 'post' ) return;
$nonce = wp_create_nonce( 'set_first_image_nonce' );
?>
<script>
jQuery(document).ready(function($) {
$('<button type="button" id="process-all-page-btn" class="page-title-action" style="margin-left:10px; background:#2271b1; color:#fff; border-color:#2271b1;">⚡ Asignar todas en esta página</button>')
.insertAfter('.wp-heading-inline');
function processButton($btn) {
return new Promise(function(resolve) {
var $status = $btn.siblings('.set-img-status');
var postId = $btn.data('postid');
$btn.prop('disabled', true).text('Procesando...');
$.post(ajaxurl, {
action: 'set_first_image_as_featured',
post_id: postId,
nonce: '<?php echo $nonce; ?>'
}, function(response) {
if (response.success) {
$btn.parent().html('<span style="color: #46b450; font-weight: bold;">✔ ' + response.data + '</span>');
} else {
$status.css('color', '#dc3232').text(response.data);
$btn.prop('disabled', false).text('Asignar imagen');
}
resolve();
}).fail(function() {
$btn.prop('disabled', false).text('Error');
resolve();
});
});
}
$(document).on('click', '.set-first-img-btn', function(e) {
e.preventDefault();
processButton($(this));
});
$('#process-all-page-btn').on('click', async function(e) {
e.preventDefault();
var $batchBtn = $(this);
var $pendingButtons = $('.set-first-img-btn:not(:disabled)');
if ($pendingButtons.length === 0) {
alert('No hay entradas pendientes de asignar en esta página.');
return;
}
$batchBtn.prop('disabled', true).text('Procesando por lote...');
for (var i = 0; i < $pendingButtons.length; i++) {
await processButton($($pendingButtons[i]));
}
$batchBtn.text('¡Lote completado!').css('background', '#46b450');
setTimeout(function() {
$batchBtn.prop('disabled', false).text('⚡ Asignar todas en esta página').css('background', '#2271b1');
}, 3000);
});
});
</script>
<?php
}
Hoe gebruik je het?
- Ga naar de lijst ‘Berichten’ in je beheerderspaneel.
- Open bovenaan het tabblad ‘Weergaveopties’ en stel het aantal weer te geven items in op 50 of 100 per pagina. (Begin met lagere getallen en verhoog deze geleidelijk als je server goed reageert.)

- Klik op de kolomkop ‘Ontbrekende hoogtepunten’ om de nog openstaande vermeldingen te sorteren en te groeperen. In de filters kun je kiezen tussen ‘Heeft’, ‘Ontbreekt’ of ‘Alles’
- Zodra je zeker weet dat je klaar bent om de ontbrekende uitgelichte afbeeldingen in batches toe te wijzen, klik je op de blauwe knop ‘ ’ met het label ‘Alles op deze pagina toewijzen’ en laat je het cascadeproces de miniatuurafbeeldingen voor die batch toewijzen. Als je dat liever hebt, kun je ze ook één voor één toewijzen.
- Ga naar de volgende pagina en herhaal dit totdat je klaar bent met het opruimen van je bibliotheek.
Optioneel: fragment voor automatische toewijzing
Om te voorkomen dat je deze taak in de toekomst nog eens moet herhalen, kun je dit korte stukje code toevoegen. Het wordt alleen op de achtergrond uitgevoerd wanneer je een bericht opslaat of publiceert: als het detecteert dat je niet handmatig een uitgelichte afbeelding hebt geselecteerd, wijst het automatisch de eerste afbeelding in de inhoud toe (of de standaardafbeelding, die je moet toevoegen in $default_image_url =).
PHP
// Asignar imagen destacada automáticamente al publicar/guardar nuevas entradas
add_action( 'save_post_post', 'auto_set_featured_image_on_save', 10, 3 );
function auto_set_featured_image_on_save( $post_id, $post, $update ) {
// Evitar autoguardados, revisiones o peticiones AJAX/REST innecesarias
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( wp_is_post_revision( $post_id ) || $post->post_status === 'auto-draft' ) return;
// Si la entrada ya tiene imagen destacada marcada, no hacemos nada
if ( has_post_thumbnail( $post_id ) ) return;
$default_image_url = 'https://tu-dominio.com/wp-content/uploads/imagen-por-defecto.jpg';
// 1. Buscar primera imagen en el contenido
preg_match_all( '/<img.+?src=[\'"]([^\'"]+)[\'"]/i', $post->post_content, $matches );
if ( isset( $matches[1][0] ) ) {
$attachment_id = attachment_url_to_postid( $matches[1][0] );
if ( $attachment_id ) {
set_post_thumbnail( $post_id, $attachment_id );
return;
}
}
// 2. Si el post no tiene imágenes, asignar la imagen por defecto
if ( ! empty( $default_image_url ) ) {
$default_id = attachment_url_to_postid( $default_image_url );
if ( $default_id ) {
set_post_thumbnail( $post_id, $default_id );
}
}
}






