Search code examples
wordpresswordpress-rest-api

How to add a post via WordPress REST API with publish date in the future?


I want to add a new post to a WordPress site and set a publishing date in the future using the WordPress 4.7 REST API and bash, curl and the JSON-Tool "jq" on the client side. I have added the basic-auth plugin to WordPress so that I can use this type of authentication.

I initially upload the posting like this:

curl -s --location --basic --user 'name:password' \
     --url "https://<server>/wp-json/wp/v2/posts" \
     -d "title=Testpost" -d "content=This is a new post." | \
jq -r '"id: \(.id), date: \(.date), status: \(.status), title: \(.title.raw)"'

> id: 45, date: 2017-02-07T10:10:31, status: draft, title: Testpost

Ok, this is as expected. Let's see whether changing the post works:

curl -s --location --basic --user 'name:password' \
     --url "https://<server>/wp-json/wp/v2/posts/45" \
     -d "title=Other title" | \
jq -r '"date: \(.date), status: \(.status), title: \(.title.raw)"'

> date: 2017-02-07T10:20:32, status: draft, title: Other title

Ok, works.

Now I want to publish the post at 3 PM ("curl" parameters shrunk as they do not change):

curl ... "/wp-json/wp/v2/posts/45" \
     -d "date=2017-02-07T15:00:00" | \
jq -r '"id: \(.id), date: \(.date), status: \(.status)"'

> id: 45, date: 2017-02-07T10:29:32, status: draft

Nope. Date changed to the moment the request was issued, not the requested publishing date. Ok, perhaps status "pending"?

curl ... "/wp-json/wp/v2/posts/45" \
     -d "status=pending" -d "date=2017-02-07T15:00:00" | \
jq -r '"id: \(.id), date: \(.date), status: \(.status)"'

> id: 45, date: 2017-02-07T10:36:24, status: pending

Well, state is set to "pending", but date is still wrong. Perhaps explicitly setting the state to "future"?

curl ... "/wp-json/wp/v2/posts/45" \
     -d "status=future" -d "date=2017-02-07T15:00:00" | \
jq -r '"id: \(.id), date: \(.date), status: \(.status)"'

> id: 45, date: 2017-02-07T10:36:48, status: publish

WHAT??? What is happening here? Now, the post is published with the current date. Precisely what I did not want to have.

But if I now, with the published post, reissue the same request

curl ... "/wp-json/wp/v2/posts/45" \
     -d "status=future" -d "date=2017-02-07T15:00:00" | \
jq -r '"id: \(.id), date: \(.date), status: \(.status)"'

> id: 45, date: 2017-02-07T16:00:00, status: future

...it does the job - at least to some extent. Somehow, the time is interpreted as UTC, but finally the post is correctly set into "future" state.

Question 1: How do I get the post into this state without having it published in the first place?

Question 2: Why is the date interpreted as UTC? Wouldn't that be "date_gmt"?

What am I missing here?


Solution

  • Ok, I think I got the status stuff (question 1).

    Solution:

    You can change the publishing date to something in the future by setting the post status to private intermediately. The following two commands change the draft's publish date to some future point of time without a premature publishing:

    curl ... "/wp/v2/posts/45" -d "status=private"
    curl ... "/wp/v2/posts/45" -d "status=future" -d "date=2017-02-07T15:00:00"
    

    Explanation:

    Ok, and why is that? It's a feature of the WordPress core which prevents the intended operation. Let's dig through it:

    The REST API is implemented below wp-includes/rest-api/endpoints. For handling posts, the code is class-wp-rest-posts-controller.php. Updating a post is handled by update_item() beginning in line 670 (in WP 4.7.2).

    That function does not do that much. Mainly, it calls wp_update_post() in the WordPress core. That method is implemented in wp-includes/post.php, starting in line 3534 (sic!).

    Not far after the method start we find the troublesome lines of code:

    // Drafts shouldn't be assigned a date unless explicitly done so by the user.
    if ( isset( $post['post_status'] ) &&
         in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) &&
         empty($postarr['edit_date']) &&
         ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
            $clear_date = true;
    else
            $clear_date = false;
    

    And this is the problem: All data checked here are the already stored information of the post. Even if I transfer a new status future with my update request through the REST API, it is not evaluated at this place. $clear_date is set solely from the information in the database. And as our post is inserted as a draft and all the other conditions match, too, it will always be true which leads the method to drop all updates to the date fields some lines further. So, there is no way to change the publish date of the post as long as the post's state is one of draft, pending or auto-draft. The core simply overwrites all intended changes to the publishing date with what it feels is "right".

    The solution, as written at the beginning of this reply, is to change the post's state intermediately to private. That status does neither trigger any publishing actions nor is it on the "special handling list" of wp_update_posts(). Therefore, we can change the date in a second step - and then also update the state so that the post will be published.

    I feel that this is after all a bug. In my opinion, the critical part of wp_update_post() should take the new post status of an update into consideration and leave the new date untouched if the new state is (at least) one of published or future.