New Settings API and workflow

Description

That includes registration of options, creating fields and forms, and handling errors.

Quick suggestions, which Ryan and others can elaborate on, as well as offer justification for:

Stop using options.php as a POST handler.

Object-oriented approach, rather than passing handles around everywhere.

Should be flexible enough to leverage the new Settings API in the Network and User admins.

Form/field construction should be easy, and core should use it.

Core should also show/hide relevant fields based on the UI, perhaps with some sort of caps integration. Likewise, authorization for saving options should be incorporated beyond the sanitization callback.

Table markup should also be moved to CSS, which requires #16413 and core leveraging the fields API.

Anything that is not done, can be moved to 3.4. We should not rush this API, and we should be absolutely satisfied with it.

Here is a wrapper I wrote to simplify the headache of creating settings: ​https://github.com/prettyboymp/voce-settings-api. It isn't written in the usual way WordPress does things, and isn't a complete integration as it updates it calls update_option() directly, but I thought it may offer some useful insight.

One feature that is really helpful is that it allows for different plugins to share a page or even group (section) without interfering with each other. This allows us to add/remove/share modules without having dependencies or having to have multiple option pages.

It's a slippery slope. Even for a text input, you would still want to add a description, which would require a variable.

That reminds me of another issue I have with the current settings API, which is the inability to pass any extra arguments back to the sanitize or display callbacks for registered settings, making it nearly impossible to reuse callback functions.

I strongly dislike form *field* construction. The best API for constructing fields is HTML.

In general I agree that creating PHP wrappers for HTML just makes implement HTML forms complicated, but there is usually a lot more to managing data entry fields than just their HTML layout.

FWIW, I've been working on exactly this problem on and off for the past 6+ months, and it's a rather hard problem to tackle. I'll be releasing what I'm working on to beta hopefully within 30 days with a goal of launching as a community plugin within 90-120 days. I can give anyone interested access to it in alpha now. Because of the scope of doing this in a manner than is flexible enough for most needs I doubt anything substantial and robust could be built in time for 3.3, or even 3.4. Maybe the core team can learn from any architecture errors I may make that are uncovered over the next year?

Also to me both patches on this ticket seem to completely miss the issue here and put a lot of effort into wrapping the current api in something "Easier" to use rather than actually improving the API itself.

Easy is part of the point. The current API is fairly abominable to use.

That said, I think some of these other APIs try to do too much. Part of that is because the current core API sucks so much. Things we should consider up front:

Don't run code on every admin page load that doesn't need to be run. The only thing that needs to run on init/admin_init is the registration of the sanitize callback (and cap callback if we add that). We need to separate registering these callbacks from registering UI related stuff. A single "setting" in the UI is often multiple options in the db. Some of these APIs don't offer this flexibility.

Move away from options.php. Make it easy for pages to handle their own posts. We could possibly get rid of the whole notion of registering which options a page can handle if we do this. Each page knows what options it should handle. This might allow us to un-API some things.

We need register_option(). This would look much like register_meta() and live in wp-includes/functions.php with the other *_option() functions. Plugins would call this during init so that their options always have sanitization registered in case XML-RPC or other places manipulate the option.

If we lose options.php there is no need to keep a big list of what is registered for each page. Each page will have their own POST and XHR handlers and will know what options it deals with.

This leaves Sections and Settings within the Sections. Something like prettyboymp's API seems fine for this. I would probably lose the *_menu_page() level stuff. I think that should be done separately and section and settings should be registered in response to the page load hook for the desired page being fired.

I would probably lose the *_menu_page() level stuff. I think that should be done separately and section and settings should be registered in response to the page load hook for the desired page being fired.

The thing is that *_menu_page() is exactly the function that returns the name of the hook that could later be used to register the settings and sections for that specific page.

Hey Guys, I'm really excited about the direction this is taking! I hear nacin saying he doesn't want to handle field generation, but I think standardizing that (while still allowing for custom callback registration) is a very smart way to go.

I'm lazy, so last year I built an ​Options API for StartBox that leverages the Settings API and handles option registration, sanitization, builds the form element, and saves the option. In it's current form, like scribu's class, it can register ​a whole slew of field types and uses the admin.css.

Currently, all options in the page get stored to a single serialized array, and there are helper functions to interact with that data as though it were a stand-alone option in the options table. There are functions for ​adding/removing an entire metabox of options and ​adding/removing individual options as well. It even allows you to register the metabox to other pages (but they'll only work on my admin page currently).

I don't think it's a perfect solution for this yet, but I think it would serve as a great launching point. I'd love to help however I can to roll this idea into core (even if this code is irrelevant). Ptah's WPFramework does some similar things, maybe better, so maybe even a hybrid of the methods could be a good route.

You can ​Download StartBox from Github -- all the Options API stuff lives in /includes/functions/admin_settings.php, and gets put to work in /includes/functions/admin/...

Really interested to see where this goes. Will check it out soon - I had to use the Settings API recently and was not happy with it. I made a class which, like the other simple ones that were mentioned, just takes care of things and makes it easier.

Just throwing out there how I had done it quickly in case it might help anyone. register_section is also available with an id and description field, then under register_option another optional argument is the section id so they can be grouped.

It's very very basic, doesn't include styling options.. Anyway It may not help much or at all, but whatever comes from this ticket I'm sure will be worth the wait :)

That's what options-media.php looks like when it uses all of the core settings API and does its own POST and error handling instead of using options.php and options-head.php. _admin_init() is a special function that core admin files can define before loading admin.php so that they can queue up actions on admin_init like plugins can. options-media.php uses this to make sure the core settings sections and fields are registered before plugins.

It hasn't been tested much for backwards compatibility at this point. I wanted to get something up to work from.

It adds the ability for different plugins to re-use pages, sections, and fields without interfering with each other, allows plugin pages to handle their own post backs and simplifies the ability to reuse field creation and sanitation functions.

A major short coming of the old api was that you can pass a only single capability, say unfiltered_html, to the add_*_page functions, but that wouldn't matter (or make sense) since it posted to options.php with a hard-coded manage_options check (plus an is_super_admin for multisite). These API's should work together.

It might be nice to do capabilities checks in save_settings_page() based on what's passed to add_*_page for plugins. (Separate issue: add_*_page functions could be enhanced to handle an array of capabilities rather then just a single capability.) The options-media example doesn't register pages in the same way as a plugin would, so perhaps call a function that handles this, and have save_settings_page call it too.

As another test case, we could look at custom-background.php and custom-header.php as examples closer to how plugins and themes would use the API and what it's short comings (if any) are, as well as a good future in-core code example of how to use it.

I'd like to make a case for some default, generic form element generation. A few optional "template tags" for option pages would be cool. The templates could live in a file somewhere by itself to be included if wanted.

As a theme/plugin developer, sometimes I just want to use what ever flavor of markup and style core is currently using for the admin. Hunting down class-names and such in Firebug aside, this markup and style may change in future versions, and as long as the default markup/style was optional/over-writable, I don't see it as a problem.

Additionally, generic tags for simple text fields could have some safe easy optional default sanitation callbacks and output escaping. Remember, no one has to use them if they'd rather do it themselves. (If it's too hard, no one uses sanitation/escaping, and that's no good.)

Potential list could include things like the new HTML5 types: email, url, number, range, date, color, time, etc., as well as other typical ones like Name, Phone Number, Address, Post-like Content (textareas which uses the appropriate kses automatically), and the generic ones, Select, Checkbox, Radio, etc. These functions could create the markup and style to match the standard admin uniformly, as well as apply a fallback sanitation callback and escaping appropriate for each one.

These things would make it easier on everyone, but no one would be required to use them. If you have a special use-case, by all means, roll your own! But they'd be great to have and maintained somewhere central.

We can't cover all cases. These functions could also provide a code example for developers attempting to write their own.

@WraithKenny +1 for template tags and template-able option pages. One imo important thing would be that those template tags could have an additional callback parameter to override their behavior (like if the callback would hook into a filter). Another thing I want to mention are "shared" option pages. In detail: In some cases you don't have hundreds of options and therefore you don't need 10 option pages, but only a single one - with let's say 15 options. Now 5 of those options are better off when they are only viewable by the developer and the other 10 are meant to get set by the owner/client who wants - of course - have an administrator account. If we would have a capability per field, we could use custom capabilities to show all fields only to the developer and the rest to the "public" majority. Sure, we can still do this in for example an options template, but having the capability check built in right into the field the process would be much more seamless.

HTML is the best place to define forms, and with HTML5, it's a pretty good place to set basic validation as well.

I've been playing with this idea, where we'd spec out a form in HTML tags, and then create the corresponding PHP form elements from parsing that, and even wire up the _POST handler to do the basic validation (ranges, email, color picker, etc.) A system like that could validate any HTML5 form field type marked as required automatically when the form comes back to the server, eliminating a huge pain point for forms handling in general (and a pain point WordPress's Setting's API passes on to plugin and theme developers). Using HTML5 form spec provides a nice predefined scope, so we don't have to go crazy. WordPress could seemingly also be wired up automatically from that.

This would involve the overhead of parsing HTML on the server, but we could probably minimize that by only parsing single fields (except with select boxes).

Here's a quick example, of what this could look like as an implemented form:

With this there is no limitation on how you structure your own HTML - it's all just plain old HTML (well, mostly). There may be other ways to encapsulate the HTML - for example - parsing an entire form all at once instead, or by using a similar function trick for each tag, but this seemed like a nice light way to go.

What's cool about that, is you can define the form as HTML, in place in the document. PHP functions are hoisted, so you can register the form in the head, and do the input validation there. This ends up being pretty DRY - you define your form, your input types, and your validation all inside the form (without limiting your layout options) just the one time, and then all the wiring can be done automatically. WordPress setting registration, section and field registration, error handling, can also all be wired up automatically from the form spec.

(Separate note: It would be nice to add to the setting API support for arrays of data - maybe a register_setting_group method or something. Registering a separate setting for each field seems like too much in many cases, where serialized array data would be enough.)

Maybe this could be useful enough even outside of WordPress to go ahead and make it work - maybe wrap Scribu's lib. :-)

Personally, I think that's pretty spiffy. I'll probably build off of this further to throw some proofs together that can automatically wire up settings, custom post types, or other meta data stuff in WordPress.

I should be able to replace the boilerplate for almost the full turnaround - start by defining the fields as HTML5 (with requirements and validation), set some variables for metadata names (and groupings), and then just let the WPOnceForm handle the form postback, the sanity/security/validation, and store the data - all of that almost completely automatic, driven off of an HTML5 form template.

I just added a DynamicForm.php example file, which shows how to make a OnceForm from a string. This enables existing functions like add_settings_fields and whatnot to be used to construct a form, and then pass that through OnceForm to get the automatic request handling. With some imagination, you can see how WPOnceForm (no proof yet) could automatically save changes to settings from these forms without piles of boilerplate.

I know a lot of people follow this ticket, I have been a regular proponent for this issue for over a year now. I am keen to see this worked on but a lot of the issues at the core of this haven't been resolved, such as whether to use a HTML parser or not, this issue is also holding up implementation of the API but also implementation of the API in the back end (wp-admin).

I'd like to help, I can't program all that well however perhaps I can help to coordinate some how? In that mindset, please could folks reply to show interest in helping with the development, and perhaps we can organise a meeting to discuss this, also would be nice to have someone who knows the community better than I who can suggest how we reach consensus and move this ticket on.

This automatically adds an nonce field to any form, and validates the request. I also reduced the PHP requirements (I was relying on ganon's use of php's magic invoke method, which only works on php 5.3 previously, but I changed it so it works on WP's target php version).

I think #15691 should be built into this to allow a setting to have a scope, i.e. where it's preferred storage is, network level or blog level. This way it solves #15691 and keeps plugin code simple, removing all the is_multisite() calls and duplicate code for handling storage of settings for network pages.

This ticket has been dragging on for a long time. Ultimately, Trac is best for implementation of things, not for having a ticket rot until someone proposes a great API for something we're not even sold on replacing, or with what. Thanks prettyboymp and others for contributing some ideas here. While there is some clear support in a better API, we don't know what that looks like yet. And that's OK — maybe in a few months we can revisit this and start with an API design brainstorming session or something.

I do like ericlewis's WP_Meta_Box work in #18179, which I think could actually be adapted in such a way to bridge (to some extent, in terms of authorization/sanitization/saving given register_setting() and register_meta()) these two form-building APIs. Something else to look into would be the API surrounding the customizer, which was brought into core back in 3.4 (back when this ticket was first proposed). Maybe there is even a way for all of these settings-related pieces to have some kind of unified foundation. Without bringing some sanity to all of these related areas, I'm not sure a rewrite is worth the headache.

Closing as maybelater. If there is any future activity on this on make.wordpress.org/core or elsewhere, can someone please cross-post here in order to update the many, many people CC'd here.

That's all it takes to create a custom post type with a custom form in a meta box. The form defines the meta keys (one per input), and defines which fields are required, validates all the post data according to the declared validation rules (as much as is supported by OnceForm) and sanitizes the inputs (at least that's the idea - I don't have test coverage on that yet), and saves everything in to the appropriate place in the database without having to write a ton of boilerplate. IT also prefixes the meta keys if you want to use a prefix.

That's a meta box demo, but I'd guess a Settings page could be done as easily.

A big part of what it seems the Settings API does is just puts together HTML that matches the admin - but my sense is that would be much easier to just grab a template, edit in the fields you want, and then throw it at OnceForm. No need for complicated layout code, just write the HTML - much easier.