So, I wanted to be able to detect whether a certain request was initiated by the block editor.
TL;DR: When the presence of the global REST_REQUEST
constant isn’t enough.
Some Context
My Share on Mastodon plugin used to rely on (only) the transition_post_status
hook to kick off a request to Mastodon’s API. (Good times!)
Before we do so, however, we look for a “checkbox”—$_POST['share_on_mastodon']
, to be exact. If it’s there, great, let’s share! If not, don’t do anything.
That’s how it worked.
Then Gutenberg came.
In the block editor, when you save a post, it sends a JSON object to WordPress’ REST API. The post gets stored, the transition_post_status
hook runs. No $_POST
variables. None.
No worries, though. The block editor, whenever a meta box is present, will submit a second request with these variables. Yay!
Except, we don’t really look for these variables to decide what to do. We look at previously stored values. This allows us to set them upfront, even programmatically, and then they’ll be there even when you schedule posts.
Right?
So now, when you’ve previously enabled sharing (e.g., when your post was still “draft”) and then, for whatever reason, you uncheck the “Share on Mastodon” checkbox right before clicking “Publish,” guess what happens.
Right. Goes right through, because the updated checkbox value isn’t known until after the post is actually published.
OK, we can deal with that. Just gotta detect whether a post was saved through the block editor, and if so, ignore the first (of two, hopefully) requests. (Since the plugin relies, or rather, relied, on a “classic” meta box, we knew there would always be a second request.)
That will allow us to, during that second request, process and store the latest and greatest values, and, subsequently, fire off our request to Mastodon’s API.
(But: if a post is saved from the classic editor, that first request must not be ignored; there will not be a second one.)
Searching for Block Editor Clues
So. No is_gutenberg()
.
But. There’s a use_block_editor_for_post_type()
. Awesome.
Except, it doesn’t … It … It checks whether a post type was registered with REST API support. And most post types are. I mean, it’s been proven to be unreliable.
use_block_editor_for_post()
? Uses the same underlying logic—with one exception: it returns false
during that second request mentioned above.
has_blocks()
? Only looks for something vaguely similar to blocks inside a post’s content. Useless for new posts.
Well, we can look for the global REST_REQUEST
constant. Except it’ll be true
for any REST request. (How is that an issue? Well, if you enabled the “Share Always” option and posted from a mobile app, and we subsequently ignored that request, nothing would be shared. That’s an issue.)
So, to exclude 3rd-party apps, we can look at the referrer URL. (Which I have at one point suggested as part of the solution to this issue.)
After all, a REST request coming from WP Admin must mean it came from the block editor. Well, not necessarily. (But inside a transition_post_status
callback? Highly likely.) Yet, privacy-minded users might have disabled referrer headers altogether.
get_current_screen()
I also came across a similar question on GitHub. Folks there suggested using get_current_screen()->is_block_editor()
, which I’m sure works great when an actual admin page is being loaded. You can’t use it in a REST controller, though. “Call to undefined function get_current_screen()
” and such.
Nonce
So, what’s left? A nonce? Each REST request is expected to come with a wp_rest
nonce. (Unless, of course, we’re authenticated using an auth token from an app or something, i.e., not logged into the web interface.)
Ah! So if the current request is, in fact, a REST request and there happens to be a valid wp_rest
nonce either as a GET or POST variable or through a request header: good chance this is a Gutenberg request!
After all, if it was a classic editor request, the REST_REQUEST
constant would be undefined. If it was a REST request initiated from a mobile app, there wouldn’t be a nonce.
By the Way
I have since added a “true” Gutenberg sidebar panel which doesn’t suffer from all of this. (Suffers from different issues, though. Plus, “duplicate” code. Lots of it, in fact.)
A Code Example
Uhm, so, this is what I’ve settled on for the time being. If you know of a more elegant solution, hit me up.
function my_is_gutenberg() {
if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ) {
return false;
}
$nonce = null;
if ( isset( $_REQUEST['_wpnonce'] ) ) {
$nonce = $_REQUEST['_wpnonce'];
} elseif ( isset( $_SERVER['HTTP_X_WP_NONCE'] ) ) {
$nonce = $_SERVER['HTTP_X_WP_NONCE'];
}
if ( null === $nonce ) {
return false;
}
return wp_verify_nonce( $nonce, 'wp_rest' );
}
Leave a Reply