Hehe, you're a newbie! We're gonna rip ya to shreds...!
j/k :) We offer warm welcome to all newbies here, glad to have you.
So this is the 3rd time I've heard this requirement, twice from clients and not again from you (and your client.) That tells me it's a reasonably common need.
I liked your analysis so I decided to code up a class to address your 2nd point. I called it LittlePromoBoxes
because I can never get this song out of my head, thanks to them. Basically I use the class to encapsulate to otherwise avoid potential naming conflicts with the functions I'd need to write.
You can put this class in your theme's functions.php
file or in a .PHP file of a plugin you might be writing (but don't worry, it looks a lot more complex than it is.)
The first function on_load()
is a static function which I call at the end of the class declaration to initialize the three (3) hooks you'll need (fyi static functions are essentially functions related to the class, not the instance) :
The init
hook to register the promo-box
post type,
The add_meta_boxes_post
hook to allow you to define the metabox, and
The wp_insert_post_data
hook to allow you to capture the selected promo boxes and save to the database.
Each of those hooks reference another static function in the class (these were the functions I was encapsulating by creating the class.)
I'll skip describing the action_init()
function and my make_labels()
helper function assuming you know how to register a post type based on you question.
The action_add_meta_boxes_post()
function registers the metabox using the WordPress core function add_meta_box()
and I've commented it's parameters to explain why I passed what I passed for each. The callback function the_little_promo_boxes_metabox()
is of course another static function of the class and it is what actually displays the content in the metabox. It primarily uses the WordPress core function wp_dropdown_pages()
to display a list of promo boxes (note that it will display other post types besides 'page' but only if they are marked as being 'hierarchical'=>true
in their post type registration. Why only hierarchical? Because that's the way they wrote it, that's why! :)
Since we're showing three (3) dropdowns we need to give each a unique ID in the HTML ("promo_box_{$i}"
) but the same name with square brackets ('promo_boxes[]'
) so that PHP will collect them into an array inside $_POST
variable (which WordPress accesses for us; you'll see how in a minute). And of course we need to set the selected value ((empty($promo_boxes[$i]) ? 0 : $promo_boxes[$i])
) if indeed one of the values had previously been selected.
I also used the WordPress core function get_post_type_object()
to show how to get the labels from a post type, and also using the WordPress core function get_post_meta()
to retrieve an array of promo box IDs from using the custom field key '_promo_boxes' which I'll show you have to save next (note I used a preceding underscore in the name '_promo_boxes'
which causes WordPress to hide from the standard custom field UI when the user is editing the post.).
The last function to describe before you see the code is filter_wp_insert_post_data()
which receives the existing post data in the first parameter ($data
) and the contents of the $_POST
array thanks to WordPress as the second parameter ($postarr
). Inside this function we call the WordPress core function update_post_meta()
and extract the promo boxes array ($postarr['promo_boxes']
) to save to the custom field value for the key '_promo_boxes'
for the post specified by the $_POST
array (i.e. $postarr['ID']
).
That said, here's the code for the LittlePromoBoxes
class:
class LittlePromoBoxes {
static function on_load() {
add_action('init',array(__CLASS__,'action_init'));
add_action('add_meta_boxes_post',array(__CLASS__,'action_add_meta_boxes_post'));
add_filter('wp_insert_post_data',array(__CLASS__,'filter_wp_insert_post_data'),10,2);
}
static function action_init() {
register_post_type('promo-box',array(
'labels' => self::make_labels('Promo Box','Promo Boxes'),
'public_queryable'=> false,
'hierarchical' => true, // IMPORTANT!!! wp_dropdown_pages() requires 'hierarchical'=>true
'show_ui' => true,
'query_var' => false,
'supports' => array('title','editor','thumbnail','custom-fields'),
'show_in_nav_menus'=>true,
'exclude_from_search'=>true,
));
}
static function make_labels($singular,$plural=false,$args=array()) {
if ($plural===false)
$plural = $singular . 's';
elseif ($plural===true)
$plural = $singular;
$defaults = array(
'name' =>_x($plural,'post type general name'),
'singular_name' =>_x($singular,'post type singular name'),
'add_new' =>_x('Add New',$singular),
'add_new_item' =>__("Add New $singular"),
'edit_item' =>__("Edit $singular"),
'new_item' =>__("New $singular"),
'view_item' =>__("View $singular"),
'search_items' =>__("Search $plural"),
'not_found' =>__("No $plural Found"),
'not_found_in_trash'=>__("No $plural Found in Trash"),
'parent_item_colon' =>'',
);
return wp_parse_args($args,$defaults);
}
static function action_add_meta_boxes_post($post) {
add_meta_box(
'little-promo-boxes', // Metabox Name, used as the "id" for a wrapping div
'Little Promo Boxes', // Metabox Title, visible to the user
array(__CLASS__,'the_little_promo_boxes_metabox'), // Callback function
'post', // Add to the Edit screen for Post Types of 'post'
'side', // Show it in the sidebar (if center then it would be 'normal'
'low' // Show it below metaboxes that specify 'high'
);
}
static function the_little_promo_boxes_metabox($post) {
$pto = get_post_type_object('promo-box');
$default_options = array(
'post_type' => 'promo-box',
'show_option_none' => "Select a {$pto->labels->singular_name}",
);
$promo_boxes = get_post_meta($post->ID,'_promo_boxes',true);
for($i=0; $i<=2; $i++) {
wp_dropdown_pages(array_merge($default_options,array(
'id' => "promo_box_{$i}",
'name' => 'promo_boxes[]',
'selected' => (empty($promo_boxes[$i]) ? 0 : $promo_boxes[$i]),
)));
}
}
static function filter_wp_insert_post_data($data, $postarr) {
update_post_meta($postarr['ID'],'_promo_boxes',$postarr['promo_boxes']);
return $data;
}
static function get_promo_boxes($post=false) {
static $promo_boxes=array();
if (!$post)
$post = $GLOBALS['post'];
if (!isset($promo_boxes[$post->ID])) {
$promo_boxes[$post->ID] = get_post_meta($post->ID,'_promo_boxes',true);
$index = 0;
foreach($promo_boxes[$post->ID] as $promo_box_id) {
$promo_boxes[$post->ID][$index++] = (is_numeric($promo_box_id) ? get_post($promo_box_id) : false);
}
}
return $promo_boxes[$post->ID];
}
static function get_promo_box($number,$post=false) {
$promo_boxes = self::get_promo_boxes($post);
return $promo_boxes[$number-1];
}
}
LittlePromoBoxes::on_load();
There are still two (2) static functions not yet mentioned: get_promo_boxes()
and get_promo_box()
; these are helper functions to help you retrieve the posts of post_type='promo-box'
by their ordinal numbers 1..3. But to make them more WordPress like here are two wrapper functions to add to your theme's functions.php
file (note that you can pass a post as a parameter but you don't have to unless you are using a different post that the one in The Loop):
function get_little_promo_boxes($post=false) {
return LittlePromoBoxes::get_promo_boxes($post);
}
function get_little_promo_box($number,$post=false) {
return LittlePromoBoxes::get_promo_box($number,$post);
}
Now you can call one or both of these functions in your single.php
theme file with code that might look like this (this code could have been written in a loop but most WordPress themers seem to like to duplicate code so they can read it instead of eliminating redundancy. So, when in Rome...):
<?php
$promo_boxes = get_little_promo_boxes();
if (isset($promo_boxes[1]))
echo '<div id="promo-box1" class="promo-box">' . get_the_title($promo_boxes[1]->ID) . '</div>';
if (isset($promo_boxes[2]))
echo '<div id="promo-box2" class="promo-box">' . get_the_title($promo_boxes[2]->ID) . '</div>';
if (isset($promo_boxes[3]))
echo '<div id="promo-box3" class="promo-box">' . get_the_title($promo_boxes[3]->ID) . '</div>';
?>