WordPress Form API

The short version: I made a Form API for WordPress. Use it in your plugin. Make better forms. Give back and help us all make better forms.

Introduction

If you’ve every written a WordPress plugin, chances are you’ve had to create a form, be it for a settings page, custom post/user meta, widget configuration, front-end user input, or a myriad of other possibilities. If you look at WordPress core or most plugins, you’ll see all of them using pretty much the same method for creating a form field, something very similar to:

 
sprintf( '<input type="text" value="%s" name="%s" />', $current_value, $field_name ');

There are a few special kinds of fields that have pre-existing functions, such as wp_dropdown_pages() or wp_dropdown_categories(). But generally speaking, we’re writing HTML in our PHP (maybe dressed up as a template include).

Lament

I want something better than that. I want a robust API that lets me create forms (or individual form elements), modify them, validate, and process user input. I want themes to have control over the display of forms. I want other plugins to be able to modify and extend my forms.

I tried a variety of existing libraries. Zend_Form from the Zend Framework has a lot to offer, in terms of a robust, object-oriented API. But it’s not very pluggable, and loading the entire Zend Framework just for the Form API is overkill.

I used to spend a lot of time in Drupal, and its Form API is somewhat pleasurable to work with. Forms are amazingly extensible and themeable. But I’m not thrilled with the array-based syntax, and porting it to WordPress would be inappropriate for a multitude of reasons.

Toward a Solution

In the grand hacker tradition, I was dissatisfied with the existing options, so I made my own. I’ve dubbed it WP Forms, and you can download/clone/fork it from its home on GitHub.

What does it do? It’s an API for forms in WordPress. You can use it to create simple form elements. You can use it to create entire forms. You can validate your forms. You can process the submitted information. You can change how elements are rendered. You can wrap elements in themeable markup.

There’s a lot of documentation. It should answer a lot of your questions. And those that I didn’t anticipate, I’d be delighted to answer.

Request for Feedback

WP Forms is surely incomplete. I’m personally not thrilled with the API around adding optgroups to select elements, but I’m not yet sure how to improve it. The API for decorating elements is probably a little complicated for beginning developers, so I’m open to suggestions for a better way to handle that (or at least provide a facade for most common operations). Beyond that, I just don’t know how people would want to use the API. I know how I would use it, but I’m not “people”, and I’m definitely not you; I’m just me.

I can think of several features to add: conditional fields/content, ajax submissions, multipage forms, media uploads, more built-in field types/validators. What features would you want to see?

Request for Contributions

WP Forms will be much more powerful if we can get behind it as a community, extending it, improving it, using it, testing it, documenting it. Do you want an awesome form API in WordPress? I hope you’ll help me make one.

WordPress Action Nesting: a cautionary tale

When you save a post in WordPress, the post’s data goes through wp_insert_post(), which in turn triggers the save_post action. So let’s say you have a callback hooking into save_post.

function my_first_callback( $post_id, $post ) {
  // do something...
}
add_action('save_post', 'my_first_callback', 10, 2);

So far, pretty simple. Now let’s put something in our callback function.

function my_first_callback( $post_id, $post ) {
  if ( $post->post_type == 'post' ) {
    wp_insert_post(array(
      'post_title' => 'A Post',
      'post_status' => 'publish',
      'post_type' => 'a_custom_post_type',
    ));
  }
}
add_action('save_post', 'my_first_callback', 10, 2);

Not a horribly complicated function. We’ll leave aside my reasons for creating a post in this callback (there’s a myriad of reasons why one might want to create or update a different post when a given post is saved) and focus on the consequences.

wp_insert_post(), called from my_first_callback() will once again trigger the save_post action, while we’re still in the middle of processing the first save_post action. So long as you avoid an infinite loop, there’s nothing necessarily wrong with that. But look closely inside do_action(), and you’ll find something else to worry about. Continue reading “WordPress Action Nesting: a cautionary tale”

WordPress Network Admin Pages

Ever since WordPress 3.0 came out, merging the multi-blog network capabilities of WordPress MU into the core or WordPress, users have been asking for plugins that let you set options across the entire network, rather than per-blog. Setting up a network settings page is similar to setting up a standard settings page, but with a few key differences.

Creating the Page

First off, rather than the admin_menu hook, you’ll use the network_admin_menu hook to register your page. E.g.:

add_action('network_admin_menu', array($this, 'register_network_admin_pages'), 10, 0);

In your callback function, you don’t have the luxury of shortcut functions (e.g., add_options_page()) for registering your settings page, so you have to use add_submenu_page(), with 'settings.php' as your first argument. Alternatively, you can still use add_menu_page() to create a new menu section, just as you would with the single blog admin menu.

The WordPress settings API doesn’t entirely work on network admin pages, but you can still take advantage of the add_settings_section(), add_settings_field(), and do_settings_sections() functions to register and display your settings, if appropriate. But register_setting() is useless here.

The action attribute for your settings form should point to edit.php?action=Your_Unique_Action, for reasons we’ll soon discover.

Individual settings should be pulled from the network-wide site options table, using get_site_option().

Saving the Settings

Your settings form gets submitted to edit.php. To save your settings, you need to hook into the network_admin_edit_ACTION hook.

add_action('network_admin_edit_Your_Unique_Action, array($this, 'save_network_settings_page'), 10, 0);

Again, register_setting() doesn’t work on admin pages, so you’ll need to sanitize, validate, and save all your options yourself. Save the settings to the network-wide site options table using update_site_option().

After all your settings are saved, do a redirect back to your settings page. If you don’t WordPress will automatically redirect to the network dashboard.

wp_redirect(add_query_arg(array('page' => 'Your_Page_Slug', 'updated' => 'true'), network_admin_url('settings.php')));
exit();

Simpler WordPress Plugin Development with WP Router

I’ve a great deal of experience with two open source content management systems: WordPress and Drupal. They both have their strengths and weaknesses, which I won’t get into here. If I had to pick the key difference between the two, though, it’s in how they answer one question:

What is a URL?

In WordPress, a URL indicates which posts to display. Your request is mapped to query variables (if you don’t have permalinks turned on, you’ll see these variables directly in your query string), and those variables are used to manipulate the database query in $wp_query->query(). It’s a very nice, consistent system: a URL equals one or more posts (or a 404 error). (NB. the admin section is a different beast entirely.)

With Drupal, a URL is an entry in the menu system. When Drupal sees the URL, it looks up the corresponding entry, and calls the function specified in that entry. That function is responsible for creating the contents of the page, be it a list of nodes, a form, flying monkeys, or what have you. You can use a module like Views to achieve results similar to WordPress, but you have a lot more power and flexibility to do something different with a given page.

Sometimes you want that power and flexibility in WordPress. You just want to say, “When a user visits this URL, call this function and display its output.” That’s not really the “WordPress way” of doing things, and it requires a fair amount of code to accomplish what seems to be a simple instruction.

Abstract the Details Away

So I went and wrote another WordPress plugin. WP Router abstracts away all the messy details of declaring a callback function for a URL in WordPress. One method call is all it takes to set up your path, your rewrite rules, your query variables, your access rules, your title, your template overrides. It reduces a few dozen action/filter callbacks to a small list of easy-to-understand arguments.

If you’ve every worked with Drupal’s menu system, you’ll find many of the arguments familiar, adapted, of course, for the WordPress framework. Read all about them in the usage notes, and check out the sample code included in the plugin.

Ongoing Development

Right now, this is at version 0.2, which is synonymous with “has all the features I thought to add initially, plus one more”. But I’m just one developer. What would make this plugin more useful to you? What doesn’t work like you expect it to? What part of the API is confusing? Please leave a comment here or create an issue in Github.

And if you’re interested in contributing, feel free to fork the project and send me updates. It’s hosted at Github, with released copied over to WordPress’s plugin repository. As usual with code I write, it’s licensed under the MIT License (i.e., do whatever you want with it, just mention where it came from).

Rethinking Object-Oriented WordPress Plugins

Following common practice in WordPress plugin development, you create a class for you plugin, instantiate that class with a

$my_plugin = new My_Plugin();

in your main plugin file, and declare all your actions/filters in your class’s __construct() function, using something like:

add_filter('init', array($this, 'my_init_function'));

And then you go on to add your plugin’s JavaScript and CSS, register post types and taxonomies, look for query variables, etc.

I’m declaring that to be “wrong”, and I’m not going to do it anymore. Why? Because it’s not good objects-oriented design, and it seems to me that if you’re going to use objects, you should follow good object-oriented design patterns. Continue reading “Rethinking Object-Oriented WordPress Plugins”