Last week we introduced how to create custom meta boxes inside the post-editor and save the data you put in them. But what are some practical applications of this technique? Today, the goal is to go over three real world examples of using custom meta boxes to improve the post page.
In the introduction article, you learned all about how to implement meta boxes and save/clean the data the goes into them. That great! But it's time to go beyond the conceptual information and put those custom meta boxes to work.
Example 1. Adding a Quote to the Top of Posts
The scenario: You run a website that primarily publishes inspirational content. One of the things you do consistently is put quotes at the top of each post. To separate these quotes from the content, you want to move them into a custom meta box.
In the "how to" article, you learned how to actually implement meta boxes, but here's a quick review.
1. Add the Meta Box
Hook a function into the add_meta_boxes
that contains a call to the add_meta_box
function.
<?php add_action( 'add_meta_boxes', 'cd_add_quote_meta' ); function cd_add_quote_meta() { add_meta_box( 'quote-meta', __( 'Inspirational Quote' ), 'cd_quote_meta_cb', 'post', 'normal', 'high' ); } ?>
2. Render the Meta Box
The create a function with the same name as the $callback
specified in add_meta_box
. This is the piece that actually displays meta box content.
<?php function cd_quote_meta_cb( $post ) { // Get values for filling in the inputs if we have them. $quote = get_post_meta( $post->ID, '_cd_quote_content', true ); $author = get_post_meta( $post->ID, '_cd_quote_author', true ); $date = get_post_meta( $post->ID, '_cd_quote_date', true ); // Nonce to verify intention later wp_nonce_field( 'save_quote_meta', 'quote_nonce' ); ?> <p> <label for="quote-content">Quote</label> <textarea class="widefat" id="quote-content" name="_cd_quote_content"><?php echo $quote; ?></textarea> </p> <p> <label for="quote-author">Author</label> <input type="text" class="widefat" id="quote-author" name="_cd_quote_author" value="<?php echo $author; ?>" /> </p> <p> <label for="quote-date">Author Dates</label> <input type="text" class="widefat" id="quote-date" name="_cd_quote_date" value="<?php echo $date; ?>" /> </p> <?php } ?>
3. Save the Data
Hook a function into save_post
that first handles verifying permissions and intention and then cleans and saves the data.
<?php add_action( 'save_post', 'cd_quote_meta_save' ); function cd_quote_meta_save( $id ) { if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if( !isset( $_POST['quote_nonce'] ) || !wp_verify_nonce( $_POST['quote_nonce'], 'save_quote_meta' ) ) return; if( !current_user_can( 'edit_post' ) ) return; $allowed = array( 'p' => array() ); if( isset( $_POST['_cd_quote_content'] ) ) update_post_meta( $id, '_cd_quote_content', wp_kses( $_POST['_cd_quote_content'], $allowed ) ); if( isset( $_POST['_cd_quote_author'] ) ) update_post_meta( $id, '_cd_quote_author', esc_attr( strip_tags( $_POST['_cd_quote_author'] ) ) ); if( isset( $_POST['_cd_quote_date'] ) ) update_post_meta( $id, '_cd_quote_date', esc_attr( strip_tags( $_POST['_cd_quote_date'] ) ) ); } ?>
Now the Fun Part: Using the Data
We could use the data saved in our meta boxes by editing our theme's template files. But that's too easy. To keep our quote code modular (one plugin file), we'll use filter hooks, part of the Plugin API. Filter hooks are a bit different from actions. When you hook into a filter, the purpose is, most of the time, to alter how a piece of content appears on a page. In our case, we're going to hook into the_content
, and if we're on a single post page that has a quote we'll add it above.
Another way to think of action vs. filter hooks is that you echo
things out in actions (eg. wp_head, see section 2), but with filters you take in one or more variables, alter them, then return
them.
To display our quote, we'll hook into the_content
, which passes one variable by default: the content of a given post. Inside our hooked function, we'll make sure we're on a single post and, if we're not, return the content right way (no alterations).
<?php add_filter( 'the_content', 'cd_display_quote' ); function cd_display_quote( $content ) { // We only want this on single posts, bail if we're not in a single post if( !is_single() ) return $content; } ?>
Next up, we'll get our $post
variable. Because we're in the loop, we just call global $post
. Then we'll get our quote, if no value comes back, we know that no quote was entered and we return the content once again without alteration.
<?php add_filter( 'the_content', 'cd_display_quote' ); function cd_display_quote( $content ) { // We only want this on single posts, bail if we're not in a single post if( !is_single() ) return $content; // We're in the loop, so we can grab the $post variable global $post; $quote = get_post_meta( $post->ID, '_cd_quote_content', true ); // Bail if we don't have a quote; if( empty( $quote ) ) return $content; } ?>
Now that we've made sure we're on a single post and we actually have a quote, we an take care of putting things together. First we'll call our author and her dates via get_post_meta()
, then we can start constructing a string in the $out
variable. First we'll add a <blockquote>
and our quote. Then we check to see if the author field was filled out. If it was we'll start a paragraph for the author then check to see if there was a date and add that to the paragraph as well. Finally, we'll add our closing </blockquote>
tag.
<?php add_filter( 'the_content', 'cd_display_quote' ); function cd_display_quote( $content ) { // We only want this on single posts, bail if we're not in a single post if( !is_single() ) return $content; // We're in the loop, so we can grab the $post variable global $post; $quote = get_post_meta( $post->ID, '_cd_quote_content', true ); // Bail if we don't have a quote; if( empty( $quote ) ) return $content; // Assemble our quote $author = get_post_meta( $post->ID, '_cd_quote_author', true ); $date = get_post_meta( $post->ID, '_cd_quote_date', true ); $out = '<blockquote>' . $quote; if( !empty( $author ) ) { $out .= '<p class="quote-author">-' . $author; if( !empty( $date ) ) $out .= ' (' . $date . ')'; $out .= '</p>'; } $out .= '</blockquote>'; } ?>
Now the most crucial step: returning the combination of our newly made $out
string which contains the quote and the original content found in $content
.
<?php add_filter( 'the_content', 'cd_display_quote' ); function cd_display_quote( $content ) { // We only want this on single posts, bail if we're not in a single post if( !is_single() ) return $content; // We're in the loop, so we can grab the $post variable global $post; $quote = get_post_meta( $post->ID, '_cd_quote_content', true ); // Bail if we don't have a quote; if( empty( $quote ) ) return $content; // Assemble our quote $author = get_post_meta( $post->ID, '_cd_quote_author', true ); $date = get_post_meta( $post->ID, '_cd_quote_date', true ); $out = '<blockquote>' . $quote; if( !empty( $author ) ) { $out .= '<p class="quote-author">-' . $author; if( !empty( $date ) ) $out .= ' (' . $date . ')'; $out .= '</p>'; } $out .= '</blockquote>'; // Return the values: quote first, then the content return $out . $content; } ?>
That's it! You can see the result.
Example 2. Adding Open Graph Meta Tags
The scenario: You have an active community of readers who regularly share your articles on Facebook. This is awesome, and it sends a lot of traffic your way. But you start noticing that the images showing up with your articles on Facebook are less than ideal. You're also not happy with how your posts titles are coming out. The solution is to add Open Graph meta tags to control how your articles display. Rather than let this get taken care of automatically, you decide to implement a custom meta box to take care of it.
Set up the Meta Box
You probably have this down by now, but here's the code for getting your meta box going.
<?php add_action( 'add_meta_boxes', 'cd_add_opengraph_meta' ); function cd_add_opengraph_meta() { add_meta_box( 'opengraph-meta', 'Opengraph', 'cd_opengraph_meta_cb', 'post', 'normal', 'high' ); } function cd_opengraph_meta_cb( $post ) { // Grab our data to fill out the meta boxes (if it's there) $title = get_post_meta( $post->ID, '_cd_opengraph_title', true ); $desc = get_post_meta( $post->ID, '_cd_opengraph_desc', true ); $image = get_post_meta( $post->ID, '_cd_opengraph_image', true ); // Add a nonce field wp_nonce_field( 'save_opengraph_meta', 'opengraph_nonce' ); ?> <p> <label for="og-title">Title</label> <input type="text" id="og-title" class="widefat" name="_cd_opengraph_title" value="<?php echo esc_attr( $title ); ?>" /> </p> <p> <label for="og-desc">Description</label> <textarea id="og-desc" class="widefat" name="_cd_opengraph_desc"><?php echo esc_attr( $desc ); ?></textarea> </p> <p> <label for="og-image">Image</label><br /> <input type="text" id="og-image" style="width:300px" name="_cd_opengraph_image" value="<?php echo esc_url( $image ); ?>" /> <input type="button" id="cdog-thickbox" value="Upload Image" /><br/> <em>Small, square images work best.</em> </p> <?php } add_action( 'save_post', 'cd_opengraph_save' ); function cd_opengraph_save( $id ) { // No auto saves if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; // Check our nonce if( !isset( $_POST['opengraph_nonce'] ) || !wp_verify_nonce( $_POST['opengraph_nonce'], 'save_opengraph_meta' ) ) return; // make sure the current user can edit the post if( !current_user_can( 'edit_post' ) ) return; // strip all html tags and esc attributes here if( isset( $_POST['_cd_opengraph_title'] ) ) update_post_meta( $id, '_cd_opengraph_title', esc_attr( strip_tags( $_POST['_cd_opengraph_title'] ) ) ); // same as above if( isset( $_POST['_cd_opengraph_desc'] ) ) update_post_meta( $id, '_cd_opengraph_desc', esc_attr( strip_tags( $_POST['_cd_opengraph_desc'] ) ) ); // make sure we get a clean url here with esc_url if( isset( $_POST['_cd_opengraph_image'] ) ) update_post_meta( $id, '_cd_opengraph_image', esc_url( $_POST['_cd_opengraph_image'], array( 'http' ) ) ); } ?>
Add Some JavaScript
To make that "Upload Image" button work, we'll have to add a bit of javascript that hijacks the built in WordPress uploader. This means we're going to use yet another action hook. This time its admin_print_script-{$page}
.
When adding scripts and/or styles to the admin area of WordPress, there is one golden rule: only add the scripts/styles where you need them. This prevents your plugin/theme from breaking something else on accident. admin_print_scripts-{$page}
lets you only insert scripts (via wp_enqueue_script function) only on the $page
specified. In this case, we need to add our script to the post.php
and post-new.php
screens. This requires hook the same function into both.
<?php add_action( 'admin_print_scripts-post.php', 'cd_opengraph_enqueue' ); add_action( 'admin_print_scripts-post-new.php', 'cd_opengraph_enqueue' ); function cd_opengraph_enqueue() { wp_enqueue_script( 'cdog-thickbox', plugin_dir_url( __FILE__ ) . 'thickbox-hijack.js', array(), NULL ); } ?>
And the javascript.
jQuery(document).ready(function() { var ogfield = null; jQuery( '#cdog-thickbox' ).click(function() { ogfield = jQuery( 'input#og-image' ).attr( 'name' ); tb_show( '', 'media-upload.php?type=image&TB_iframe=true' ); return false; }); window.send_to_editor_old = window.send_to_editor; window.send_to_editor = function( html ) { var ogimg; if( ogfield != null ) { ogimg = jQuery( 'img', html ).attr( 'src' ); jQuery( 'input#og-image' ).val( ogimg ); tb_remove(); ogfield = null; } else { window.send_to_editor_old( html ); } }; });
First we make sure the thickbox uploader pops up when clicking on the button, and we set up a variable that tells WordPress that it was our button that was clicked. Next, we save the window.send_to_editor
function with a new name. This is the function that actually inserts the image HTML into the post editing area. We're going to hijack this function to send on the src attribute to our own form field, but only if the thickbox was brought up by our button.
Add the Open Graph Tags
We're going to hook into the wp_head
action to add our meta tags in the <head>
section. First we'll make sure we're on a single post page, and then set up our $post
variable. $post
shouldn't be empty at this point as WordPress has already decided what sort of object its rendering and what template file it needs to use. But, in case it is, we'll retrieve post with get_queried_object()
.
<?php add_action( 'wp_head', 'cd_opengraph_display' ); function cd_opengraph_display() { // If this isn't a single post, bail if( !is_single() ) return; global $post, $wp_query; // if $post is empty, get the queired object if( empty( $post ) ) $post = $wp_query->get_queried_object(); } ?>
Next up we can go through each Open Graph variable, fetching everything with get_post_custom()
, and, if its there, echo it out into the head section of our page.
<?php add_action( 'wp_head', 'cd_opengraph_display' ); function cd_opengraph_display() { // If this isn't a single post, bail if( !is_single() ) return; global $post, $wp_query; // if $post is empty, get the queired object if( empty( $post ) ) $post = $wp_query->get_queried_object(); $values = get_post_custom( $post->ID ); if( isset( $values['_cd_opengraph_title'] ) ) echo '<meta name="og:title" value="' . esc_attr( $values['_cd_opengraph_title'][0] ) . '" />' ."\n"; if( isset( $values['_cd_opengraph_desc'] ) ) echo '<meta name="og:description" value="' . esc_attr( $values['_cd_opengraph_desc'][0] ) . '" />' . "\n"; if( isset( $values['_cd_opengraph_image'] ) ) echo '<meta name="og:image" value="' . esc_url( $values['_cd_opengraph_image'][0] ) . '" />' . "\n"; } ?>
Example 3. Change Twenty Eleven Layouts on the Fly
The scenario: You rely heavily on Twenty Eleven's sidebar page template. But you want to be able to switch between left and right sidebars for each page.
The following code would be something better left in a theme's functions file. That said, because we're using a plugin here, we can hook into the init
and with our function check to make sure Twenty Eleven is the current theme. If it's not, we'll deactivate the plugin. First, however, we'll define a constant containing the URL of the directory in which our plugin resides.
<?php define( 'CDSB_URL' , plugin_dir_url( __FILE__ ) ); add_action( 'admin_init', 'cd_test_2011' ); function cd_test_2011() { if( get_current_theme() != 'Twenty Eleven' ) deactivate_plugins( __FILE__ ); } ?>
Adding the Meta Box
Same routine as before: add the meta box, render it, and save the data. This time, however, we're going to display our meta box on the edit screen for pages. We're also going to use a nifty wordpress function called get_template_directory_uri, which returns a string containing the URI of the directory for the current theme. We're going to use this to borrow a few images that Twenty Eleven uses on its theme options page. We're also going to use the constant we defined earlier to add an image of our own.
<?php add_action( 'add_meta_boxes', 'cd_layout_meta' ); function cd_layout_meta() { add_meta_box( 'cd-sidebar-pos', 'Page Layout', 'cd_layout_meta_cb', 'page', 'normal', 'high' ); } function cd_layout_meta_cb( $post ) { $layout = get_post_meta( $post->ID, '_cd_post_layout', true ); // Set our layout variable, even on new posts if( empty( $layout ) ) $layout = 'default'; // Theme directory for borrowing 2011 images $dir = get_template_directory_uri(); wp_nonce_field( 'save_post_layout', 'layout_nonce' ); ?> <fieldset class="clearfix"> <p>Please note: this only works if you've selected "Sidebar Template" in the Page Attributes section</p> <div class="cd-layout"> <input type="radio" id="sidebar-default" name="_cd_post_layout" value="default" <?php checked( $layout, 'default' ); ?> /> <label for="sidebar-default"> <img src="<?php echo CDSB_URL ?>default.png" alt="Use the Default Sidebar" /> <span>Use theme default</span> </label> </div> <div class="cd-layout"> <input type="radio" id="sidebar-left" name="_cd_post_layout" value="left" <?php checked( $layout, 'left' ); ?> /> <label for="sidebar-left"> <img src="<?php echo $dir; ?>/inc/images/sidebar-content.png" alt="sidebar then content" /> <span>Sidebar on the left</span> </label> </div> <div class="cd-layout"> <input type="radio" id="sidebar-right" name="_cd_post_layout" value="right" <?php checked( $layout, 'right' ); ?> /> <label for="sidebar-right"> <img src="<?php echo $dir; ?>/inc/images/content-sidebar.png" alt="content then sidebar" /> <span>Sidebar on the right</span> </label> </div> </fieldset> <?php } add_action( 'save_post', 'cd_layout_save' ); function cd_layout_save( $id ) { if( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return; if( !isset( $_POST['layout_nonce'] ) || !wp_verify_nonce( $_POST['layout_nonce'], 'save_post_layout' ) ) return; if( !current_user_can( 'edit_page' ) ) return; if( isset( $_POST['_cd_post_layout'] ) ) update_post_meta( $id, '_cd_post_layout', esc_attr( strip_tags( $_POST['_cd_post_layout'] ) ) ); } ?>
To prettify our meta box a bit, we'll need to add our own stylesheet as well. Remember admin_print_scripts-{$page}
from the second scenario above? It has a brother, admin_print_styles-{$page}
, which, as the name implies, lets you add stylesheet to the wordpress admin on specific pages. We'll need to hook into this function for post.php
and post-new.php
. We'll also be using wp_enqueue_style(); it works the same was as wp_enqueue_script(), which we used in the second example above.
<?php add_action( 'admin_print_styles-post.php', 'cd_layout_enqueue' ); add_action( 'admin_print_styles-post-new.php', 'cd_layout_enqueue' ); function cd_layout_enqueue() { wp_enqueue_style( 'cdlayout-style', CDSB_URL . 'style.css', array(), NULL, 'all' ); } ?>
And the CSS.
div.cd-layout { width:200px; float:left; } div.cd-layout input { display:block; } #cd-sidebar-pos .clearfix:after { clear: both; content: ' '; display: block; font-size: 0; line-height: 0; visibility: hidden; width: 0; height: 0; } #cd-sidebar-pos label span { display:block; margin-top:5px; }
Digging into Twenty Eleven
Twenty Eleven accomplishes its sidebar positioning by hooking into a filter called body_class
. This is part of the function called <?php body_class(); ?>
, which, if you've designed a theme before, you've probably used. If the default layout is two column, Twenty Eleven adds one of two additional items to body_class: right-sidebar or left-sidebar. You can see the code for this in the theme's inc
folder in the file theme-options.php
.
Our own code is also going to hook into body_class
. First, we'll make sure we're on a page, and that that page is using the Sidebar Template. Then we'll get the $post
variable or set it if its empty. Notice two additional arguments for add_filter
. 99 is the priority. We want this to fire last, so we use a higher number. 1 is the number or arguments to send to our function.
<?php add_filter( 'body_class', 'cd_change_layout', 99, 1 ); function cd_change_layout( $classes ) { // If this isn't a page, or we're not using the sidebar template, get out of here if( !is_page() || !is_page_template( 'sidebar-page.php' ) ) return $classes; // make sure we have the $post object (the current item ) global $post; if( empty( $post ) ) $post = get_queried_object(); } ?>
body_class
will send an array of all the items that will go into the body_class()
output function. From here, we just need to get our own meta values. If our value is 'right', we'll look for "left-sidebar" in the body class array. If it's there, we'll unset it and replace it with "right-sidebar". Vice versa if our value is left.
<?php add_filter( 'body_class', 'cd_change_layout', 99, 1 ); function cd_change_layout( $classes ) { // If this isn't a page, or we're not using the sidebar template, get out of here if( !is_page() || !is_page_template( 'sidebar-page.php' ) ) return $classes; // make sure we have the $post object (eg. the current item ) global $post; if( empty( $post ) ) $post = get_queried_object(); $layout = get_post_meta( $post->ID, '_cd_post_layout', true ); // if we're using the right layout, add if( $layout == 'right' ) { $key = array_search( 'left-sidebar', $classes ); if( $key ) { unset( $classes[$key] ); $classes[] = 'right-sidebar'; } } elseif ( $layout = 'left' ) { $key = array_search( 'right-sidebar', $classes ); if( $key ) { unset( $classes[$key] ); $classes[] = 'left-sidebar'; } } return $classes; } ?>
The above would work, but we've left out a little detail. If a user happened to have Twenty Eleven's theme options set to a one column display, none of the options in our meta box would work. So lets modify out add_meta_box
call a bit. First we'll get Twenty Eleven's options, then we'll make sure the theme layout option is not set to one column. If the theme is set to one column, we won't add the meta box.
<?php add_action( 'add_meta_boxes', 'cd_layout_meta' ); function cd_layout_meta() { $opts = get_option( 'twentyeleven_theme_options' ); if( $opts['theme_layout'] != 'content' ) add_meta_box( 'cd-sidebar-pos', 'Page Layout', 'cd_layout_meta_cb', 'page', 'normal', 'high' ); } ?>
Wrap Up
As you might imagine, there are many other uses for custom meta boxes... these are just a few practical examples to get your mind working. When combined with custom post types, they allow you to create extremely customized editing screens. The real strength of custom meta boxes, however, lies in how theme designers and plugin developers can create more user friendly interfaces for post or page level settings.
We hope you enjoyed the tutorial!
Comments