Un upgrade major al unui site WordPress vechi, în special pentru site-urile cu o istorie extinsă, aduce adesea la lumină discrepanțe structurale. În cazul unui client care deține un blog încă din 2008, problema a constat în lipsa atributului Featured Image (Imagine reprezentativă) pe postări. Deși postările conțineau imagini inserate în corpul conținutului, tema veche le afișa în sumarul de pe prima pagină printr-un mecanism de extracție largă a conținutului, ignorând standardul nativ al WordPress.
Cu alte cuvinte, clientul nu a folosit niciodată opțiunea de Featured Image când a creat articole.
Soluția
Pentru a rezolva această problemă, am dezvoltat un set de funcții PHP pentru a automatiza procesul de atribuire a miniaturilor.
Mecanismul de bază, integrat prin funcția ofi_set_thumbnail_from_content(), efectuează o scanare a conținutului postării pentru a identifica și extrage sursa (URL-ul) primei etichete <img> utilizând o expresie regulată (preg_match). Această abordare este rapidă și sigură. Odată obținut URL-ul imaginii, funcția ofi_get_or_make_attachment_id() preia controlul. Aceasta încearcă întâi să mapaeze URL-ul la un atașament existent în biblioteca media a WordPress prin attachment_url_to_postid().
Dacă imaginea este externă sau nu a fost înregistrată anterior, funcția recurge la sideloading – un proces prin care fișierul imagine este descărcat pe server, importat oficial în Media Library și atașat postării curente, asigurând astfel că toate dimensiunile de miniaturi necesare temei sunt generate corespunzător. Această logică este executată automat prin hook-ul save_post, prevenind apariția postărilor fără miniatură pe viitor.
Nu vreau să te plictisesc prea tare cu termeni tehnici. Codul de care ai nevoie este următorul:
<?php function ofi_extract_first_image_src($h){if(empty($h))return false;if(preg_match('/<img[^>]+src=["\']([^"\']+)["\']/i',$h,$m)){$u=trim($m[1]);if(0===strpos($u,'/')&&0!==strpos($u,'//')){$u=home_url($u);}return $u;}return false;}function ofi_get_or_make_attachment_id($u,$p){if(!$u)return 0;$a=attachment_url_to_postid($u);if($a)return $a;if(0===strpos($u,'data:'))return 0;if(!function_exists('media_sideload_image')){require_once ABSPATH.'wp-admin/includes/media.php';require_once ABSPATH.'wp-admin/includes/file.php';require_once ABSPATH.'wp-admin/includes/image.php';}$id=media_sideload_image($u,$p,null,'id');if(is_wp_error($id))return 0;return(int)$id;}function ofi_set_thumbnail_from_content($p){$p=get_post($p);if(!$p||'post'!==$p->post_type)return;if(has_post_thumbnail($p->ID))return;$s=ofi_extract_first_image_src($p->post_content);if(!$s)return;$a=ofi_get_or_make_attachment_id($s,$p->ID);if($a){set_post_thumbnail($p->ID,$a);}}add_action('save_post',function($p,$o,$u){if(defined('DOING_AUTOSAVE')&&DOING_AUTOSAVE)return;if(wp_is_post_revision($p))return;ofi_set_thumbnail_from_content($p);},20,3);add_action('admin_menu',function(){add_management_page('Backfill Featured Images','Backfill Featured Images','manage_options','ofi-backfill-thumbs','ofi_render_backfill_page');});function ofi_render_backfill_page(){if(!current_user_can('manage_options'))return;$pr=0;$s=0;$l=0;$m=[];if(isset($_POST['ofi_backfill'])&&check_admin_referer('ofi_backfill_nonce')){$b=max(1,min(100,intval($_POST['ofi_batch']??50)));$q=new WP_Query(['post_type'=>'post','post_status'=>'publish','posts_per_page'=>$b,'meta_query'=>[['key'=>'_thumbnail_id','compare'=>'NOT EXISTS']],'fields'=>'ids','orderby'=>'date','order'=>'DESC',]);foreach($q->posts as $p){$pr++;$h=has_post_thumbnail($p);ofi_set_thumbnail_from_content($p);if(has_post_thumbnail($p)&&!$h){$s++;}}$l=(new WP_Query(['post_type'=>'post','post_status'=>'publish','posts_per_page'=>1,'meta_query'=>[['key'=>'_thumbnail_id','compare'=>'NOT EXISTS']],'fields'=>'ids',]))->found_posts;$m[]=sprintf('Processed: %d posts | Newly set: %d | Still left (est.): %d',$pr,$s,$l);}?><div class="wrap"><h1>Backfill Featured Images</h1><p>This will scan published posts without a Featured Image and set it from the first image in the content (sideloading external images if needed).</p><?php foreach($m as $t):?><div class="notice notice-success"><p><?php echo esc_html($t);?></p></div><?php endforeach;?><form method="post"><?php wp_nonce_field('ofi_backfill_nonce');?><p><label for="ofi_batch">Batch size (1–100):</label><input type="number" min="1" max="100" name="ofi_batch" id="ofi_batch" value="50" /></p><p><button class="button button-primary" name="ofi_backfill" value="1">Run batch</button></p><p><em>Tip:</em> Click “Run batch” multiple times until the “left” counter reaches 0.</p></form></div><?php }if(defined('WP_CLI')&&WP_CLI){WP_CLI::add_command('ofi backfill',function($a,$aa){$l=isset($aa['limit'])?(int)$aa['limit']:200;$q=new WP_Query(['post_type'=>'post','post_status'=>'publish','posts_per_page'=>$l,'meta_query'=>[['key'=>'_thumbnail_id','compare'=>'NOT EXISTS']],'fields'=>'ids','orderby'=>'date','order'=>'DESC',]);$s=0;foreach($q->posts as $p){ofi_set_thumbnail_from_content($p);if(has_post_thumbnail($p))$s++;}WP_CLI::success("Processed: {$q->post_count}, newly set: {$s}");});}
Codul trebuie adăugat în fișierul functions.php al temei active pe site. Totuși, mult mai simplu și mai sigur este să introduci codul prin intermediul unui plugin WordPress de genul Code Snippets.
După adăugarea codului va trebui să accesezi panoul de administrare WordPress – Unelte (Tools). Acolo îți va apărea o nouă subcategorie numită Backfill Featured Images. Accesezi subcategoria și vei vedea următoarea interfață simpă:

Observi că ai un singur input de completat și un singur buton. Va trebui să introduci în câmpul Batch size numărul de articole asupra cărora va rula script-ul. După mai multe teste am înțeles că rularea batch-ului funcționează fără probleme la un maxim de 100 de articole / sesiune. Asta înseamnă că pentru 2000 de articole ar trebui să repeți procedura de 20 de ori. După fiecare sesiune vei putea vedea câte articole mai ai de procesat.
Evident, este necesar ca articolele vechi să aibă totuși în corpul lor o imagine care să poată fi preluată.