Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

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?

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


?>
share|improve this question

2 Answers 2

up vote 5 down vote accepted
+50

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
share|improve this answer
    
Wow - thats a very comprehensive answer. My code is obviously what you get from trying to adapt tutorials which walk you through code but don't explain why they use certain functions, etc... Thanks - will apply to my code now and come back to you :) –  Amanda Sep 7 '11 at 12:31
    
Incidentally, is it better to do the save_post stuff in 1 huge save_post function? –  Amanda Sep 7 '11 at 12:40
    
using 1 huge function or multiple functions hooked to save_post is totally your choice. For example I always divide my save_post into multiple functions depending on the type of information I am saving. –  Hameedullah Khan Sep 7 '11 at 13:15
    
@Amanda, if you want some more explanation about what's going on with creating and saving metaboxes, I recently wrote an article on the topic: wproots.com/complex-meta-boxes-in-wordpress. Hopefully, it can answer some of the why questions for you. –  tollmanz Sep 13 '11 at 2:30

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.

share|improve this answer
    
Unfortunately, it doesn't... –  Amanda Jul 19 '11 at 7:37
1  
I'm pretty sure that the nonce is the problem too. In the first part the nonce name is screenings_noncename but you check it as screenings-nonce. Replace all the returns with wp_die('reason') and you'll see for yourself –  onetrickpony Sep 7 '11 at 11:53

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.