Why won’t my metabox data save?

I have a custom post with a lot of meta boxes. I recently tried to add some dynamic metaboxes, using this question: Create more Meta Boxes as needed

I managed to get the metaboxes to add nicely, the jquery works, everything works except the save_post action – the “Reviews” save nicely, but the “Screenings” don’t. What have I done wrong in my code? I’m sure it must be something very simple that I’m overlooking, perhaps with the nonces?

Read More

Edit: I know I can do all of this with one save_post, and that works if I only have 1 of these dynamic metaboxes, but if I add a second (or more) dynamic metabox section, that data doesn’t save.

Edit: If necessary, I can post my entire custom-posts.php function file to pastebin or something, but I have just included what I think is the relevant snippet here for brevity

Edit: Updated code…

<?php
add_action('save_post', 'save_postdata'); // saves post data from another function earlier on

add_action('save_post', 'save_postdata_dynamic_reviews_metabox' );  
add_meta_box("film-reviews", "Reviews", "print_dynamic_reviews_metabox", "film", "normal", "low");

add_action('save_post', 'save_postdata_dynamic_screenings_metabox' );
add_meta_box("film-screenings", "Screenings", "print_dynamic_screenings_metabox", "film", "normal", "low");

/* Prints the box content */
function print_dynamic_reviews_metabox() {
    global $post;
    // Use nonce for verification

        echo '<input type="hidden" name="reviews_noncename" id="reviews_noncename" value="' . wp_create_nonce( 'reviews-nonce' ) . '" />';
        echo '<div id="meta_inner-reviews">';
        echo '<ol id="reviews-meta">';
    $reviews = get_post_meta($post->ID,'reviews',true); //get any previously saved meta as an array so we can display it
    // print_r($reviews);
    $c = 0;
    if( is_array($reviews) ) {
      foreach($reviews as $review ) {
                if (isset($review['review-name']) || isset($review['review-link']) ) {
          echo '
                        <li><span class="remove-review" title="Delete">Remove</span>
                       <label><strong>Review Name/Title:</strong> <input type="text" class="meta-review-name saveddata" name="reviews['.$c.'][review-name]" value="'.$review['review-name'].'" /></label>
                         <label><strong>Review URL:</strong> (don't forget the http://) <input type="text" class="meta-review-url saveddata" name="reviews['.$c.'][review-url]" value="'.$review['review-url'].'" /></label>';
                    echo '<span class="remove-review" title="Delete">Remove</span></li>';
          $c = $c +1;
          } // ends if isset $award[album]
        } // ends foreach
            } // ends if (is_array)
        echo '</ol>';
    echo '<span class="add-review">Add New Review</span>';
        ?>
            <script>
                var $ =jQuery.noConflict();
                $(document).ready(function() {
                    var count = <?php echo $c; ?>;
                    $(".add-review").click(function() {
                        count = count + 1;
                        $('#reviews-meta').append('<li><span class="remove-review" title="Delete">Remove</span> <label><strong>Review Name/Title:</strong> <input type="text" class="meta-review-name" name="reviews['+count+'][review-name]" value="" /></label> <label><strong>Review URL:</strong> (don't forget the http://) <input type="text" class="meta-review-url" name="reviews['+count+'][review-url]" value="" /></label></li> <span class="remove-review" title="Delete">Remove</span>');
                        return false;
                    });
                    $(".remove-review").live('click', function() {
                        $(this).parent().remove();
                    });
                });
              </script>

     <?php
     echo '</div>'; // ends div#meta_inner
} // ends function print_dynamic_reviews_metabox()


function save_postdata_dynamic_reviews_metabox( $post_id ) {
  // verify if this is an auto save routine.
  // If it is our form has not been submitted, so we dont want to do anything
    if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
        return;
        }
    // Check permissions
    if ( 'page' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
    elseif ( !current_user_can( 'edit_post', $post_id )) { return $post_id;}

  // verify this came from the our screen and with proper authorization,
  // because save_post can be triggered at other times
    if (isset($_POST['reviews_noncename'])){
        if ( !wp_verify_nonce( $_POST['reviews_noncename'], 'reviews-nonce' ) )
            return;
    }else{return;}

    // OK, we're authenticated: we need to find and save the data
    $reviews = $_POST['reviews'];
    update_post_meta($post_id,'reviews',$reviews);
    } // ends function save_postdata_dynamic_reviews_metabox



    /* Prints the box content */
function print_dynamic_screenings_metabox() {
global $post;
// Use nonce for verification

    echo '<input type="hidden" name="screenings_noncename" id="screenings_noncename" value="' . wp_create_nonce( 'screenings-nonce' ) . '" />';
    echo '<div id="meta_inner-screenings">';
    echo '<ol id="screenings-meta">';
$screenings= get_post_meta($post->ID,'screenings',true); //get any previously saved meta as an array so we can display it
// print_r($screenings);
$c = 0;
if( is_array($screenings) ) {
  foreach($screenings as $screening ) {
            if (isset($screening['screening-festival-name']) || isset($screening['screening-festival-date']) ) {
      echo '
                    <li><span class="remove-screening" title="Delete">Remove</span>
                   <label><strong>Festival Name:</strong> <input type="text" class="meta-screening-festival-name saveddata" name="screenings['.$c.'][screening-festival-name]" value="'.$screening['screening-festival-name'].'" /></label>
                     <label><strong>Festival Date:</strong> <input type="text" class="meta-screening-festival-date saveddata" name="screenings['.$c.'][screening-festival-date]" value="'.$screening['screening-festival-date'].'" /></label>';
                echo '<span class="remove-screening" title="Delete">Remove</span></li>';
      $c = $c +1;
      } // ends if isset $award[album]
    } // ends foreach
        } // ends if (is_array)
    echo '</ol>';
echo '<span class="add-screening">Add New Screening</span>';
    ?>
        <script>
            var $ =jQuery.noConflict();
            $(document).ready(function() {
                var count = <?php echo $c; ?>;
                $(".add-screening").click(function() {
                    count = count + 1;
                    $('#screenings-meta').append('<li><span class="remove-screening" title="Delete">Remove</span> <label><strong>Festival Name:</strong> <input type="text" class="meta-screening-festival-name" name="screenings['+count+'][screening-festival-name]" value="" /></label> <label><strong>Festival Date:</strong> <input type="text" class="meta-screening-festival-date" name="screenings['+count+'][screening-festival-date]" value="" /></label> <span class="remove-screening" title="Delete">Remove</span>');
                    return false;
                });
                $(".remove-screening").live('click', function() {
                    $(this).parent().remove();
                });
            });
          </script>

 <?php
 echo '</div>'; // ends div#meta_inner
} // ends function print_dynamic_screenings_metabox()



function save_postdata_dynamic_screenings_metabox( $post_id ) {
  // verify if this is an auto save routine.
  // If it is our form has not been submitted, so we dont want to do anything
    if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
        return;
        }
    // Check permissions
    if ( 'page' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
    elseif ( !current_user_can( 'edit_post', $post_id )) { return $post_id;}

  // verify this came from the our screen and with proper authorization,
  // because save_post can be triggered at other times
    if (isset($_POST['screenings_noncename'])){
        if ( !wp_verify_nonce( $_POST['screenings_noncename'], 'screenings-nonce' ) )
            return;
    }else{return;}

    // OK, we're authenticated: we need to find and save the data
    $screenings= $_POST['screenings'];
    update_post_meta($post_id,'screenings',$screenings);
    } // ends function save_postdata_dynamic_screenings_metabox


?>

Related posts

Leave a Reply

2 comments

  1. Following are the issues that were preventing from the meta values to be saved in both of your save functions.

    1. Incorrect post_type checking.
    You are checking post type to be of ‘page’ in your save function where as your meta boxes are being displayed on your custom post type of ‘film’.

    Your code:

    if ( 'page' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
    

    It should be:

    if ( 'film' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
    

    2. Incorrect nonce field name.
    As @tollmanz pointed out you are checking incorrect field names when checking nonces. The field name in save functions should match the input field name in print meta box functions.

    Your code:

    if (isset($_POST['reviews-nonce'])){
        if ( !wp_verify_nonce( $_POST['reviews-nonce'], 'reviews-nonce' ) )
            return;
    }else{return;}
    

    It should be:

    if (isset($_POST['reviews_noncename'])){
        if ( !wp_verify_nonce( $_POST['reviews_noncename'], 'reviews-nonce' ) )
            return;
    }else{return;}
    

    For screening nonce your code:

    if (isset($_POST['screenings-nonce'])){
        if ( !wp_verify_nonce( $_POST['screenings-nonce'], 'screenings-nonce' ) )
            return;
    }else{return;}
    

    It should be:

    if (isset($_POST['screenings_noncename'])){
        if ( !wp_verify_nonce( $_POST['screenings_noncename'], 'screenings-nonce' ) )
            return;
    }else{return;}
    

    3. Incorrectly saving the meta values.
    You were using update_post_meta to store the meta values.

    4. Inoccrectly getting meta values.
    You were using third parameter in your get_post_meta calls which is to specify to only fetch single meta value, were in your case you need to fetch all meta values.

    Your code:

    $reviews = get_post_meta($post->ID,'reviews',true); //get any previously saved meta as an array so we can display it
    

    It should be:

    $reviews = get_post_meta($post->ID,'reviews'); //get any previously saved meta as an array so we can display it
    

    Your code for screenings:

    $reviews = get_post_meta($post->ID,'screenings',true); //get any previously saved meta as an array so we can display it
    

    It should be:

    $reviews = get_post_meta($post->ID,'screenings',true); //get any previously saved meta as an array so we can display it
    

    Below is the correct code. The code below should work for you as I have tested it and it worked fine. If it doesn’t work for some reason I will need to look at your complete code.

    add_action('save_post', 'save_postdata_dynamic_reviews_metabox' );  
    add_meta_box("film-reviews", "Reviews", "print_dynamic_reviews_metabox", "film", "normal", "low");
    
    add_action('save_post', 'save_postdata_dynamic_screenings_metabox' );
    add_meta_box("film-screenings", "Reviews", "print_dynamic_screenings_metabox", "film", "normal", "low");
    /* Prints the box content */
    function print_dynamic_reviews_metabox() {
        global $post;
        // Use nonce for verification
    
            echo '<input type="hidden" name="reviews_noncename" id="reviews_noncename" value="' . wp_create_nonce( 'reviews-nonce' ) . '" />';
            echo '<div id="meta_inner-reviews">';
            echo '<ol id="reviews-meta">';
        $reviews = get_post_meta($post->ID,'reviews'); //get any previously saved meta as an array so we can display it
        // print_r($reviews);
        $c = 0;
        if( is_array($reviews) ) {
          foreach($reviews as $review ) {
                    if (isset($review['review-name']) || isset($review['review-link']) ) {
              echo '
                            <li><span class="remove-review" title="Delete">Remove</span>
                           <label><strong>Review Name/Title:</strong> <input type="text" class="meta-review-name saveddata" name="reviews['.$c.'][review-name]" value="'.$review['review-name'].'" /></label>
                             <label><strong>Review URL:</strong> (don't forget the http://) <input type="text" class="meta-review-url saveddata" name="reviews['.$c.'][review-url]" value="'.$review['review-url'].'" /></label>';
                        echo '<span class="remove-review" title="Delete">Remove</span></li>';
              $c = $c +1;
              } // ends if isset $award[album]
            } // ends foreach
                } // ends if (is_array)
            echo '</ol>';
        echo '<span class="add-review">Add New Review</span>';
            ?>
                <script>
                    var $ =jQuery.noConflict();
                    $(document).ready(function() {
                        var count = <?php echo $c; ?>;
                        $(".add-review").click(function() {
                            count = count + 1;
                            $('#reviews-meta').append('<li><span class="remove-review" title="Delete">Remove</span> <label><strong>Review Name/Title:</strong> <input type="text" class="meta-review-name" name="reviews['+count+'][review-name]" value="" /></label> <label><strong>Review URL:</strong> (don't forget the http://) <input type="text" class="meta-review-url" name="reviews['+count+'][review-url]" value="" /></label></li> <span class="remove-review" title="Delete">Remove</span>');
                            return false;
                        });
                        $(".remove-review").live('click', function() {
                            $(this).parent().remove();
                        });
                    });
                  </script>
    
         <?php
         echo '</div>'; // ends div#meta_inner
    } // ends function print_dynamic_reviews_metabox()
    
    
    function save_postdata_dynamic_reviews_metabox( $post_id ) {
      // verify if this is an auto save routine.
      // If it is our form has not been submitted, so we dont want to do anything
        if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
            return;
            }
    
        // Check permissions
        if ( 'film' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
        elseif ( !current_user_can( 'edit_post', $post_id )) { return $post_id;}
    
      // verify this came from the our screen and with proper authorization,
      // because save_post can be triggered at other times
        if (isset($_POST['reviews_noncename'])){
            if ( !wp_verify_nonce( $_POST['reviews_noncename'], 'reviews-nonce' ) )
                return;
        }else{return;}
    
        delete_post_meta( $post_id, 'reviews' );
        // OK, we're authenticated: we need to find and save the data
        $reviews = $_POST['reviews'];
        foreach ( $reviews as $review ) {
            add_post_meta($post_id,'reviews',$review);
        }
    } // ends function save_postdata_dynamic_reviews_metabox
    
    
    /* Prints the box content */
    function print_dynamic_screenings_metabox() {
    global $post;
    // Use nonce for verification
    
        echo '<input type="hidden" name="screenings_noncename" id="screenings_noncename" value="' . wp_create_nonce( 'screenings-nonce' ) . '" />';
        echo '<div id="meta_inner-screenings">';
        echo '<ol id="screenings-meta">';
    $reviews = get_post_meta($post->ID,'screenings'); //get any previously saved meta as an array so we can display it
    // print_r($reviews);
    $c = 0;
    if( is_array($screenings) ) {
      foreach($screenings as $screening ) {
                if (isset($screening['screening-festival-name']) || isset($screening['screening-festival-date']) ) {
          echo '
                        <li><span class="remove-screening" title="Delete">Remove</span>
                       <label><strong>Festival Name:</strong> <input type="text" class="meta-screening-festival-name saveddata" name="screenings['.$c.'][screening-festival-name]" value="'.$screening['screening-festival-name'].'" /></label>
                         <label><strong>Festival Date:</strong> <input type="text" class="meta-screening-festival-date saveddata" name="screenings['.$c.'][screening-festival-date]" value="'.$screening['screening-festival-date'].'" /></label>';
                    echo '<span class="remove-screening" title="Delete">Remove</span></li>';
          $c = $c +1;
          } // ends if isset $award[album]
        } // ends foreach
            } // ends if (is_array)
        echo '</ol>';
    echo '<span class="add-screening">Add New Screening</span>';
        ?>
            <script>
                var $ =jQuery.noConflict();
                $(document).ready(function() {
                    var count = <?php echo $c; ?>;
                    $(".add-screening").click(function() {
                        count = count + 1;
                        $('#screenings-meta').append('<li><span class="remove-screening" title="Delete">Remove</span> <label><strong>Festival Name:</strong> <input type="text" class="meta-screening-festival-name" name="screenings['+count+'][screening-festival-name]" value="" /></label> <label><strong>Festival Date:</strong> <input type="text" class="meta-screening-festival-date" name="screenings['+count+'][screening-festival-date]" value="" /></label> <span class="remove-screening" title="Delete">Remove</span>');
                        return false;
                    });
                    $(".remove-screening").live('click', function() {
                        $(this).parent().remove();
                    });
                });
              </script>
    
     <?php
     echo '</div>'; // ends div#meta_inner
    } // ends function print_dynamic_screenings_metabox()
    
    
    function save_postdata_dynamic_screenings_metabox( $post_id ) {
      // verify if this is an auto save routine.
      // If it is our form has not been submitted, so we dont want to do anything
        if ( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
            return;
            }
        // Check permissions
        if ( 'film' == $_POST['post_type'] ) { if ( !current_user_can( 'edit_page', $post_id )) { return $post_id; }}
        elseif ( !current_user_can( 'edit_post', $post_id )) { return $post_id;}
    
      // verify this came from the our screen and with proper authorization,
      // because save_post can be triggered at other times
        if (isset($_POST['screenings_noncename'])){
            if ( !wp_verify_nonce( $_POST['screenings_noncename'], 'screenings-nonce' ) )
                return;
        }else{return;}
    
        delete_post_meta( $post_id, 'screenings' );
        // OK, we're authenticated: we need to find and save the data
        $screenings = $_POST['screenings'];
        foreach ( $screenings as $screening ) {
            add_post_meta($post_id,'screenings',$screening);
        }
    } // ends function save_postdata_dynamic_reviews_metabox
    
  2. I believe the problem lies in the following code:

    if (isset($_POST['reviews-nonce'])){
        if ( !wp_verify_nonce( $_POST['reviews-nonce'], 'reviews-nonce' ) )
            return;
    }else{return;}
    

    Good job checking for the nonce; however, the nonce name is incorrect. In the HTML form, you specify:

    echo '<input type="hidden" name="reviews_noncename" id="reviews_noncename" value="' . wp_create_nonce( 'reviews-nonce' ) . '" />';
    

    The name of the nonce field is reviews_noncename not reviews-nonce. So, I believe you can correct the code within the save function to:

    if (isset($_POST['reviews_noncename'])){
        if ( !wp_verify_nonce( $_POST['reviews_noncename'], 'reviews-nonce' ) )
            return;
    }else{return;}
    

    I think this will fix your problem.