Almost a year ago (wow, time flies!) I wrote a little tutorial explaining the nuances of add_meta_box in WordPress, and how it can be used to create some nifty Custom Write Panels. It was a nice little script, and still functioned great. I use it on almost all of the projects Liam and I have, and up to this point, it has been great. However, for an upcoming project, I foresaw some problems.

The current way things are set up in the script, it created a new row in the postmeta table for each custom input you included. Because of this, when you wanted to display the results on your theme, you also needed a separate variable for each input. Again, this great, and I never had any problems with it. But what happens when you have more than just one or two inputs? What about 8-10? Then things start getting a little hairy. Not only do you have to create 8-10 separate variables, but it creates a bunch of unneeded entries in the database.

So I decided to revise my script. Although it is only about 15 lines shorter than the previous one, it should function quite a bit faster (although I’ve done no real time trials.) The basic idea of the changes is to use a single row to include all the input fields, instead of one for each. If we store all of the data in a serialized array, we can have one key that holds all of our data. Because there is only one row to retrieve, we only have to define one variable to display our data.

A quick Example

This graphic is just a quick example to those of you who are new to custom write panels to help you see how this technique can be used to improve your experience in WordPress. Click to enlarge.

Things start of basically the same. I’ve slightly altered my meta box arrays (taking out info that never really gets used.) I’ve also added a new variable, $key which will be used to label our new data (among other things.)

Where We Left Off (in functions.php)

Note: Please excuse the code indentions. The WordPress plugin auto-formats it. Sorry!

<?php
/*
Plugin Name: Custom Write Panel
Plugin URI: http://wefunction.com/2009/10/revisited-creating-custom-write-panels-in-wordpress
Description: Allows custom fields to be added to the WordPress Post Page
Version: 1.1
Author: Spencer
Author URI: http://wefunction.com
/* ----------------------------------------------*/

$key = "key";
$meta_boxes = array(
"image" => array(
"name" => "image",
"title" => "Image",
"description" => "Using the \"<em>Add an Image</em>\" button, upload an image and paste the URL here. Images will be resized. This is the Article's main image and will automatically be sized."),
"tinyurl" => array(
"name" => "tinyurl",
"title" => "Tiny URL",
"description" => "Add a small URL of this post that will be used to track tweets, and share the post.")
);

Then, to get it out of the way, lets add our function that actually creates the panels:

function create_meta_box() {
global $key;

if( function_exists( 'add_meta_box' ) ) {
add_meta_box( 'new-meta-boxes', ucfirst( $key ) . ' Custom Post Options', 'display_meta_box', 'post', 'normal', 'high' );
}
}

Same exact thing as before, just used our $key variable to label the title.

Displaying the Write Panels

The next part is pretty similar as well. This is what we use to build the meta boxes. Like I said, not much changes. I’ve just removed some extra code that wasn’t needed (checking for standard values, which was removed from the arrays) and I also changed the way I setup the nonce.

<?php
function display_meta_box() {
global $post, $meta_boxes, $key;
?>

<div class="form-wrap">

<?php
wp_nonce_field( plugin_basename( __FILE__ ), $key . '_wpnonce', false, true );

foreach($meta_boxes as $meta_box) {
$data = get_post_meta($post->ID, $key, true);
?>
<div class="form-field form-required">
<label for="<?php echo $meta_box[ 'name' ]; ?>"><?php echo $meta_box[ 'title' ]; ?></label>
<input type="text" name="<?php echo $meta_box[ 'name' ]; ?>" value="<?php echo htmlspecialchars( $data[ $meta_box[ 'name' ] ] ); ?>" />
<p><?php echo $meta_box[ 'description' ]; ?></p>
</div>

<?php } ?>

</div>
<?php
}
?>

Now I’m using WordPress’s wp_nonce_field to create a nonce. This time it is outside of the foreach loop, because we clearly only need one! (Not sure what I was thinking before.)

As I mentioned before, I took out some code to check for default values, and instead replaced it with a $data value. This looks for our single meta row, with our defined key, and fills the input with any necessary data.

Saving the Data

The final part, where we save the data, is what got changed the most. The basic idea is to loop through our original $meta_boxes array, creating a new array to hold the values. In English: for each array in $meta_boxes, get the value of the input field, and add it to a new $data array.

So now we have a single array. Check out the code below:

foreach( $meta_boxes as $meta_box ) {
$data[ $meta_box[ 'name' ] ] = $_POST[ $meta_box[ 'name' ] ];
}

Not too bad right? In the function, we also need to verify the data. Since I have a better understanding of how the nonce works now, I’ll try and explain how we verify it. We can use WordPress function, wp_verify_nonce to verify that the correct nonce was used in the time limit. If that’s not true, we return the $post_id to abort the script. This stops you from being tricked into doing something you don’t want to. You can read more about nonces from Mark Jaquith.

The next snipet checks to make sure that the current user has the authority to edit a post. Because we have only created the meta_boxes on the post page, we only need to check that they can edit posts.

if ( !wp_verify_nonce( $_POST[ $key . '_wpnonce' ], plugin_basename(__FILE__) ) )
return $post_id;

if ( !current_user_can( 'edit_post', $post_id ))
return $post_id;

If you remember the old script, in order to save the data, we first had to check to see if it existed, update it if it did, add it if it didn’t, or delete it if it was blank. Phew! A lot of checking. Imagine having the database do that 10 times? Seems quite slow right?

Well shortly after I wrote the old tutorial, WordPress updated update_post_meta so that if the row does not yet exist, it will create if for you. That allows us to only use one function, instead of checking for three. (I decided to exclude the delete, because chances are you’ll always have something, and one row isn’t nearly as bad as 3 or 4.)

So our final save function looks like this:

function save_meta_box( $post_id ) {
global $post, $meta_boxes, $key;

foreach( $meta_boxes as $meta_box ) {
$data[ $meta_box[ 'name' ] ] = $_POST[ $meta_box[ 'name' ] ];
}

if ( !wp_verify_nonce( $_POST[ $key . '_wpnonce' ], plugin_basename(__FILE__) ) )
return $post_id;

if ( !current_user_can( 'edit_post', $post_id ))
return $post_id;

update_post_meta( $post_id, $key, $data );
}

Final Code

Here is all of the new code. The last few lines initiate the script.

<?php
$key = "key";
$meta_boxes = array(
"image" => array(
"name" => "image",
"title" => "Image",
"description" => "Using the \"<em>Add an Image</em>\" button, upload an image and paste the URL here. Images will be resized. This is the Article's main image and will automatically be sized."),
"tinyurl" => array(
"name" => "tinyurl",
"title" => "Tiny URL",
"description" => "Add a small URL of this post that will be used to track tweets, and share the post.")
);

function create_meta_box() {
global $key;

if( function_exists( 'add_meta_box' ) ) {
add_meta_box( 'new-meta-boxes', ucfirst( $key ) . ' Custom Post Options', 'display_meta_box', 'post', 'normal', 'high' );
}
}

function display_meta_box() {
global $post, $meta_boxes, $key;
?>

<div class="form-wrap">

<?php
wp_nonce_field( plugin_basename( __FILE__ ), $key . '_wpnonce', false, true );

foreach($meta_boxes as $meta_box) {
$data = get_post_meta($post->ID, $key, true);
?>

<div class="form-field form-required">
<label for="<?php echo $meta_box[ 'name' ]; ?>"><?php echo $meta_box[ 'title' ]; ?></label>
<input type="text" name="<?php echo $meta_box[ 'name' ]; ?>" value="<?php echo htmlspecialchars( $data[ $meta_box[ 'name' ] ] ); ?>" />
<p><?php echo $meta_box[ 'description' ]; ?></p>
</div>

<?php } ?>

</div>
<?php
}

function save_meta_box( $post_id ) {
global $post, $meta_boxes, $key;

foreach( $meta_boxes as $meta_box ) {
$data[ $meta_box[ 'name' ] ] = $_POST[ $meta_box[ 'name' ] ];
}

if ( !wp_verify_nonce( $_POST[ $key . '_wpnonce' ], plugin_basename(__FILE__) ) )
return $post_id;

if ( !current_user_can( 'edit_post', $post_id ))
return $post_id;

update_post_meta( $post_id, $key, $data );
}

add_action( 'admin_menu', 'create_meta_box' );
add_action( 'save_post', 'save_meta_box' );
?>

Implementation

One of the main reasons I redid the script was to have better implementation. Because all of our data is stored in one single row, we only need to call it once.

$data = get_post_meta( $post->ID, 'key', true );

Put that inside the while in the WordPress loop. Remember to change “key” to whatever value you entered in the script. That variable is now holding the array of information stored in the database. So you can simply access it like so:

<?php echo $data[ 'tinyurl' ]; ?>

Or

<?php echo $data[ 'image' ]; ?>

Like I said, not much changed on the back-end, it’s only a few lines shorter, but it should be quite a bit more efficient, as well as more expandable.

If you have any questions about the code above, or the tutorial in general, please do not hesitate to leave a comment below. I will do my best to help answer any questions that may arise.