All we need is an easy explanation of the problem, so here it is.
Title sounds counter-intuitive but bear with me. 🙂
I have options page, made with Settings API. When user enters invalid data I want to display error notice with
But! To determine that data is invalid I need to make an remote API call. That call relies on saved data. So I can’t do this in sanitization callback (which is recommended place to throw such notices) because my data is not saved yet.
Instead I had tried to hooking my check into
admin_notices. It works fine most of the time except one (and most important) case – when settings are saved they are always followed by native Settings saved. notice and my custom notices are completely ignored for some reason.
So how do I throw that error notice even if WP thinks everything is fine?
More focused question – why exactly Settings saved. trumps any other notices?
PS I could try to make API call optionally take data as argument instead of reading it from saved option, but so far I think it will make arguments overly bulky.
How to solve :
I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.
Ok, I think I have an idea what is going on.
The list of notices to display is retrieved by
get_settings_errors()( source ).
This function reads notices from global
$wp_settings_errorsunless there is
settings_errorstransient set, which trumps global var.
When settings are saved there is check for no setting errors and if so Settings saved. notice is generated. After that (in either case) errors are saved into
settings_errorstransient (I assume to preserve them on redirect) ( source ) .
Basically no matter what notices you generate in your code – they will be ignored when transient is set and it is always going to be set after saving settings.
As for me it would make sense to concatenate transient with global variable rather than making it exclusive or choice.
And I guess to display custom notices when transient is set I will need to mess with that transient, which is probably not worth the trouble.
One thing you could do, i guess:
- in your validation callback, read the current settings (you may do so anyways?) and store them in a variable
- check all other values first
- if they aren’t valid, return errors, don’t check the critical value
- if they are, store them in the db (within your validation function)
- do your remote API call
- validate the tricky value
- if valid, return the whole bunch and all good
- if not, store the old settings you got at the beginning back to the db, do your error output via
Of course that somewhat defeats/abuses the Settings API, but it could be a way.
Edit: The Settings Saved message doesn’t trump your other notices, it rather only shows up because your other notices have already been wiped (for some reason) from the internal settings_error store. Why that’s the case i don’t know, but the Settings Saved message simply gets added to the error store if and only if it’s empty. What could contribute to your dilemma is that whenever the settings error store gets read (via
get_settings_errors()) it also gets wiped.
Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂