Wordpress - Check for update vs new post on save_post action

Since WordPress version 3.7. - IIRC - the save_post hook - more information about the hook and its usage at Code Reference: save_post and Codex: save_post - has a third parameter $update which can be used to determine just that.

@param     int               $post_ID     Post ID.
@param     WP_Post     $post          Post object.
@param     bool            $update     Whether this is an existing post being updated or not.


Note:

$update is not always true – you can see and test it yourself with below code. It is not well documented though, possibly far from optimally named, and hence creates misleading expectations. Below code can be used for some debugging, play around with when to intercept code execution, because otherwise you won't see the information/messages. I think, the culprit in deceptive behavior is the handling of revisions and auto saves – which could be disabled, but I don't recommend it, and haven't tested it. Not sure if this warrants a Trac Ticket, so I didn't open one, if you think so, please follow the link and do it yourself. Aside from that, as stated in the comments, if you have a specific problem, post a new question.

add_action( 'save_post', 'debug_save_post_update', 10, 3 );
function debug_save_post_update( $ID, $post, $update ) {

  echo '<pre>';
  print_r( $post ); echo '<br>';
  echo '$update == ';
  echo $update ? 'true' : 'false';

  //conditions
  if( ! $update && $post->post_status == "auto-draft" ) {
    // applies to new post
    echo ' && $post->post_status == "auto-draft"';
    //die();
  } else if ( ! $update ) {
    // applies basically to the (auto saved) revision 
    //die();
  } else {
    // applies to updating a published post
    // when there is a revision, which is normally the case, 
    // standard behavior of WordPress, then it is considered 
    // an update, which is where the confusion sets in
    // there are other methods, like checking time or post status
    // depending on your use case it might be more appropriate 
    // to use one of those alternatives 
    //die();
  }

  echo '</pre>';
  //die();
}

The way I perform this check (within a hooked function) is to compare the post date and modified date (in GMT for standardisation)

function check_new_vs_update( $post_id ){
    $myPost        = get_post($post_id);
    $post_created  = new DateTime( $myPost->post_date_gmt );
    $post_modified = new DateTime( $myPost->post_modified_gmt );
    $diff          = $created->diff( $modified );
    $seconds_difference = ((($diff->y * 365.25 + $diff->m * 30 + $diff->d) * 24 + $diff->h) * 60 + $diff->i)*60 + $diff->s;

    if( $seconds_difference <= 1 ){
        // New post
    }else{
        // Updated post
    }
}
add_action('save_post', 'check_new_vs_update' );

This works because even at creation the post has a 'modified' date attached to it, which is exactly the same as the 'created' date, but we allow a variance of 1 second either way in case a second ticks over during the creation of the post.


I ended up just checking for the existence of a custom value prior to setting it. That way, if it's a newly created post the custom value would not yet exist.

function attributes_save_postdata($post_id) {
  if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
  if (!wp_verify_nonce($_POST['_attributes_noncename'], plugin_basename(__FILE__))) return;
  if ('page' == $_POST['post_type']) {
    if (!current_user_can('edit_page', $post_id)) return;
  } else {
    if (!current_user_can('edit_post', $post_id)) return;
  }
  $termid = get_post_meta($post_id, '_termid', true);
  if ($termid != '') {
    // it's a new record
    $termid = 'update';
  } else {
    // it's an existing record
  }
  update_post_meta($post_id, '_termid', $termid);
}
add_action('save_post', 'attributes_save_postdata');