
לפני כמה ימים סיימתי משימה שהייתה צריכה להיעשות כבר מזמן, אך דחיתי אותה במשך זמן רב.השלמתי את כלהתמונות המובלטות החסרות והוספתי את כל טקסט ה-alt החסר לתמונות. כמו כן, סיימתי לכתוב את כל תיאורי המטא החסרים. בסך הכל, הוספתי או ערכתי יותר מ-15,000 רשומות באמצעות שלושה קטעי קוד חד-פעמיים שכתבתי בעצמי. היום אני משתף כאן את קטע הקוד לאיתור התמונות המובלטות החסרות, ששימש אותי גם לביצוע המשימה הזו באתרים אחרים.
אם אתם כותבים בבלוג כבר זמן מה, סביר להניח שיש לכם לא מעט פוסטים ישנים שלא הוקצתה להם תמונה מובלטת. זה קרה הרבה בימי העורך הקלאסי, כשנהגנו להוסיף את התמונה ישירות לגוף הפוסט, אך שכחנו לבחור אותה בתיבת ה"תמונה נבחרת".
פרסום פוסטים ללא תמונה ראשית אינו רק עניין אסתטי, בין היתר; הדבר משפיע באופן ישיר על אופטימיזציית האתר ועל חוויית המשתמש:
- SEO ברשתות החברתיות ו-Open Graph: כאשר משתפים את הפוסט ברשתות החברתיות, באפליקציות מסרים או באגרגטורי תוכן, תגי ה-Open Graph (
og:image) אינם מצליחים לאתר משאב ברור להצגה, מה שמפחית באופן דרמטי את שיעור הקליקים (CTR). - אחידות ויזואלית בערכת העיצוב: תבניות מודרניות (כגון אלה שעוצבו באמצעות GeneratePress או GenerateBlocks) מסתמכות על התמונה המובלטת כדי לבנות רשתות ארכיון, רשימות של פוסטים קשורים או כותרות. בלעדיה, הפריסה נפגמת או נוצרים רווחים לא מיושרים.
- גילוי ועדכונים (RSS / Google Discover): כלים כגון Google Discover או קוראי RSS מעניקים עדיפות או דורשים תוכן הכולל תמונה ממוזערת ברזולוציה גבוהה, כדי לשפר את כרטיס המאמר.
כדי לפתור את הבעיה הזו מבלי להזדקק לתוספים (כמו זה) או לתהליכים איטיים, יצרתי את קטע הקוד הפשוט והקל הזה ב-PHP, שמגדיר באופן אוטומטי את התמונה הראשונה בכל פוסט כתמונה המובלטת, ישירות ממרכז השליטה של וורדפרס. אם בפוסט שלכם אין תמונות כלל, תוכלו להקצות תמונה ברירת מחדל על ידי הוספת כתובת ה-URL שלה לקוד.
מה בדיוק עושה קטע הקוד הזה?
הקוד מוסיף כלי עיבוד אצווה חכם ומובנה לרשימת הפוסטים של וורדפרס (wp-admin/edit.php):
- הוספת עמודה מותאמת אישית: צרו את העמודה " תמונהמומלצת חסרה " ברשימת הפוסטים כדי לראות במבט אחד אילו פוסטים חסרים תמונה ממוזערת.
- עיבוד ב-3 שלבים: כאשר אתה מפעיל את ההקצאה עבור פוסט, המערכת מחפשת את התמונה בדיוק בסדר הזה:
- בדוק אם יש תמונות המצורפות ישירות לפוסט בספריית המדיה.
- אם אין קבצים מצורפים ישירים (למשל, אם הם הוכנסו באמצעות בלוק תמונה או HTML), יש לסרוק את התוכן עד לתגית
<img>הראשונה כדי לחלץ את המזהה שלה באמצעותהפונקציה attachment_url_to_postid(). - אם הערך אינו מכיל תמונות כלל, המערכת מקצה תמונה ברירת מחדל (חלופית) שצוינה בתצורה.
- עיבוד המוני ב-AJAX (עיבוד אצווה): כולל כפתור בכותרת העליונה (“⚡ הקצה את כל הפריטים בעמוד זה”) המעבד את כל הרשומות הנראות על המסך ברצף באמצעות בקשות אסינכרוניות רצופות. ניתן לבצע זאת גם באופן ידני באמצעות הכפתור הנפרד של כל פוסט ברשימה.
- סינון טיוטות: מסנן באופן אוטומטי רשומות במצב טיוטה, כך שתוכל להתמקד אך ורק בתוכן שפורסם.
- עמודה הניתנת למיון: מאפשרת למיין את הרשימה כך שכל הערכים שאין להם תמונה מומלצת יקובצו יחד בחלק העליון.
מדוע הוא כל כך קל משקל, והאם זה לא משפיע על ביצועי השרת?
חשש נפוץ בעת השימוש בכלי אוטומטי מסוג זה הוא החשש מפני נעילת מסד הנתונים או עומס יתר על זיכרון ה-PHP באתרים המכילים אלפי מאמרים. קטע הקוד הזה תוכנן בהתאם לקריטריוני האופטימיזציה הבאים:
1. הוא פועל רק באזור הניהול (is_admin())
הקוד מיועד אך ורק למסך ההזנה של לוח הניהול (edit.php). הוא אינו מוסיף ולו שורת קוד אחת לעומס על הבקשות באתר הציבורי. הדבר לא ישפיע בשום צורה על המשתמשים ועל מדדי המהירות.
2. הפנו שאילתות למטא-קאש
כדי לקבוע אם פוסט מסוים מכיל תמונה או לא, הקטע הקוד אינו מבצע חיפושים מורכבים במסד הנתונים עבור כל שורה. הוא משתמש במפתח המטא-נתונים _thumbnail_id, ש-WordPress טוען לזיכרון באופן מובנה באמצעות מטמון המטא-נתונים בעת הצגת הרשימה. הטבלה נטענת כמעט באופן מיידי.
3. עיבוד אסינכרוני מדורג (לולאת AJAX רציפה)
במקום להריץ שאילתה ענקית על מסד הנתונים כדי לעדכן 700 או 1,000 רשומות בבת אחת (מה שעלול לגרום לפקיעת זמן המתנה או לניצול כל זיכרון השרת), הכפתור 'הקצה הכל' מעבד את הרשומות אחת אחת באמצעות הבטחות JavaScript:
POST 1 (AJAX)⟶תגובה "OK"⟶POST 2 (AJAX)⟶תגובה "OK"…
כך מובטח שימוש יציב ועקבי במשאבים, מה שמאפשר לכם לעקוב אחר ההתקדמות בזמן אמת, שורה אחר שורה.
4. אין כפילויות בקבצים
התוסף אינו מוריד, מאחד מחדש או משכפל תמונות בכונן הקשיח שלכם. הוא פשוט לוקח את המזהה של התמונה שכבר נמצאת בספריית המדיה שלכם ומקשר אותה לפוסט על ידי הקצאת ערך ה-meta_value המתאים בטבלת wp_postmeta.
הקוד (PHP + JavaScript)
ניתן להוסיף את הקוד הזה לקובץ functions.php של ערכת העיצוב-הבת שלכם, לתוסף מותאם אישית, או באמצעות תוסף פונקציות מותאם אישית כגון Code Snippets או תוסף דומה.
הערה: זכרו להגדיר את המשתנה
$default_image_urlבשורה 56 לכתובת ה-URL המלאה של התמונה הגנרית שברצונכם להקצות לפוסטים שאינם מכילים תמונות בטקסט.
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
}
איך משתמשים בזה?
- עבור לרשימת 'הפוסטים' בלוח הבקרה שלך.
- פתח את הכרטיסייה "אפשרויות תצוגה" בחלק העליון ושנה את מספר הפריטים המוצגים ל-50 או 100 בכל עמוד. (התחל במספרים קטנים יותר והגבר אותם בהדרגה אם השרת שלך מגיב היטב.)

- לחץ על כותרת העמודה "נקודות בולטות חסרות" כדי למיין ולקבץ את הערכים שטרם סומנו. במסננים, תוכל לבחור בין "יש", "חסר" או "הכל"
- ברגע שתהיו בטוחים שאתם מוכנים להתחיל להקצות תמונות מובלטות חסרות בקבוצות, לחצו על הכפתור הכחול ' ' שכותרתו 'הקצה את כל התמונות בעמוד זה' ותנו לתהליך האוטומטי להשלים את הקצאת התמונות הממוזערות לאותה קבוצה. אם אתם מעדיפים, תוכלו להקצות אותן אחת אחת.
- עבור לדף הבא וחזור על התהליך עד שתסיים לסדר את הספרייה שלך.
אופציונלי: קטע קוד להקצאה אוטומטית
כדי להימנע מהצורך לחזור על משימה זו בעתיד, תוכלו להוסיף את קטע הקוד הקצר הזה. הוא פועל ברקע רק כשאתם שומרים או מפרסמים פוסט: אם הוא מזהה שלא בחרתם ידנית תמונה מומלצת, הוא מקצה אוטומטית את התמונה הראשונה בתוכן (או את תמונת הגיבוי המוגדרת כברירת מחדל, אותה תצטרכו להוסיף ב-$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 );
}
}
}







