1

I have a custom post type which checks and validates some (custom) meta fields added with it upon publishing. I am using wp_insert_post_data for the purpose:

public function __construct()
{
    $this->sconfig= ['post_type'=> 'event', 'slug'=>'events'];
    add_filter('wp_insert_post_data', array($this, 'validate_event_meta'), 99, 2);
    add_filter('post_updated_messages', array($this, 'event_save_update_msg'), 10, 1);
    //add_action('admin_notices', array($this, 'event_save_update_msg'));
}

function validate_event_meta($postData, $postArray)
{
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
        return $postData;
    }
    if (array_key_exists('post_type', $postData) && $postData['post_type'] === $this->sconfig['post_type']) {
        if (array_key_exists('post_status', $postData) && $postData['post_status'] === 'publish') {
            $valstat = $this->get_meta_posted_vals();
            /*
            $valstat['stat'] is 0 or 1 depending on validation success
            $valstat['log'] has the error message
            */
            if ($valstat['stat'] == 0) {
                $postData['post_status'] = 'draft';
                set_transient(get_current_user_id() . '_event8273_add_notice', 'ERROR: ' . $valstat['log']);
                add_filter('redirect_post_location', array($this, 'alter_event_save_redirect'), 99);
            }
         }
    }
    return $postData;
}

function alter_event_save_redirect($location)
{
    remove_filter('redirect_post_location', __FUNCTION__, 99);
    $location = remove_query_arg('message', $location);
    $location = add_query_arg('message', 99, $location);
    return $location;
}

function event_save_update_msg($messages)
{
    $message = get_transient(get_current_user_id() . '_event8273_add_notice');
    if ($message) {
        delete_transient(get_current_user_id() . '_event8273_add_notice');
        //echo $message;
        //$messages['post'][99] = $message;
        $messages[$this->sconfig['post_type']][99] = $message;
    }
    return $messages;
}

Though the validation system is working correctly, I cannot display any notices on the error. Each time the code encounters invalid meta value during 'publish', it reverts the post into 'draft' status and the 'Draft Saved Preview' message pops up.

Upon some research, I have found that the block editor uses javascript to display custom notices. But what I cannot understand is how to call the javascript function (file already enqueued in admin) after the validation from wp_insert_post_data.

function event_save_alert(errmsg)
{
    ( function ( wp ) {
        wp.data.dispatch( 'core/notices' ).createNotice(
            'error', // Can be one of: success, info, warning, error.
            errmsg, // Text string to display.
            {
                isDismissible: true, // Whether the user can dismiss the notice.
                // Any actions the user can perform.
                actions: [
                    {
                        url: '#',
                        label: 'View post',
                    },
                ],
            }
        );
    } )( window.wp );
}

Any kind of help is appreciated. Thanks for reading this far and giving it a thought.

2 Answers 2

3

what I cannot understand is how to call the javascript function (file already enqueued in admin) after the validation from wp_insert_post_data

You can use the same approach that Gutenberg uses for saving meta boxes (or custom fields), which you can find here on GitHub. It basically uses wp.data.subscribe() to listen to changes in the editor's state (e.g. whether the editor is saving or autosaving the current post) and after the post is saved (but not autosaved), the meta boxes will be saved.

As for the PHP part, you can just store the error in a cookie and read its value using the window.wpCookies API, which is a custom JavaScript cookie API written by WordPress.

So for example, I used the following when testing:

  • PHP: setcookie( 'event8273_add_notice', 'ERROR: A test error ' . time(), 0, '/' );
  • JS: wpCookies.get( 'event8273_add_notice' )

And for checking for and displaying the error, I used this:

( () => {
    const editor = wp.data.select( 'core/editor' );

    // Name of the cookie which contains the error message.
    const cookieName = 'event8273_add_notice';

    // Set the initial state.
    let wasSavingPost     = editor.isSavingPost();
    let wasAutosavingPost = editor.isAutosavingPost();

    wp.data.subscribe( () => {
        const isSavingPost     = editor.isSavingPost();
        const isAutosavingPost = editor.isAutosavingPost();

        // Display the error on save completion, except for autosaves.
        const shouldDisplayNotice =
            wasSavingPost &&
            ! wasAutosavingPost &&
            ! isSavingPost &&
            // If its status is draft, then maybe there was an error.
            ( 'draft' === editor.getEditedPostAttribute( 'status' ) );

        // Save current state for next inspection.
        wasSavingPost     = isSavingPost;
        wasAutosavingPost = isAutosavingPost;

        if ( shouldDisplayNotice ) {
            const error = wpCookies.get( cookieName );

            // If there was an error, display it as a notice, and then remove
            // the cookie.
            if ( error ) {
                event_save_alert( error );
                wpCookies.remove( cookieName, '/' );
            }
        }
    } );
} )();

So just copy that and paste it into your JS file, after your custom event_save_alert() function.

  • Make sure that the script's dependencies contain wp-data, utils and wp-edit-post.

  • You should also load the script only on the post editing screen for your post type. E.g.

    add_action( 'enqueue_block_editor_assets', function () {
        if ( is_admin() && 'event' === get_current_screen()->id ) {
            wp_enqueue_script(
                'your-handle',
                plugins_url( 'path/to/file.js', __FILE__ ),
                array( 'wp-data', 'utils', 'wp-edit-post' )
            );
        }
    } );
    
2
  • Works like a charm!
    – sariDon
    Feb 17 at 18:20
  • Glad to hear that :) and I should add that when setting the cookie, you might want to do it like so: if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { setcookie( ... ); }. Because Gutenberg uses the REST API.
    – Sally CJ
    Feb 18 at 4:20
0

Since the notice is not a regular one, making a custom javascript notice would be easier. For simplicity, I use the alert() method to show it.

/location/our_custom_script.js:

const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
    
if urlParams.get('message') == 99 {
    alert("Wrong Meta!"); //or anything else
}

Then we use enqueue_block_editor_assets hook:

function our_custom_error() {
    wp_enqueue_script(
        'our_custom_error',
        plugins_url('/location/our_custom_scripts.js', __FILE__)
    );
}
add_action('enqueue_block_editor_assets', 'our_custom_error');

It's not pretty, but it works, I hope...

ps: To make it similar to the real deal, I guess we can copy the original notice style. But I personally would style this kind of error in different manner than the usual notice.

3
  • The code only seems to trigger during the initial page load. I have placed a simple alert in the javascript file which is triggered only on initially, not during the post insertion.
    – sariDon
    Feb 17 at 12:53
  • I just realized nowadays Wordpress use its REST API, which happens after the page is generated and executed by browser via javascript. Thus, all those hooks are unusable. When I have time, I would try to venture the solution, perhaps soon. Feb 18 at 6:41
  • Thanks. You can check Sally CJ's working answer here: wordpress.stackexchange.com/questions/413949/…
    – sariDon
    Feb 18 at 7:04

Your Answer

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

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