One of my favorite things about developing the LightSpeed site was the interesting custom CMS features I needed to create to accomplish the features that we wanted.
I knew I wanted to have a custom post type for the Issues so that I could aggregate all content from an issue in a single post, and also store things like buy link urls for the various e-issues all in a single place, rather than on each content post itself. The idea being, the editor creates an issue, which stores links, cover image, masthead image, artist link, etc.. When I am writing template code for an individual post, I can grab the value of a meta field for the id of the issue and have all of that, or I can just query the latest issue for settings such as which masthead graphic to use.
To make this work, I would need a way on the individual content entries to link them to the issue posts. This to be as user-friendly as possible, so simply using a normal meta field and requiring something like a post ID in it wasn’t going to cut it. So I trolled the web and experimented until I worked up the following code to accomplish the custom select menus.
All of the code below goes in the functions.php file. This code has been cobbled together from various sources, primarily this tutorial. If you want a more detailed walk-through, take a look at that. *ETA: I’m told the original of that tutorial can actually be found at wefunction.com. It’s a better run down of the individual parts.
First, we create an array of arrays defining the new custom meta boxes that would appear. In the actual site, I implemented one of these for authors as well as issues (rather than creating WordPress users to create a data object for authors), but I’ve removed that to simplify what I’ve presented here.
$new_meta_boxes =
array(
"issueslist" => array(
"type" => "select",
"std" => "",
"name" => "assignissue",
"title" => "Set Issue",
"description" => "select the issue for the content",
"category" => 4
)
);
Our meta box array defines a type of field (text, select, textarea), a name, the title associated with it, a description, and a category for the related content. In this case, category 4 is assigned to all Issue posts types. I’m currently using Magic Fields to accomplish custom write panels, which means I have not converted over to using custom post types as introduced fully in WordPress 3.0. To sort content out for my custom write panels, I assign categories to them, and then I exclude these categories from most loops. They only display when I write a custom loop for them.
Next, we write a function to loop through our $new_meta_boxes array and create the HTML used in the admin/. One thing I noticed going through this code–I’m not entirely sure why I left the new_meta_boxes array outside of the new_meta_boxes() function. Oh, and yes, I don’t use camelCase for naming my functions and variables. I find it’s easier to screw up capitalization than it is to leave out underscores, so it saves me time on coding. Your mileage may of course differ.
function new_meta_boxes() {
global $post, $new_meta_boxes;
foreach($new_meta_boxes as $meta_box) {
$args=array(
'category' => $meta_box['category'],
'orderby' => 'title',
'showposts' => '9999',
'order' => 'ASC',
);
$args_future= array(
'category' => $meta_box['category'],
'orderby' => 'title',
'showposts' => '9999',
'order' => 'ASC',
'post_status' => 'future'
);
$getposts = get_posts($args);
$getpostsFuture = get_posts($args_future);
echo'<input type="hidden" name="'.$meta_box['name'].'_noncename" id="'.$meta_box['name'].'_noncename" value="'.wp_create_nonce( plugin_basename(__FILE__) ).'" />';
echo'<h2>'.$meta_box['title'].'</h2>';
if( $meta_box['type'] == "text" ) {
$meta_box_value = get_post_meta($post->ID, $meta_box['name'].'_value', true);
if($meta_box_value == "")
$meta_box_value = $meta_box['std'];
echo'<input type="text" name="'.$meta_box['name'].'_value" value="'.$meta_box_value.'" size="55" />';
} elseif ( $meta_box['type'] == "select" ) {
echo'<select name="'.$meta_box['name'].'_value">';
echo'<option value="">'.$meta_box['title'].'</option>';
foreach ($getposts as $option) {
if ( get_post_meta($post->ID, $meta_box['name'].'_value', true) == $option->ID ) {
$sel = ' selected="selected"';
} else {
$sel = '';}
echo'<option value="'.$option->ID.'"'. $sel .'>'. $option->post_title.'</option>';
}
foreach ($getpostsFuture as $option) {
if ( get_post_meta($post->ID, $meta_box['name'].'_value', true) == $option->ID ) {
$sel = ' selected="selected"';
} else {
$sel = '';}
echo'<option value="'.$option->ID.'"'. $sel .'>'. $option->post_title.'</option>';
}
echo'</select>';
}
echo'<p><label for="'.$meta_box['name'].'_value">'.$meta_box['description'].'</label></p>';
}
}
To explain what’s happening here: we’re using get_posts to create an array of posts based on our settings from the meta boxes array. I’ve put together two post arrays here because we wanted to be able to set scheduled issues as well as posted ones. By default, scheduled posts aren’t queried, and I couldn’t figure out how to use a single get_posts() to get both future posts and published ones. Simple enough– I just tack on another foreach loop for the future posts to the bottom of the select. Still, it would have been better to just do one. I need to sort this out at some point.
Line 19 is a hidden field with nonces for security, and comes into play in our save_postdata() function that we’ll get to later on.
Line 20 is our header for the select box.
Lines 27–43 deal with the creation of the select boxes. For each of the get_posts arrays, we loop through and echo out a title inside of an option and an ID as the value, so that we ultimately can save the value of select box to the meta field on the post, and call it using get_post_meta in our templates.
We also have some basic if else code checking to see what the type of meta box is, as I anticipated maybe needing to add some regular text fields, but never actually needed them.
It’s not pretty and probably not as efficient as it could be, but it works.
Next, we create the function that we’ll actually call when we add the action using WordPress hooks. Add meta_box has a lot of parameters that you can’t pass when you use add_action, so you have to create a wrapper function like this. And it lets use one add_action to create all the meta fields.
function create_meta_box() {
global $theme_name;
if (function_exists('add_meta_box') ) {
add_meta_box( 'new-meta-boxes', 'Author Info', 'new_meta_boxes', 'post', 'normal', 'high' );
}
}
In retrospect, I’m not sure why this has the $theme_name global here. I should have commented that requirement so I would know why in the world I have it here. Ah well. And here’s how we hook our function into the admin so we get to see our handiwork in the WordPress admin:
add_action('admin_menu', 'create_meta_box');
All of the above will generate our display, but it doesn’t actually save anything. This next bit of code, I took mostly unmodified from the other tutorial. This function does all the necessary security checks– the nonce, checking the user to make sure they have the permissions to modify the data, and finally adding the data, or updating it, or deleting it, depending on what’s there already. When you’re working with small budgets, you learn to use what you can and save time, so as to save your clients money.
function save_postdata( $post_id ) {
global $post, $new_meta_boxes;
foreach($new_meta_boxes as $meta_box) {
// Verify
if ( !wp_verify_nonce( $_POST[$meta_box['name'].'_noncename'], plugin_basename(__FILE__) )) {
return $post_id;
}
if ( 'page' == $_POST['post_type'] ) {
if ( !current_user_can( 'edit_page', $post_id ))
return $post_id;
} else {
if ( !current_user_can( 'edit_post', $post_id ))
return $post_id;
}
$data = $_POST[$meta_box['name'].'_value'];
if(get_post_meta($post_id, $meta_box['name'].'_value') == "")
add_post_meta($post_id, $meta_box['name'].'_value', $data, true);
elseif($data != get_post_meta($post_id, $meta_box['name'].'_value', true))
update_post_meta($post_id, $meta_box['name'].'_value', $data);
elseif($data == "")
delete_post_meta($post_id, $meta_box['name'].'_value', get_post_meta($post_id, $meta_box['name'].'_value', true));
}
}
All that’s left is to hook this in with add_action to the save_post hook, so that when a post is saved, the data in our custom meta boxes are saved as well.
add_action('save_post', 'save_postdata');
So anywhere I needed to get the value of the issues box, I just use the following code:
$post_author = get_post_meta($post->ID, 'assignauthor_value', true);
That gives me a variable I can then use to query against in a custom loop, and then grab other custom fields using get() which is provided by the Magic Fields plugin.
So that’s the code in a nutshell. Questions and comments welcome!