I’m working on a custom post type in WordPress for “Projects” that includes three custom meta boxes: one for a project title, one for a project description, and one for an image gallery. The gallery meta box allows me to add images using the WordPress media uploader. The images display correctly in the meta box immediately after being added, but once I update the project and refresh the page, the gallery images vanish.
Here’s a summary of my setup:
Custom Post Type: “progetti” (Projects) with support for featured images.
Meta Boxes:
Project Title: Uses wp_editor and saves its value as a meta field (_progetto_titolo).
Project Description: Also uses wp_editor (saved as _progetto_descrizione).
Gallery: Stores a comma-separated list of attachment IDs in a meta field (_progetto_gallery). It includes JavaScript that adds and removes images from the gallery.
I initially updated the actual post title by calling wp_update_post() inside my save_post hook after saving the custom title. I suspected this was triggering a second save cycle that might be wiping out other meta values (like the gallery). So I tried a couple of approaches:
Temporarily removing the save_post hook before calling wp_update_post(), then re-adding it.
Using the wp_insert_post_data filter to update the title before the post is saved.
I even consolidated my nonce usage into a single nonce field to avoid potential conflicts. Despite these attempts, the gallery meta field still ends up empty after the post is updated and reloaded.
Snippet below:
// Registra il Custom Post Type "Progetti"
function create_custom_post_type_progetti() {
$labels = array(
'name' => _x( 'Progetti', 'Post Type General Name', 'textdomain' ),
'singular_name' => _x( 'Progetto', 'Post Type Singular Name', 'textdomain' ),
'menu_name' => __( 'Progetti', 'textdomain' ),
'name_admin_bar' => __( 'Progetto', 'textdomain' ),
'archives' => __( 'Archivio Progetti', 'textdomain' ),
'attributes' => __( 'Attributi Progetto', 'textdomain' ),
'parent_item_colon' => __( 'Progetto Genitore:', 'textdomain' ),
'all_items' => __( 'Tutti i Progetti', 'textdomain' ),
'add_new_item' => __( 'Aggiungi Nuovo Progetto', 'textdomain' ),
'add_new' => __( 'Aggiungi Nuovo', 'textdomain' ),
'new_item' => __( 'Nuovo Progetto', 'textdomain' ),
'edit_item' => __( 'Modifica Progetto', 'textdomain' ),
'update_item' => __( 'Aggiorna Progetto', 'textdomain' ),
'view_item' => __( 'Visualizza Progetto', 'textdomain' ),
'view_items' => __( 'Visualizza Progetti', 'textdomain' ),
'search_items' => __( 'Cerca Progetto', 'textdomain' ),
'not_found' => __( 'Non trovato', 'textdomain' ),
'not_found_in_trash' => __( 'Non trovato nel cestino', 'textdomain' ),
'featured_image' => __( 'Immagine di copertina', 'textdomain' ),
'set_featured_image' => __( 'Imposta immagine di copertina', 'textdomain' ),
'remove_featured_image' => __( 'Rimuovi immagine di copertina', 'textdomain' ),
'use_featured_image' => __( 'Usa come immagine di copertina', 'textdomain' ),
'insert_into_item' => __( 'Inserisci nel progetto', 'textdomain' ),
'uploaded_to_this_item' => __( 'Caricato in questo progetto', 'textdomain' ),
'items_list' => __( 'Lista progetti', 'textdomain' ),
'items_list_navigation' => __( 'Navigazione lista progetti', 'textdomain' ),
'filter_items_list' => __( 'Filtra lista progetti', 'textdomain' ),
);
$args = array(
'label' => __( 'Progetto', 'textdomain' ),
'description' => __( 'Custom post type per progetti', 'textdomain' ),
'labels' => $labels,
// Supporta l'immagine in evidenza; per titolo e contenuto usiamo metabox personalizzati
'supports' => array( 'thumbnail' ),
'hierarchical' => false,
'public' => true,
'show_ui' => true,
'show_in_menu' => true,
'menu_position' => 5,
'menu_icon' => 'dashicons-portfolio',
'show_in_admin_bar' => true,
'show_in_nav_menus' => true,
'can_export' => true,
'has_archive' => true,
'exclude_from_search' => false,
'publicly_queryable' => true,
'capability_type' => 'post',
'show_in_rest' => true,
);
register_post_type( 'progetti', $args );
}
add_action( 'init', 'create_custom_post_type_progetti', 0 );
// Enqueue delle funzioni necessarie per il media uploader in admin
function progetti_enqueue_admin_scripts( $hook ) {
global $post;
if ( ( $hook == 'post-new.php' || $hook == 'post.php' ) && isset( $post ) && $post->post_type === 'progetti' ) {
wp_enqueue_media();
}
}
add_action( 'admin_enqueue_scripts', 'progetti_enqueue_admin_scripts' );
// Aggiunge le Meta Box per i campi personalizzati
function progetti_add_meta_boxes() {
add_meta_box( 'progetti_titolo', 'Titolo Progetto', 'progetti_titolo_meta_box_callback', 'progetti', 'normal', 'high' );
add_meta_box( 'progetti_descrizione', 'Descrizione Progetto', 'progetti_descrizione_meta_box_callback', 'progetti', 'normal', 'high' );
add_meta_box( 'progetti_gallery', 'Gallery Immagini', 'progetti_gallery_meta_box_callback', 'progetti', 'normal', 'high' );
}
add_action( 'add_meta_boxes', 'progetti_add_meta_boxes' );
// Meta box per il Titolo: include il nonce
function progetti_titolo_meta_box_callback( $post ) {
wp_nonce_field( 'progetti_save_meta_box_data', 'progetti_meta_box_nonce' );
$value = get_post_meta( $post->ID, '_progetto_titolo', true );
wp_editor( $value, 'progetto_titolo', array(
'textarea_name' => 'progetto_titolo',
'media_buttons' => false,
'textarea_rows' => 5,
) );
}
// Meta box per la Descrizione: non include il nonce
function progetti_descrizione_meta_box_callback( $post ) {
$value = get_post_meta( $post->ID, '_progetto_descrizione', true );
wp_editor( $value, 'progetto_descrizione', array(
'textarea_name' => 'progetto_descrizione',
'media_buttons' => false,
'textarea_rows' => 10,
) );
}
// Meta box per la Gallery: non include il nonce
function progetti_gallery_meta_box_callback( $post ) {
$gallery = get_post_meta( $post->ID, '_progetto_gallery', true );
?>
<div id="progetti-gallery-container">
<ul id="progetti-gallery-list">
<?php
if ( ! empty( $gallery ) ) {
$gallery_ids = explode( ',', $gallery );
foreach ( $gallery_ids as $attachment_id ) {
$img_url = wp_get_attachment_thumb_url( $attachment_id );
if ( $img_url ) {
echo '<li data-attachment_id="' . esc_attr( $attachment_id ) . '">
<img src="' . esc_url( $img_url ) . '" style="max-width:100px; margin:0 10px 10px 0;"/>
<a href="#" class="progetti-remove-image">Rimuovi</a>
</li>';
}
}
}
?>
</ul>
<input type="hidden" id="progetti_gallery" name="progetti_gallery" value="<?php echo esc_attr( $gallery ); ?>" />
</div>
<p>
<a href="#" id="progetti-add-gallery-images" class="button">Aggiungi Immagini</a>
</p>
<script>
jQuery(document).ready(function($){
var frame;
$('#progetti-add-gallery-images').on('click', function(e){
e.preventDefault();
if ( frame ) {
frame.open();
return;
}
frame = wp.media({
title: 'Seleziona Immagini per la Gallery',
button: {
text: 'Aggiungi alla gallery'
},
multiple: true
});
frame.on( 'select', function() {
var attachments = frame.state().get('selection').toArray();
var gallery_ids = $('#progetti_gallery').val();
var ids = gallery_ids ? gallery_ids.split(',') : [];
attachments.forEach(function(attachment){
attachment = attachment.toJSON();
if ( ids.indexOf(attachment.id.toString()) === -1 ) {
ids.push(attachment.id);
var img_url = attachment.sizes.thumbnail ? attachment.sizes.thumbnail.url : attachment.url;
$('#progetti-gallery-list').append('<li data-attachment_id="'+attachment.id+'"><img src="'+img_url+'" style="max-width:100px; margin:0 10px 10px 0;"/><a href="#" class="progetti-remove-image">Rimuovi</a></li>');
}
});
$('#progetti_gallery').val(ids.join(','));
});
frame.open();
});
// Rimuove l'immagine dalla gallery
$('#progetti-gallery-list').on('click', '.progetti-remove-image', function(e){
e.preventDefault();
$(this).closest('li').remove();
var ids = [];
$('#progetti-gallery-list li').each(function(){
ids.push($(this).data('attachment_id'));
});
$('#progetti_gallery').val(ids.join(','));
});
});
</script>
<?php
}
add_filter( 'wp_insert_post_data', 'progetti_set_custom_title', 10, 2 );
function progetti_set_custom_title( $data, $postarr ) {
if ( isset( $postarr['post_type'] ) && $postarr['post_type'] === 'progetti' ) {
if ( isset( $_POST['progetto_titolo'] ) ) {
$custom_title = wp_strip_all_tags( $_POST['progetto_titolo'] );
$data['post_title'] = $custom_title;
}
}
return $data;
}
function progetti_save_meta_box_data( $post_id ) {
// Verifica il nonce (presente solo nel meta box del Titolo)
if ( ! isset( $_POST['progetti_meta_box_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['progetti_meta_box_nonce'], 'progetti_save_meta_box_data' ) ) {
return;
}
// Evita autosave e revisioni
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( isset( $_POST['post_type'] ) && 'progetti' == $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
}
// Salva il titolo e il contenuto personalizzato
if ( isset( $_POST['progetto_titolo'] ) ) {
$custom_title = wp_kses_post( $_POST['progetto_titolo'] );
update_post_meta( $post_id, '_progetto_titolo', $custom_title );
}
if ( isset( $_POST['progetto_descrizione'] ) ) {
update_post_meta( $post_id, '_progetto_descrizione', wp_kses_post( $_POST['progetto_descrizione'] ) );
}
// Salva la gallery, se presente
if ( isset( $_POST['progetti_gallery'] ) ) {
update_post_meta( $post_id, '_progetto_gallery', sanitize_text_field( $_POST['progetti_gallery'] ) );
}
}
add_action( 'save_post', 'progetti_save_meta_box_data' );
I attempted several approaches to fix the issue. First, I updated the post title within the save_post hook using wp_update_post and temporarily removed the hook to avoid recursion. Next, I tried using the wp_insert_post_data filter to update the title before the post was saved. I also consolidated nonce usage across my meta boxes to prevent any conflicts. I expected that the gallery meta field—holding a comma-separated list of image attachment IDs—would be saved correctly and persist after the post update. However, after saving and refreshing the page, the gallery meta field is empty, causing the images to disappear.