31

I have a function that defines a custom field on a post type. Say the field is "subhead".

When the post is saved, I want to do some validation on the input, and display an error message on the post edit screen if necessary. Something like:

// Handle post updating
function wpse_update_post_custom_values($post_id, $post) {

    // Do some checking...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors->add('oops', 'There was an error.');

    }

    return $errors;

} 
add_action('save_post','wpse_update_post_custom_values',1,2);

I'm trying to hook this to the save_post action, but I can't figure out how to handle errors. There doesn't appear to be an error object passed into the function, and if i create my own WP_Error obj and return it, it's not respected by whatever mechanism spits out errors on the post edit page.

I currently have an on-page error message inside my custom meta box, but this is less than ideal--I'd rather have a big, red, up-at-the-top error like WP normally displays.

Any ideas?

UPDATE:

Based on @Denis' answer, I tried a few different things. Storing errors as a global didn't work, because Wordpress does a redirect during the save_post process, which kills the global before you can display it.

I ended up storing them in a meta field. The problem with this is that you need to clear them out, or they won't go away when you navigate to another page, so I had to add another function attached to the admin_footer that just clears out the errors.

I wouldn't have expected that error handling for something so common (updating posts) would be this clunky. Am I missing something obvious or is this the best approach?

// Handle post updating
function wpse_5102_update_post_custom_values($post_id, $post) {

    // To keep the errors in
    $errors = false;

    // Do some validation...
    if($_POST['subhead'] != 'value i expect') {

        // Add an error here
        $errors .= 'whoops...there was an error.';

    }

    update_option('my_admin_errors', $errors);

    return;

} 
add_action('save_post','wpse_5102_update_post_custom_values',1,2);


// Display any errors
function wpse_5102_admin_notice_handler() {

    $errors = get_option('my_admin_errors');

    if($errors) {

        echo '<div class="error"><p>' . $errors . '</p></div>';

    }   

}
add_action( 'admin_notices', 'wpse_5102_admin_notice_handler' );


// Clear any errors
function wpse_5102__clear_errors() {

    update_option('my_admin_errors', false);

}
add_action( 'admin_footer', 'wpse_5102_clear_errors' );
6
  • Good question. I think you could get rid of the admin_footer hook if you clear out the errors at the end of your notice handler function. Simplifies things just a bit.
    – Geert
    May 3, 2011 at 6:54
  • 1
    How are you dealing with repopulating the form fields (with the possible invalid data)?
    – Geert
    May 3, 2011 at 6:58
  • 1
    I have a basic question. What Wordpress php file is this in?
    – user5721
    May 26, 2011 at 23:25
  • @Karen This would be in a custom plugin file, or in your functions.php.
    – MathSmath
    May 27, 2011 at 4:38
  • I might be missing something obvious, but would it be slightly more efficient to run update_option('my_admin_errors', false); immediately after the if statement at the end of wpse_5102_admin_notice_handler()? Nov 30, 2012 at 22:55

7 Answers 7

7

Store errors in your class or as a global, possibly in a transient or meta, and display them in admin notices on POST requests. WP does not feature any flash message handler.

2
  • Thanks for pointing me in this direction! I ended up using a meta to store errors, because I had issues trying to do it as a global or a property. I'm updating my answer right now to explain how I'm doing it... please let me know if this is the kind of thing you were suggesting, or if there's a better way that I'm not getting.
    – MathSmath
    Dec 10, 2010 at 0:15
  • That kind of thing, yeah. Maybe store it in a session variable, on second thoughts, though. This, in order to enable multiple authors to edit posts at the same time. :-) Also, I believe it's not possible to store false in an option. Store an empty string instead. Dec 10, 2010 at 9:35
6

I suggest to use sessions since this will not create strange effects when two users editing at the same time. So this is what I do:

Sessions are not started by wordpress. So you need to start a session in your plugin, functions.php or even wp-config.php:

if (!session_id())
  session_start();

When saving the post, append errors and notices to the session:

function my_save_post($post_id, $post) {
   if($something_went_wrong) {
     //Append error notice if something went wrong
     $_SESSION['my_admin_notices'] .= '<div class="error"><p>This or that went wrong</p></div>';
     return false; //might stop processing here
   }
   if($somthing_to_notice) {  //i.e. successful saving
     //Append notice if something went wrong
     $_SESSION['my_admin_notices'] .= '<div class="updated"><p>Post updated</p></div>';
   }

   return true;
} 
add_action('save_post','my_save_post');

Print notices and errors and then clean the messages in the session:

function my_admin_notices(){
  if(!empty($_SESSION['my_admin_notices'])) print  $_SESSION['my_admin_notices'];
  unset ($_SESSION['my_admin_notices']);
}
add_action( 'admin_notices', 'my_admin_notices' );
4
  • fix for the session version: at the first time of using the session variable don't use .= only = if you turn on the debugging, you can check why...
    – user18140
    Jul 13, 2012 at 13:12
  • 3
    I've been doing this too, but if you release a plugin to a wide audience like that people will end up hating you for it. Wordpress doesn't instantiate sessions because it's designed to be stateless and not need them, and some weird server setups will break it. Use the transients API - codex.wordpress.org/Transients_API instead of sessions and you'll maintain compatibility. Just thought it was worth flagging a reason why not to do this here.
    – pospi
    Sep 12, 2012 at 1:37
  • @pospi this seems to have similar issues as the original use of the get_option and update_option functions. So I guess the solution would be to append the current user's ID to the key?
    – Gazillion
    Oct 24, 2013 at 16:55
  • Yeah that would totally work! So long as you add something to uniquely identify the user you will avoid messages being mixed up between logged in users (:
    – pospi
    Oct 28, 2013 at 0:23
6

Based on pospi's suggestion to use transients, I came up with the following. The only problem is there is no hook to put the message below the h2 where other messages go, so I had to do a jQuery hack to get it there.

First, save the error message duing your save_post (or similar) handler. I give it a short lifetime of 60 seconds, so it is there just long enough for the redirect to happen.

if($has_error)
{
  set_transient( "acme_plugin_error_msg_$post_id", $error_msg, 60 );
}

Then, just retrieve that error message on the next page load and display it. I also delete it so it wont get displayed twice.

add_action('admin_notices', 'acme_plugin_show_messages');

function acme_plugin_show_messages()
{
  global $post;
  if ( false !== ( $msg = get_transient( "acme_plugin_error_msg_{$post->ID}" ) ) && $msg) {
    delete_transient( "acme_plugin_error_msg_{$post->ID}" );
    echo "<div id=\"acme-plugin-message\" class=\"error below-h2\"><p>$msg</p></div>";
  }
}

Since admin_notices fires before the primary page content is generated, the notice is not where the other post edit messages go, so I had to use this jQuery to move it there:

jQuery('h2').after(jQuery('#acme-plugin-message'));

Since the post ID is part of the transient name, this should work in most multi-user environments except when multiple users are concurrently editing the same post.

2
  • Could you elaborate on "Since the post ID is part of the transient name"? I created a class to handle error messages using this technique but I require my constructor to pass a user_ID. Does the transient API use the user_id when hashing the key? (I ask because the codex doesn't seem to mention this)
    – Gazillion
    Oct 24, 2013 at 17:37
  • No, but you could add it in manually. In the code I posted above, the name of the transient is acme_plugin_error_msg_POSTID. You could just add user ID to that like acme_plugin_error_msg_POSTID_USERID.
    – Josh Coady
    Jan 30, 2015 at 4:34
5

When save_post runs, it has already saved the post on the database.

Looking into WordPress core code, more specifically at the wp-includes/post.php's update_post() function, there is no built-in way to intercept a request before it is saved on the database.

However, we can hook pre_post_update and use header() and get_post_edit_link() to prevent the post from being saved.

<?php

/**
*   Performs validation before saving/inserting custom post type
*/
function custom_post_site_save($post_id, $post_data) {
    // If this is just a revision, don't do anything.
    if (wp_is_post_revision($post_id))
        return;

    if ($post_data['post_type'] == 'my_custom_post_type') {
        // Deny post titles with less than 5 characters
        if (strlen($post_data['post_title'] < 5)) {
            header('Location: '.get_edit_post_link($post_id, 'redirect'));
            exit;
        }
    }
}
add_action( 'pre_post_update', 'custom_post_site_save', 10, 2);

If you to notify the user what went wrong, check this gist: https://gist.github.com/Luc45/09f2f9d0c0e574c0285051b288a0f935

1
  • Thank you for this, handles validation perfectly, whether publishing for the first time or updating the post. You just saved me a great deal of time and effort.
    – Zade
    Dec 15, 2018 at 8:31
1

Why don't you validate your field with the help of some Javascript? I think this would be the best approach for this.

2
  • Thanks for the suggestion! What I left out of the question (for simplicity sake) is that I'm trying to handle file upload errors, so it needs to be server-side. Thanks for the suggestion though!
    – MathSmath
    Dec 10, 2010 at 16:33
  • 1
    javascript validating doesn't prevent from some attacks, server-side validation is the only secure one. Furthermore, wordpress offer some good tools to validate user data. But you're right if it just check some values before sending data to server, you can save some time in low server ^^
    – nderambure
    Mar 14, 2011 at 23:50
1

Trying to use the script above, I ran into a strange problem. Two messages are shown on the edit screen, after the post update. One is showing state of the content from previous save and another one from the current. For example, if I save the post properly and then make an error, first one is "error" and second one is "ok" - altough they are generated in the same time. If I change the script and append only one message (e.g. "error"), initiate one update with "error" and after that another one with "ok", "error" message stays (is displayed for the second time). I must save with "ok" once again to get rid of it. I really don't know what's wrong, I've tested it on three different local servers and there's the same issue on each one of them. If anyone has any idea or suggestion, please help!

2
  • I did some more tests of the simplier, second version of script which I've mentioned above and it seems that if the "error" message is really appended to session array, it is shown on edit screen. If there is no message (everything is "ok") and previous message was error, it appears on the screen. What is strange, it is generated on the time of saving (not cached) - I've checked it using date() in the error message body. I'm totally confused now.
    – jlub
    Sep 8, 2011 at 17:55
  • Ok, in case anyone else is pulling hair out of his head - it turned out that the Wordpress revisions system was the problem (some kind of bug probably?). I've disabled it and now everything is just fine.
    – user8594
    Sep 12, 2011 at 9:41
1

I've written a plugin that adds in a flash error handling for post edit screens and prevents posts being published until required fields are filled out:

https://github.com/interconnectit/required-fields

It allows you to make any post fields mandatory but you can use the API it provides to make any custom fields required too with a customisable error message and validation function. It defaults to checking if the field is empty or not.

1
  • Don't hesitate to add any issues on github if you come across them. I need to document the API a bit better too as there are some adiditional filters you can use. Dec 7, 2012 at 14:13

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