New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
transition_post_status runs twice with same old and new status #15094
Comments
I've had this issue for as long as gutenberg rooled out too. In the end I used a mutex to make sure that only one of the two was taken into account. |
To show what is going on behind the scenes, I've made this little plugin:
With WP_DEBUG on, this will reveal the difference between the two passes that occur with Gutenberg and not with Classic Editor (or older WP versions). With Classic Editor on, when editing and saving an existing published post, the log entries will look like this (as expected) :
But with the block editor it's like this:
This shows that both passes have the same post old/new status, but the first pass does not carry any $_POST data (empty array). So simply testing for post status (old or new) does not allow us to distinguish between the two, nor can we rely on up-tot-date post data or (custom) post meta data as the first pass will fetch such data from the database, not from the post submission. It does show however, that we can work around the issue by polling for an empty $_POST array. Like:
|
Hmmm... come to think of it, that won't work for planned posts. A future post transitioning to published status shows these log messages:
Which means we cannot distinguish between the first Gutenberg API pass and a publication via WP Cron... We'd need something like an is_rest() flag here as suggested three years ago on https://core.trac.wordpress.org/ticket/34373 or manually test for
|
Improved test plugin, shows which flags are present during the different passes, and on different occasions:
|
Further testing shows that issue #12897 is indeed related: when switching a post from draft to publish, the first REST API pass will say This is completely useless when we need the actual posted data (not what is stored as draft in the DB) on an new publication (not a post edit) :'( |
I'm also having the same issue @drov0, |
This was also reported here: https://core.trac.wordpress.org/ticket/47548 |
I can confirm that this happens but am not yet clear on why. We'll need to do some more digging. It could be due to the extra HTTP request made by saving meta boxes. Maybe this second request happens so soon after the first that the database hasn't had a chance to update, yet. |
@noisysocks looking forward to your findings |
Editing a post with the test plugin above on WordPress 5.3-alpha-46194, I notice something has changed. Now, only two REST requests are logged. One publish>publish with the full post object as $post (not passed through $_POST) and a second new>inherit which is a revision... Tested updating from Quick Edit and cron passing a future post as well and in all cases, the logged data seems coherent. My preliminary conclusion : this can be marked as solved for 5.3 Can anyone confirm this? Note: when using transition actions, do NOT rely on $_POST data because that only gets populated when submitting from the Quick Edit post list. Instead, get the third function parameter $post. |
Oh, hang on... I'm mistaken. As soon as there is a plugin active that adds meta boxes to the post edit page, the double passes start appearing again. One REST request without $_POST data (as above) followed by a second pass with $_POST data. Only on the second, the submitted meta box data is passed. Apparently, this can only be done with a regular $_POST request outside of Gutenberg hence the need for the second action run... So I guess the issue remains unaddressed. Or do I need to look into how meta boxes should be hooking into the new editor REST request to avoid the need for this second $_POST request? |
In fact, even the WordPress internal Custom Fields option will create a traditional meta box which in turn causes these double transition_post_status calls... So @noisysocks and @rgomezp it is indeed the extra request to save meta box data. And to distinguish between the two, I only see the REST_REQUEST test... |
In my case, it's still happening on 5.3 |
Do you see any non-core metaboxes on your post edit screen? Either at the bottom of the center/content area or at the bottom of the right side under the Document tab? Or maybe you have the Custom Fields meta box activated? In those cases, there will be two post submissions (one via REST and one traditional post request) and therefore two transition_post_status passes. Without the additional (second!) pass, WordPress would not be able to save the post meta data. Currently, as far as I can figure, the only way to distinguish between the two passes is to either test for the REST_REQUEST constant (if it's set and true, it's a REST request) or the $_POST global (if it's empty, it's probably a REST request) or a combination of these... Whatever you need to make it work :) Just know: the first pass with REST_REQUEST set, has an empty $_POST and no meta data, the second pass, that normally happens about one second later (I suppose only after successful REST respons), has $_POST holding the new meta box data. |
This is related to #12903. I'm having a similar issue, where my code may need to run twice if there are postmeta updates coming in from metabox save, but it's not possible to tell from the REST request whether there will be a second request or not. There is no way to tell when a post is "done" saving. |
@kevinfodness indeed, that's entirely unknown :/ |
Any update? |
Hi @rgomezp et all, Why this happens? What can you do as plugin or theme dev?
Now if you have your own custom meta box and want to save its data on
But if you are doing something else on transition post status, and you cannot be sure there is a second pass going to happen (because that depends on other plugins adding meta boxes or not), then I suggest something more complex like this:
This should make sure your code runs on every case, and only once. Only thing is that $_POST will not be always available. If you depend on $_POST data then you'll have to find another way to fetch your data, like extract it from $post data or fetch it from the database. $_POST It simply is not there anymore in the new block editor except when there is a custom meta box... (Hint: you could always force the new editor to use a second pass by adding your own custom meta box... but that's not a very elegant solution.) |
@RavanH thanks so much for such a detailed response! |
This is very nice RavanH. |
Add this code in callback function: if ( ! empty( $_REQUEST['meta-box-loader'] ) ) { // phpcs:ignore
return;
} |
@mihdan Nice! I was not aware of 'meta-box-loader' (available since WP 5.0 it seems?)... How does that request parameter behave when someone is using Classic Editor for example? |
Could you clarify what the purpose of the transient is, and how you arrived at 10 as a sensible value for lifespan? This is very nice, it seems to work perfectly, but I want to understand it more. |
Great! Thanks for that :)!! |
The transient is used to know if the ghi15094_my_updater was run before or not. This would happen if ghi15094_transition_action is called twice, frist by the REST request (with the REST_REQUEST constant) and then shortly after that by a possible legacy request (to accommodate plugins or theme). I just assume the time between these passes should be no longer than 10 seconds. I'm also assuming that there would not be a second update post action within 10 seconds. Hence that transient timeout. But that is based on 2 assumptions, I admit... |
thank you, you saved me time, it took me 2 days to process it :( |
Describe the bug
When submitting a post update or an new post, it appears the transition_post_status hook(s) get called twice. First without the is_admin flag, then with is_admin passing true. The result may be unexpected as actions may behave differently or different actions may be included in both runs.
Please note this behavior is not seen with the classic editor.
Related issue #12897
To reproduce
I'll write a quick test plugin to show what is happening and how it may affect behavior...
The text was updated successfully, but these errors were encountered: