Skip to content

Instantly share code, notes, and snippets.

@n7studios
Last active March 9, 2024 10:47
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save n7studios/56fd05f19f5da26f19f6da0ccb57b144 to your computer and use it in GitHub Desktop.
Save n7studios/56fd05f19f5da26f19f6da0ccb57b144 to your computer and use it in GitHub Desktop.
Determine Post Publish / Update via Classic Editor, Gutenberg and REST API
<?php
/**
* Example class implementation to perform actions, such as sending a Post
* to a third party API or service, when the Post is published or updated through:
* - Classic Editor
* - Gutenberg
* - REST API
*
* @package Post_To_Social
* @author Tim Carr
* @version 1.0.0
*/
class Post_To_Social {
/**
* Constructor
*
* @since 1.0.0
*/
public function __construct() {
// Actions
add_action( 'wp_loaded', array( $this, 'register_publish_hooks' ), 1 );
}
/**
* Registers publish hooks against all public Post Types,
*
* @since 1.0.0
*/
public function register_publish_hooks() {
add_action( 'transition_post_status', array( $this, 'transition_post_status' ), 10, 3 );
}
/**
* Fired when a Post's status transitions.
*
* Called by WordPress when wp_insert_post() is called.
*
* As wp_insert_post() is called by WordPress and the REST API whenever creating or updating a Post,
* we can safely rely on this hook.
*
* @since 1.0.0
*
* @param string $new_status New Status
* @param string $old_status Old Status
* @param WP_Post $post Post
*/
public function transition_post_status( $new_status, $old_status, $post ) {
// Bail if the Post Type isn't public
// This prevents the rest of this routine running on e.g. ACF Free, when saving Fields (which results in Field loss)
$post_types = array( 'post', 'page' );
if ( ! in_array( $post->post_type, $post_types ) ) {
return;
}
// Bail if we're working on a draft or trashed item
if ( $new_status == 'auto-draft' || $new_status == 'draft' || $new_status == 'inherit' || $new_status == 'trash' ) {
return;
}
/**
* = REST API =
* If this is a REST API Request, we can't use the wp_insert_post action, because any metadata
* included in the REST API request is *not* included in the call to wp_insert_post().
*
* Instead, we must use a late REST API action that gives the REST API time to save metadata.
*
* Thankfully, the REST API supplies an action to do this: rest_after_insert_posttype, where posttype
* is the Post Type in question.
*
* Note that any meta being supplied in the REST API Request MUST be registered with WordPress using
* register_meta(). If you're using a third party plugin to register custom fields, you'll need to
* confirm it uses register_meta() as part of its process.
*
* = Gutenberg =
* If Gutenberg is being used on the given Post Type, two requests are sent:
* - a REST API request, comprising of Post Data and Metadata registered *in* Gutenberg,
* - a standard request, comprising of Post Metadata registered *outside* of Gutenberg (i.e. add_meta_box() data)
*
* If we're publishing a Post, the second request will be seen by transition_post_status() as an update, which
* isn't strictly true.
*
* Therefore, we set a meta flag on the first Gutenberg REST API request to defer acting on the Post until
* the second, standard request - at which point, all Post metadata will be available to the Plugin.
*
* = Classic Editor =
* Metadata is included in the call to wp_insert_post(), meaning that it's saved to the Post before we use it.
*/
// Flag to determine if the current Post is a Gutenberg Post
$is_gutenberg_post = $this->is_gutenberg_post( $post );
// If a previous request flagged that an 'update' request should be treated as a publish request (i.e.
// we're using Gutenberg and request to post.php was made after the REST API), do this now.
$needs_publishing = get_post_meta( $post->ID, '_needs_publishing', true );
if ( $needs_publishing ) {
// Run Publish Status Action now
delete_post_meta( $post->ID, '_needs_publishing' );
add_action( 'wp_insert_post', array( $this, 'wp_insert_post_publish' ), 999 );
// Don't need to do anything else, so exit
return;
}
// If a previous request flagged that an update request be deferred (i.e.
// we're using Gutenberg and request to post.php was made after the REST API), do this now.
$needs_updating = get_post_meta( $post->ID, '_needs_updating', true );
if ( $needs_updating ) {
// Run Publish Status Action now
delete_post_meta( $post->ID, '_needs_updating' );
add_action( 'wp_insert_post', array( $this, 'wp_insert_post_update' ), 999 );
// Don't need to do anything else, so exit
return;
}
// Publish
if ( $new_status == 'publish' && $new_status != $old_status ) {
/**
* Classic Editor
*/
if ( ! defined( 'REST_REQUEST' ) || ( defined( 'REST_REQUEST' ) && ! REST_REQUEST ) ) {
add_action( 'wp_insert_post', array( $this, 'wp_insert_post_publish' ), 999 );
// Don't need to do anything else, so exit
return;
}
/**
* Gutenberg Editor
* - Non-Gutenberg metaboxes are POSTed via a second, separate request to post.php, which appears
* as an 'update'. Define a meta key that we'll check on the separate request later.
*/
if ( $is_gutenberg_post ) {
update_post_meta( $post->ID, '_needs_publishing', 1 );
// Don't need to do anything else, so exit
return;
}
/**
* REST API
*/
add_action( 'rest_after_insert_' . $post->post_type, array( $this, 'rest_api_post_publish' ), 10, 2 );
// Don't need to do anything else, so exit
return;
}
// Update
if ( $new_status == 'publish' && $old_status == 'publish' ) {
/**
* Classic Editor
*/
if ( ! defined( 'REST_REQUEST' ) || ( defined( 'REST_REQUEST' ) && ! REST_REQUEST ) ) {
add_action( 'wp_insert_post', array( $this, 'wp_insert_post_update' ), 999 );
// Don't need to do anything else, so exit
return;
}
/**
* Gutenberg Editor
* - Non-Gutenberg metaboxes are POSTed via a second, separate request to post.php, which appears
* as an 'update'. Define a meta key that we'll check on the separate request later.
*/
if ( $is_gutenberg_post ) {
update_post_meta( $post->ID, '_needs_updating', 1 );
// Don't need to do anything else, so exit
return;
}
/**
* REST API
*/
add_action( 'rest_after_insert_' . $post->post_type, array( $this, 'rest_api_post_update' ), 10, 2 );
// Don't need to do anything else, so exit
return;
}
}
/**
* Helper function to determine if the Post is using the Gutenberg Editor.
*
* @since 1.0.0
*
* @param WP_Post $post Post
* @return bool Post uses Gutenberg Editor
*/
private function is_gutenberg_post( $post ) {
// This will fail if a Post is created or updated with no content and only a title.
if ( strpos( $post->post_content, '<!-- wp:' ) === false ) {
return false;
}
return true;
}
/**
* Called when a Post has been Published via the REST API
*
* @since 1.0.0
*
* @param WP_Post $post Post
* @param WP_REST_Request $request Request Object
*/
public function rest_api_post_publish( $post, $request ) {
$this->wp_insert_post_publish( $post->ID );
}
/**
* Called when a Post has been Published via the REST API
*
* @since 1.0.0
*
* @param WP_Post $post Post
* @param WP_REST_Request $request Request Object
*/
public function rest_api_post_update( $post, $request ) {
$this->wp_insert_post_update( $post->ID );
}
/**
* Called when a Post has been Published
*
* @since 1.0.0
*
* @param int $post_id Post ID
*/
public function wp_insert_post_publish( $post_id ) {
// Call main function
$this->send( $post_id, 'publish' );
}
/**
* Called when a Post has been Updated
*
* @since 1.0.0
*
* @param int $post_id Post ID
*/
public function wp_insert_post_update( $post_id ) {
// Call main function
$this->send( $post_id, 'update' );
}
/**
* Main function. Called when any Page, Post or CPT is published or updated
*
* @since 1.0.0
*
* @param int $post_id Post ID
* @param string $action Action (publish|update)
* @return mixed WP_Error | API Results array
*/
public function send( $post_id, $action ) {
// Get Post
global $post;
$post = get_post( $post_id );
if ( ! $post ) {
return new WP_Error( 'no_post', sprintf( __( 'No WordPress Post could be found for Post ID %s' ), $post_id ) );
}
// @TODO Save any metadata that your Plugin expects now - such as post-specific settings your Plugin may offer via add_meta_box() calls
update_post_meta( $post_id, 'your-key', sanitize_text_field( $_POST['your-key'] ) );
// @TODO Add your code here to send your Post to whichever API / third party service
}
}
@marijnbent
Copy link

marijnbent commented Feb 1, 2022

If anyone reads this in 2022; I ended up adding a nonce to the metabox for the classic editor. On the save_post action, I check if this nonce exists to save the metabox value. Otherwise, I ignore it and handle it in JS for Gutenberg metaboxes.

Edited example:

//add_action('add_meta_boxes', [$this, 'addMetaboxToClassicEditor'])
    public function addMetaboxToClassicEditor()
    {
            add_meta_box(
                'MyId',
                'MyTitle',
                [$this, 'myHtml'],
                'your-post-type',
                'side',
                'default',
                [
                    '__back_compat_meta_box' => true, //removes the metabox from the block editor.
                ]
            );
    }

//add_action('save_post', [$this, 'myNonceKey'])
    public function saveClassicMetaboxPostMeta($postId)
    {
        if (array_key_exists('myNonceKey', $_POST)) {
            if (wp_verify_nonce($_POST['myNonceKey'], 'save_post')) {
                update_post_meta(
                    $postId,
                    $this->metaKey,
                    array_key_exists($this->metaKey, $_POST)
                );
            }
        }
    }

    public function myHtml($post)
    {        
        wp_nonce_field('save_post', 'myNonceKey');
        ?>
        <input type="checkbox" id="<?php echo $this->metaKey; ?>" name="<?php echo $this->metaKey; ?>"
               value="1" <?php echo boolval($value) ? 'checked' : ''; ?>>
        <label for="<?php echo $this->metaKey; ?>"My Label</label>
        <?php
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment