Conflicting save_post functions when passing the post id and saving custom meta boxes for different post types

Post save functions are conflicting with each other when adding them to the save_post action hook.

2 different custom post types with 2 different (one for each post type) custom meta boxes.

Read More

I’m only including the code for 1 of the meta boxes. The other one is very similar and each one works fine separately but not together.

The ‘register_metabox_cb’ call back function:

function c3m_video_meta() {
    add_meta_box('_c3m_video_embed', __('Enter Video Embed Code In the Box Below') , 'c3m_video_embed', 'video', 'normal', 'low');
    }

Adding the meta box to the post edit screen:

function c3m_video_embed($post) {
    global $post;
    wp_nonce_field(__FILE__,'video_nonce');
    $embed-code = get_post_meta($post->ID, '_embed-code', true);
    echo '<input type="text" name="_embed-code" value=""' . $embed-code . '" class="widefat" />' ; 
    }

The save function:

  function c3m_save_video_meta( $post_id , $post ) { 

            if ( !wp_verify_nonce( $_POST [ 'video_nonce' ], __FILE__ ) ) { return $post ->ID; 
            }
            if ( !current_user_can( 'edit_post' , $post ->ID )) return $post ->ID; 
            $c3m_video_embed-code [ '_embed-code' ] = $_POST [ '_embed-code' ]; 
                        foreach ( $c3m_video_embed-code as $key => $value ) { 
                        if ( $post ->post_type == 'revision' ) return ; 

                        $value = implode( ',' , ( array ) $value );
                        if (get_post_meta( $post ->ID, $key , FALSE)) { 
                        update_post_meta( $post ->ID, $key , $value ); } else { 
                        add_post_meta( $post ->ID, $key , $value ); } if (! $value ) delete_post_meta( $post ->ID, $key ); 
                        }
}

The action hooks:

add_action( 'admin_menu' , 'c3m_video_meta' );

add_action( 'save_post' , 'c3m_save_video_meta' , 1, 2);

This is fine and dandy and works but when I add another meta box to a different post type and use a similar function (different function name, nonce name and key ) when the post is saved I get this error:

Notice: Undefined index: _embed-code
in
/Applications/MAMP/htdocs/wordpress/wp-content/plugins/ieew-custom-functions/ieew-custom-functions.php
on line 181

Warning: Cannot modify header
information – headers already sent by
(output started at
/Applications/MAMP/htdocs/wordpress/wp-content/plugins/ieew-custom-functions/ieew-custom-functions.php:181)
in
/Applications/MAMP/htdocs/wordpress/wp-includes/pluggable.php
on line 897

The undefined _embed-code is when saving the post that does not contain the _embed-code variable.

Since the error messages are reversed depending on wich post type I try to save it leads me to believe that both (2 different) save functions are being added to the save_post action. They are also being added when saving a normal post. If I’m only using 1 of the save functions there are no errors when saving a normal post.

Rather than this question being a “Fix My Code” question I would rather the answer contain the how and why of adding custom meta boxes and using the various nonce methods. Sure I could use the More Fields plugin but I would rather learn the best way to accomplish customizing the post edit screen with custom content types.

I have used this same code and method to add multiple meta boxes to a single custom post type and it has always worked fine.

Related posts

Leave a Reply

3 comments

  1. After doing some more research I found that:

    • Instead of hooking the add_meta_box function into admin_menu it should be hooked into add_meta_boxes
    • Instead of the foreach loop use the update_post_meta function on the save function
    • Instead of using the wp_nonce_field the data can be sanitized using esc_attr and strip_tags
    • To pass the post id to the save meta box function you don’t need to include the additional $post variable
    • You have to add a conditional state for the post type on the save function
    • You have to call global $post on the save function

    The new and much simpler code for adding both meta boxes:

    add_action( 'add_meta_boxes', 'c3m_sponsor_meta' );
    
    function c3m_sponsor_meta() {
        add_meta_box( 'c3m_sponsor_url', 'Sponsor URL Metabox', 'c3m_sponsor_url', 'sponsor', 'side', 'high' );
    }
    
    function c3m_sponsor_url( $post ) {
        $sponsor_url = get_post_meta( $post->ID, '_sponsor_url', true);
        echo 'Please enter the sponsors website link below';
        ?>
            <input type="text" name="sponsor_url" value="<?php echo esc_attr( $sponsor_url ); ?>" />
        <?php
    }
    
    add_action( 'save_post', 'c3m_save_sponsor_meta' );
    
    function c3m_save_sponsor_meta( $post_id ) {
        global $post;
        if( $post->post_type == "sponsor" ) {
            if (isset( $_POST ) ) {
                update_post_meta( $post_ID, '_sponsor_url', strip_tags( $_POST['sponsor_url'] ) );
            }
        }
    }
    
    add_action( 'add_meta_boxes', 'c3m_video_meta' );        
    
    function c3m_video_meta() {
        add_meta_box( 'c3m_video_embed_code', 'Video Embed Code Meta Box', 'c3m_video_embed_code', 'video', 'normal', 'high' );
    }
    
    function c3m_video_embed_code( $post ) {
        $embed_code = get_post_meta( $post->ID, '_embed_code', true);
        echo 'Please enter the video embed code below';
        ?>
            <input type="text" name="embed_code" value="<?php echo esc_attr( $embed_code ); ?>" />
        <?php
    }
    
    add_action( 'save_post', 'c3m_save_video_meta' );
    
    function c3m_save_video_meta( $post_id ) {
        global $post;
        if( $post->post_type == "video" ) {
            if (isset( $_POST ) ) {
                update_post_meta( $post_ID, '_embed_code', strip_tags( $_POST['embed_code'] ) );
            }
        }
    }
    
  2. Instead of using the wp_nonce_field the data can be sanitized using esc_attr and strip_tags

    I don’t follow the logic of escaping data in place of using a nonce? the example given on wordpress.org uses a nonce to verify intention, and also escapes the data before inserting it. how are those two things related?

  3. you should not need to global $post, you definitely can pass it into the function.

    i had 2 different save_post actions, one for quick edit and one for a metabox on the same post type and i ended up combining them to get rid of undefined index notices about my different nonces. since the 2 items were covering the same data i used the same nonce generating name.

    anyway, i would consider combining your save_post functions into 1… OR doing a check on the post type before you check the nonce.

    add_action( 'save_post' , 'c3m_save_meta' , 20, 2); //moved priority to later. you had priority 1 so is possible that WP actions were happening after your code
    
    function c3m_save_meta( $post_id , $post ) {   
    
       // 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 $post_id;
    
       //don't save if only a revision
       if ( $post->post_type == 'revision' ) return $post_id;   
    
       // Check permissions
       if ( 'page' == $post->post_type ) {
        if ( !current_user_can( 'edit_page', $post_id ) ) return $post_id;
       } else {
        if ( !current_user_can( 'edit_post', $post_id ) ) return $post_id;
       }      
    
      //save video post meta
      if( $post->post_type == "video" && wp_verify_nonce( $_POST [ 'video_nonce' ], __FILE__ )) {
         if (isset( $_POST['_embed_code'] ) ) {
            update_post_meta( $post_ID, '_embed_code', esc_attr( $_POST['embed_code'] ) );
         }
      }
    
      //save sample bacon post meta
       if( $post->post_type == "bacon" && wp_verify_nonce( $_POST [ 'bacon_nonce' ], __FILE__ )) {
          if (isset( $_POST['_bacon_code'] ) ) {
            update_post_meta( $post_ID, '_bacon_code', esc_attr( $_POST['bacon_code'] ) );
          }
       }
    }
    

    or you could use the same nonce name for both metaboxes. can’t comment on the security of this, but seems ok to me as WP appears to do the same thing with _wpnonce in both quick edit and regular edit modes. WP also doesn’t seem to have a separate nonce for every metabox.

    function c3m_save_meta( $post_id , $post ) {   
           if(!wp_verify_nonce( $_POST [ 'c3m_nonce' ], __FILE__ )) return $post_id; //change both nonces to name=c3m_nonce
    
           // 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 $post_id;  
    
           //don't save if only a revision
           if ( $post->post_type == 'revision' ) return $post_id; 
    
           // Check permissions
           if ( 'page' == $post->post_type ) {
            if ( !current_user_can( 'edit_page', $post_id ) ) return $post_id;
           } else {
            if ( !current_user_can( 'edit_post', $post_id ) ) return $post_id;
           }      
    
          //save video post meta
          if( $post->post_type == "video") {
             if (isset( $_POST['_embed_code'] ) ) {
                update_post_meta( $post_ID, '_embed_code', esc_attr( $_POST['embed_code'] ) );
             }
          }
    
          //save sample bacon post meta
           if( $post->post_type == "bacon" && wp_verify_nonce( $_POST [ 'bacon_nonce' ], __FILE__ )) {
              if (isset( $_POST['_bacon_code'] ) ) {
                update_post_meta( $post_ID, '_bacon_code', esc_attr( $_POST['bacon_code'] ) );
              }
           }
        }