Over the course of the next two posts, we'll take a look at how we can leverage the WordPress API to define our own custom meta boxes for attaching a document (such as a PDF) to our WordPress pages. We'll also take a look at how to properly remove files should we choose to delete them from our posts.
A few months ago, Christopher Davis demonstrated how to create Custom WordPress Write/Meta Boxes (http://wp.tutsplus.com/tutorials/plugins/how-to-create-custom-wordpress-writemeta-boxes/). As mentioned in his article, custom meta boxes are incredibly powerful features that allow us to add various pieces of additional information to our WordPress posts and pages.
Of course, we're not limited to text or options such as radio buttons or checkboxes. We can also attach images and files to posts, pages, and other post types by taking advantage of custom meta boxes; however, dealing with file types requires a bit more code to properly handle uploads and deletions of the file.
Before getting started, it's important to note that this series assumes that we're working with the Twentyeleven Theme. Although the code will work with any WordPress Theme, this ensures that we're all working with the same codebase and should make it easier to follow along with the tutorial.
Defining The Custom Meta Box
First, let's create the post meta box. Specifically, the meta box should…
- Be available on both posts and pages
- Appear beside the post editor
- Accept an input file
At this point, locate functions.php in the root of the Twentyeleven Theme directory. We'll be making all of our changes to the bottom of the file. We'll start by defining a function called add_custom_meta_boxes and it will be registered with the add_meta_boxes hook.
function add_custom_meta_boxes() { } // end add_custom_meta_boxes add_action('add_meta_boxes', 'add_custom_meta_boxes');
Next, let's define our custom meta box. First, we'll write the code after which I'll explain what the code is doing:
function add_custom_meta_boxes() { // Define the custom attachment for posts add_meta_box( 'wp_custom_attachment', 'Custom Attachment', 'wp_custom_attachment', 'post', 'side' ); // Define the custom attachment for pages add_meta_box( 'wp_custom_attachment', 'Custom Attachment', 'wp_custom_attachment', 'page', 'side' ); } // end add_custom_meta_boxes add_action('add_meta_boxes', 'add_custom_meta_boxes');
Notice that the following two calls to add_meta_box are nearly identical.
- The first parameter is the ID of the meta box. This is used when saving the value.
- The second parameter is the label. This value appears in the caption above the post meta box
- The third value is the callback function that is used to actually define the markup that appears in the meta box. We'll get to this momentarily.
- The fourth value tells WordPress the post type on which this custom meta box should appear. Since we want it on both posts and pages, we've defined it twice.
- The final parameter defines where we want the meta box to appear. It can be either side, advanced, or normal. We've select side so that it appears beside the post editor
Setting Up The Callback
At this point, the custom meta box doesn't do anything. In fact, it doesn't even display anything:
This is because we haven't defined the callback function that is used generate the markup for the meta box. In order to do that, we need to define the function with the name that we listed it above. Specifically: 'wp_custom_attachment.'
The input should accept a PDF so we'll give a short description and the proper input element for accepting files:
function wp_custom_attachment() { wp_nonce_field(plugin_basename(__FILE__), 'wp_custom_attachment_nonce'); $html = '<p class="description">'; $html .= 'Upload your PDF here.'; $html .= '</p>'; $html .= '<input type="file" id="wp_custom_attachment" name="wp_custom_attachment" value="" size="25" />'; echo $html; } // end wp_custom_attachment
The first line of the code defines a nonce value in order to properly validate and secure our upload.
Next, we're simply setting up the markup for displaying the input field.
At this point, we've got a custom meta box that looks decent but doesn't actually work.
Saving The File
Now we're ready to save the file. First, we need to create a function that hooks into the save_post hook. Let's define that now:
function save_custom_meta_data($id) { /* --- security verification --- */ if(!wp_verify_nonce($_POST['wp_custom_attachment_nonce'], plugin_basename(__FILE__))) { return $id; } // end if if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $id; } // end if if('page' == $_POST['post_type']) { if(!current_user_can('edit_page', $id)) { return $id; } // end if } else { if(!current_user_can('edit_page', $id)) { return $id; } // end if } // end if /* - end security verification - */ } // end save_custom_meta_data add_action('save_post', 'save_custom_meta_data');
Although this function doesn't actually save the value - not yet, at least - it includes a bit of code that ensures we're ready to save the file. Specifically, the function makes sure that the expected nonce value is present, that an automatic save is not occurring, and that the user attempting to save data has permissions to do so.
Now we're ready to begin validating and saving the file. When it comes to saving file-based custom meta data, additional code has to be introduced to properly handle indiosynchrocies of uploading files. First, we'll define the code then we'll explain it. The function should look like this:
function save_custom_meta_data($id) { /* --- security verification --- */ if(!wp_verify_nonce($_POST['wp_custom_attachment_nonce'], plugin_basename(__FILE__))) { return $id; } // end if if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $id; } // end if if('page' == $_POST['post_type']) { if(!current_user_can('edit_page', $id)) { return $id; } // end if } else { if(!current_user_can('edit_page', $id)) { return $id; } // end if } // end if /* - end security verification - */ // Make sure the file array isn't empty if(!empty($_FILES['wp_custom_attachment']['name'])) { // Setup the array of supported file types. In this case, it's just PDF. $supported_types = array('application/pdf'); // Get the file type of the upload $arr_file_type = wp_check_filetype(basename($_FILES['wp_custom_attachment']['name'])); $uploaded_type = $arr_file_type['type']; // Check if the type is supported. If not, throw an error. if(in_array($uploaded_type, $supported_types)) { // Use the WordPress API to upload the file $upload = wp_upload_bits($_FILES['wp_custom_attachment']['name'], null, file_get_contents($_FILES['wp_custom_attachment']['tmp_name'])); if(isset($upload['error']) && $upload['error'] != 0) { wp_die('There was an error uploading your file. The error is: ' . $upload['error']); } else { add_post_meta($id, 'wp_custom_attachment', $upload); update_post_meta($id, 'wp_custom_attachment', $upload); } // end if/else } else { wp_die("The file type that you've uploaded is not a PDF."); } // end if/else } // end if } // end save_custom_meta_data add_action('save_post', 'save_custom_meta_data');
The new block of code does several things. Comments have been provided in order to give clarity, but here's what's happening:
- First, we make sure the file array isn't empty
- Next, we setup an array for the supported file types and verify that the uploaded file is of that type
- Next, we use wp_upload_bits to copy the file to the server
- Finally, if there are any errors, we'll halt execution and display them to the user.
A note on wp_upload_bits(http://codex.wordpress.org/Function_Reference/wp_upload_bits). This function is an alternative to wp_handle_upload(http://codex.wordpress.org/Function_Reference/wp_handle_upload). In my experience, wp_handle_upload has yielded some problems with certain server configurations such that it gives a false negative. By that, I mean that it claims that it has a problem uploading the file when, in reality, the file was actually uploaded.
Linking Up the File
At this point, we should be ready to provide a link to the file on our posts and our pages. In the Twentyeleven Theme directory, locate the two files single.php and page.php. Each file contains a line of code that looks like this:
get_template_part( 'content', 'single' );
Just below that line, we'll need to request the custom post meta information by doing this:
echo get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
Specifically, this function is requesting the post meta data identified by 'wp_custom_attachment' associated with this post ID. Clear, right? The last parameter is telling WordPress that we want the result back in a string format (the alternative is in the format of an array and that's beyond the scope of this tutorial).
The final block of code should look like this:
get_template_part( 'content', 'single' );
echo get_post_meta(get_the_ID(), 'wp_custom_attachment', true);
Now, attempt to upload a file for a page or post. Load up the post and page in your browser. Permitting everything has gone correctly, you'll notice that ... oops ... you don't actually have the path to a file.
Update The Post Form
The problem is that, by default, the form element used to save all of the post information and its associated data doesn't accept file types. This can be fixed with adding one more function that hooks into the WordPress page life cycle:
function update_edit_form() { echo ' enctype="multipart/form-data"'; } // end update_edit_form add_action('post_edit_form_tag', 'update_edit_form');
This will append the enctype attribute to the post editor form element so that file uploads will not be supported.
Now, let's try to upload a file a again. Locate your post or page with the custom post meta box and attempt to upload a PDF. If all goes well, you should be able to navigate to the post and/or page and see the URL to the file.
Linking It Up
The last step is the easiest. Next up, revisit the single.php and page.php files and wrap the call to the custom meta data request in an anchor such that it looks like this:
<?php get_template_part( 'content', 'single' ); ?> <?php $img = get_post_meta(get_the_ID(), 'wp_custom_attachment', true); ?>
<a href="<?php echo $img['url']; ?>"> Download PDF Here </a>
At this point, you should be able to attach a custom PDF to your page and have a link appear at the bottom of the page content providing a link to the download. In the next post, we'll take a look at how we can provide some better styling for the download anchor as well as delete the file from the page using the WordPress API.
In the meantime, try experimenting with customizing the meta box even further. For example, attempt to include a custom label to provide content for the link.
Comments