4

I'm saving my CPT in private status. So the transition would be from pending to private. Thing is, when the post was first submitted and is pending there's a date of that submission on the post_date field in db. But when the post got published, the date updated with the current date.

I want to keep the original date of the submission of the post even the post privately published later.

So I did something like below:

function mycpt_keep_pending_date_on_publishing( $new_status, $old_status, $post ) {
    if( 'mycpt' === $post->post_type && 'pending' === $old_status && 'private' === $new_status ) :
        $pending_datetime = get_post_field( 'post_date', $post->ID, 'raw' );

        // Update the post  
        $modified_post = array(  
            'ID'            => $post->ID,  
            'post_date'     => $pending_datetime,
            'post_date_gmt' => get_gmt_from_date( $pending_datetime )
        );  

        // Update the post into the database  
        wp_update_post( $modified_post );

    endif;
}

add_action( 'transition_post_status', 'mycpt_keep_pending_date_on_publishing' );

But it's not working. What can be the reason?

2
  • Just FYI, there is {$old_status}_to_{$new_status} hook. If I were to make a wild guess, I would think that the post_date gets updated on publish before it actually gets set to private.
    – Howdy_McGee
    Commented Apr 3, 2017 at 17:19
  • Tried pending_to_publish and pending_to_private. Tried updating the post from front end with my code and from admin end with core code. No luck. :( Commented Apr 3, 2017 at 17:44

2 Answers 2

3

@Howdy_McGee is on the right track in his comment: by the time transition_post_status is fired, the post has already been updated (i.e., written to the db).

What you need to do is hook into wp_insert_post_data, instead of transition_post_status, as follows:

add_filter ('wp_insert_post_data', 'mycpt_keep_pending_date_on_publishing', 10, 2) ;

function
mycpt_keep_pending_date_on_publishing ($data, $postarr)
{
    if ($data['post_type'] != 'mycpt') {
        return ($data) ;
        }

    // this check amounts to the same thing as transition_post_status(private, pending)
    if ('private' != $data['post_status'] || 'pending' != $postarr['original_post_status']) {
        return ($data) ;
        }

    $pending_datetime = get_post_field ('post_date', $data['ID'], 'raw') ;

    $data['post_date'] = $pending_datetime ;
    $data['post_date_gmt'] = get_gmt_from_date ($pending_datetime) ;

    return ($data) ;
}

Note: I used the same function name as you did, but the body of that function is different.

6
  • I would suggest to hook above filter callback inside the wp_insert_post_data hook, but remove it again during the transitioning hook to make sure it only runs once. There are plenty of example here on this site for this.
    – kaiser
    Commented Apr 3, 2017 at 20:57
  • @kaiser in case it wasn't clear, I'm suggesting the above code instead of, not in addition to, hooking into transition_post_status. I edited my answer to make that a little clearer. Commented Apr 3, 2017 at 21:13
  • I know. And I suggested to remove the callback right inside the transition hook, so it does not interfere with any later code. There might be other things like plugins, that modify post data as well and one should be sure that you only do your task once. Take a look at this answer for an example.
    – kaiser
    Commented Apr 3, 2017 at 22:03
  • Added your code, it's working fine if the post is updated from the admin panel. But if it's updated from the front end, even changes like this isn't working. Could you please shed some light there? Commented Apr 4, 2017 at 18:48
  • I just did a quick scan of of your code on github. Adding 'original_post_status' => 'pending' to line 771 of includes/ns-functions.php should do the trick. That is passed to wp_update_post() when a post is updated on the backend and is used in the code in my answer to mimic transition_post_status. Commented Apr 4, 2017 at 20:27
2

In addition to the other answers, here's a small plugin that makes sure the code is only executed once. In case the data is reset by a plugin running later, try using PHP_INT_MAX -1 as priority (not for publicly distributed plugins). In this case, you will have to set the same value for remove_filter(), else the callback will not get removed.

<?php
/**
 * Plugin Name: (WPSE) Static post date
 * Description: Keep the post date as the original date for posts published as private
 */
namespace WPSE;

add_filter( 'wp_insert_post_data', '\WPSE\save', 10, 2 );
function save( $post, $raw ) {
    if ( ! in_array( $post['post_status'], [ 'private', 'pending', ] ) ) {
        return $post;
    }
    if ( 'your_post_type' !== $post['post_type'] ) {
        return $post;
    }

    $date = get_post_field( 'post_date', $post['ID'], 'raw' );
    $post['post_date'] = $date;
    $post['post_date_gmt'] = get_gmt_from_date( $date );

    return $post;
}

add_action( 'transition_post_status', function() {
    # Make sure above callback is only triggered once
    remove_filter( 'wp_insert_post_data', '\WPSE\save' );
} );

IIRC your main problem with above code is, that the private_to_published filter was deprecated, so there is nothing specific enough aside from a post type status filter. Try the following plugin and see if it works (if your post type really is named mycpt):

<?php
/* Plugin Name: (WPSE) Test post type status actions */
add_action( 'private_mycpt', function( $ID, \WP_Post $post ) {
    var_dump( current_filter() );
    exit;
}, 10, 2 );
4
  • 1
    now I see what you were talking about. But I don't think your code will make sure that the code hooked into wp_insert_post_data is only executed once because there any number of hooks fired after the post is updated which could trigger another call to wp_insert_post(), and hence, another firing of wp_insert_post_data and you couldn't possibly enumerate them all (to hook into them and remove the func hooked to wp_insert_post_data). Commented Apr 4, 2017 at 0:41
  • @Paul'SparrowHawk'Biron The code gets removed as soon as there is a transition. With the arguments (that I have taken from your answer), we can make quite sure that the modifications the callback triggers are limited to very few cases. Anyway, you are not completely off with your comment: While the callback gets removed and not added more than once, the case in which it gets removed probably is not the right one. It might trigger in some other cases as well. And the callback gets removed in other cases as well.
    – kaiser
    Commented Apr 5, 2017 at 7:18
  • This code won't work. The code will always return early. The line if ( ! in_array( $post['post_status'], [ 'private', 'pending', ] ) ) will always return true when clicking "Publish" because $post['post_status'] refers to the new post_status and not the previous post_status. Also the other code snippet from Paul has the same issue. He calls original_post_status which doesn't exist in Wordpress. His code seems to work but only because original_post_status doesn't exist and thus returns false. None of the answers in this entire thread are correct and no one cared to double check.
    – Kevin M
    Commented May 20, 2023 at 8:47
  • @KevinM This answer is 5 years old. I am very sure it worked back then just fine. If you found out that something does not work and know how to fix it, then please file an edit and contribute your knowledge. This is how this site works: Everyone has edit rights and chooses to contribute. And so should you. Good luck and thanks for contributing in advance!
    – kaiser
    Commented May 22, 2023 at 17:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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