How To Create A Better Meta Box In WordPress (Part 2)

wordpress How To Create A Better Meta Box In WordPress (Part 2)

After I wrote the article How To Create A Better Meta Box In WordPress Post Editing Page, I received some questions that asked me how to create multiple meta boxes for multiple post types (in WordPress 3.0). So I decide to improve the script to do that.

Note: It’s strongly recommended to read the first part, because I won’t take your time by explaining all variables’ meanings. They can be found in the first part.

To follow this part, you should download the source code of the first part and alter it.

Multiple Post Types

Since version 3.0, WordPress allows us to create custom post types, each post type can have it own meta boxes. So, to register multiple post types for each meta box, we can do like that:

$meta_box = array(
    'id' => 'my-meta-box-1',
    'title' => 'Custom meta box 1',
    'pages' => array('post', 'page', 'link'), // multiple post types, accept custom post types
    'context' => 'normal',
    'priority' => 'high',
    'fields' => array(
        array(
            'name' => 'Text box',
            'desc' => 'Enter something here',
            'id' => $prefix . 'text',
            'type' => 'text',
            'std' => 'Default value 1'
        )
    )
);

To add this meta box to multiple editing screens for multiple post types, we just call the function add_meta_box multiple times (in function mytheme_add_box) like this:

// Add meta box
function mytheme_add_box() {
    global $meta_box;

    foreach ($meta_box['pages'] as $page) {
        add_meta_box($meta_box['id'], $meta_box['title'], 'mytheme_show_box', $page, $meta_box['context'], $meta_box['priority']);
    }
}

This function will loop all the registered post types, and for each post type create a meta box with given data.

Note: the term ‘post type’ is used here not very exactly, for example ‘link’ is not a post type. It’s the type of editing page, which includes post type. But we accept this for easy understanding.

Multiple Meta Boxes

1. Define multiple meta boxes

For creating multiple meta boxes, we first need to declare them. We’ll use the array() syntax like this:

$meta_boxes = array();

// first meta box
$meta_boxes[] = array(
    'id' => 'my-meta-box-1',
    'title' => 'Custom meta box 1',
    'pages' => array('post', 'page', 'link'), // multiple post types
    'context' => 'normal',
    'priority' => 'high',
    'fields' => array(
        array(
            'name' => 'Text box',
            'desc' => 'Enter something here',
            'id' => $prefix . 'text',
            'type' => 'text',
            'std' => 'Default value 1'
        )
    )
);

// second meta box
$meta_boxes[] = array(
    'id' => 'my-meta-box-2',
    'title' => 'Custom meta box 2',
    'pages' => array('post', 'link'), // custom post type
    'context' => 'normal',
    'priority' => 'high',
    'fields' => array(
        array(
            'name' => 'Select box',
            'id' => $prefix . 'select',
            'type' => 'select',
            'options' => array('Option 1', 'Option 2', 'Option 3')
        )
    )
);

All the parameters I’ve explained carefully in the first part. You might want to take a look if you forget.

Here, I declared each meta box as an element of a big array $meta_boxes. I separate meta box’s definition to make it easy to scan. Of course, you can declare like this:

$meta_boxes = array(
    array(
        'id' => 'my-meta-box-1',
        'title' => 'Custom meta box 1',
        'pages' => array('post', 'page', 'link'), // multiple post types
        'context' => 'normal',
        'priority' => 'high',
        'fields' => array(
            array(
                'name' => 'Text box',
                'desc' => 'Enter something here',
                'id' => $prefix . 'text',
                'type' => 'text',
                'std' => 'Default value 1'
            )
        )
    ),
    array(
        'id' => 'my-meta-box-2',
        'title' => 'Custom meta box 2',
        'pages' => array('post', 'link'), // custom post type
        'context' => 'normal',
        'priority' => 'high',
        'fields' => array(
            array(
                'name' => 'Select box',
                'id' => $prefix . 'select',
                'type' => 'select',
                'options' => array('Option 1', 'Option 2', 'Option 3')
            )
        )
    )
);

Just the style, don’t waste time in there.

2. Wrap all our code to a class

When first time I tried to create multiple meta boxes, I wanted to use the foreach loop to walkthrough the $meta_boxes and for each meta box, I would use the same function to create, show it and save it’s data.

Unfortunately, the function mytheme_show_box, used for displaying meta box cannot handle multiple meta boxes’ data. It can display only one meta box at one time.

So, I think about using a class, that take the meta box’s data and deal with it. We can use it as in the following code:

foreach ($meta_boxes as $meta_box) {
    $my_box = new My_meta_box($meta_box);
}

All old functions are now wrapped in My_meta_box class as in the following code:

class My_meta_box {

    protected $_meta_box;

    // create meta box based on given data
    function __construct($meta_box) {
        $this->_meta_box = $meta_box;
        add_action('admin_menu', array(&$this, 'add'));

        add_action('save_post', array(&$this, 'save'));
    }

    /// Add meta box for multiple post types
    function add() {
        foreach ($this->_meta_box['pages'] as $page) {
            add_meta_box($this->_meta_box['id'], $this->_meta_box['title'], array(&$this, 'show'), $page, $this->_meta_box['context'], $this->_meta_box['priority']);
        }
    }

    // Callback function to show fields in meta box
    function show() {
        global $post;

        // Use nonce for verification
        echo '<input type="hidden" name="mytheme_meta_box_nonce" value="', wp_create_nonce(basename(__FILE__)), '" />';

        echo '<table class="form-table">';

        foreach ($this->_meta_box['fields'] as $field) {
            // get current post meta data
            $meta = get_post_meta($post->ID, $field['id'], true);

            echo '<tr>',
                    '<th style="width:20%"><label for="', $field['id'], '">', $field['name'], '</label></th>',
                    '<td>';
            switch ($field['type']) {
                case 'text':
                    echo '<input type="text" name="', $field['id'], '" id="', $field['id'], '" value="', $meta ? $meta : $field['std'], '" size="30" style="width:97%" />',
                        '<br />', $field['desc'];
                    break;
                case 'textarea':
                    echo '<textarea name="', $field['id'], '" id="', $field['id'], '" cols="60" rows="4" style="width:97%">', $meta ? $meta : $field['std'], '</textarea>',
                        '<br />', $field['desc'];
                    break;
                case 'select':
                    echo '<select name="', $field['id'], '" id="', $field['id'], '">';
                    foreach ($field['options'] as $option) {
                        echo '<option', $meta == $option ? ' selected="selected"' : '', '>', $option, '</option>';
                    }
                    echo '</select>';
                    break;
                case 'radio':
                    foreach ($field['options'] as $option) {
                        echo '<input type="radio" name="', $field['id'], '" value="', $option['value'], '"', $meta == $option['value'] ? ' checked="checked"' : '', ' />', $option['name'];
                    }
                    break;
                case 'checkbox':
                    echo '<input type="checkbox" name="', $field['id'], '" id="', $field['id'], '"', $meta ? ' checked="checked"' : '', ' />';
                    break;
            }
            echo     '<td>',
                '</tr>';
        }

        echo '</table>';
    }

    // Save data from meta box
    function save($post_id) {
        // verify nonce
        if (!wp_verify_nonce($_POST['mytheme_meta_box_nonce'], basename(__FILE__))) {
            return $post_id;
        }

        // check autosave
        if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
            return $post_id;
        }

        // check permissions
        if ('page' == $_POST['post_type']) {
            if (!current_user_can('edit_page', $post_id)) {
                return $post_id;
            }
        } elseif (!current_user_can('edit_post', $post_id)) {
            return $post_id;
        }

        foreach ($this->_meta_box['fields'] as $field) {
            $old = get_post_meta($post_id, $field['id'], true);
            $new = $_POST[$field['id']];

            if ($new && $new != $old) {
                update_post_meta($post_id, $field['id'], $new);
            } elseif ('' == $new && $old) {
                delete_post_meta($post_id, $field['id'], $old);
            }
        }
    }
}

This class has 3 methods and one constructor:

  • The constructor will take the data and save it into protected variable of class for further using. It also hooks to 2 actions: admin_menu for adding meta box and save_post for save meta box’s data.
  • The add, show, save functions are just the converted version of old functions mytheme_add_box, mytheme_show_box and mytheme_save_data. In these functions, instead of access the global variable $meta_box (as in the old code), we use the protected variable of the class $this->_meta_box.

That’s all. Now you can create meta boxes as many as you want, and each meta box can be displayed in multiple editing pages (multiple post types). For easy using the code, I also create a file that includes all we need. What you have to do is just include it into your functions.php file and re-define the meta boxes in the very top of the file.

Download version 2.4.1

Download latest version from the project page

If you like it, please buy me a beer!

x click but21 How To Create A Better Meta Box In WordPress (Part 2)

Changelog:

  • - 2.4.1: fix bug of not receiving value for select box
  • - 2.4: (image upload features are credit to Kai
  • + change image upload using meta fields to using default WP gallery
  • + add delete button for images, using ajax
  • + allow to upload multiple images
  • + add validation for meta fields
  • - 2.3: add wysiwyg editor type, improve check for upload fields, change context and priority attributes to optional
  • - 2.2: add enctype to post form (fix upload bug), thanks to http://www.hashbangcode.com/blog/add-enctype-wordpress-post-and-page-forms-471.html
  • - 2.1: add file upload, image upload support
  • - 2.0: oop code, support multiple post types, multiple meta boxes
  • - 1.0: procedural code
Update 1: I’ve updated the script to support file and image upload. Just download with the link above and enjoy!

Update 2 (27/11/2010): After a long time, I received many suggestions in the comments. And I tried to make the script better and what we have now is the version 2.4 with many features (listed in the changelog). Many thanks to our contributors!

21 Comments

  1. Just want to say thank you for the time you put in posting and coding this. I’ll give it a shot on my other site. Cheers!

    Reply
  2. Thank you for a great plugin! I do have a problem though, using the demo file, I’ve deleted images inside the edit post area, but notice that they are still attached to the post, even though I clicked the delete button inside the edit post area. How can I get them to delete or unattach from the Media folder so that they are no longer associated with the post?

    Thank you!

    Reply
  3. I’m using the Image Advanced field and notice that when I delete an image inside the post, the image remains attached, even though it removes from the edit area. Is there a way to unattached images that are deleted?

    Reply
  4. Super useful code, thanks! Does anyone have any idea how to strip html from the description for your users reference to see how to use html in their entry? See my example below:


    array(
    'name' => __('TEAM'),
    'desc' => __('We need a lil HTML love.. use as reference: Jon Doe - Director of Digital'),
    'id' => "team",
    'type' => 'textarea',
    'std' => ""
    ),

    That example acquires the HTML in the admin area.. I don’t want to see the formatted but rather the HTML itself. Is this possible? Thanks in advance!

    Reply
    • Well it looks like my comment acquired the formatting as well but you get the picture. How do I strip the formatting?

      Reply
    • What do you mean strip HTML here? You just enter plain text, that’s all. BTW, your description doesn’t have any HTML.

      Reply
      • “BTW, your description doesn’t have any HTML.” – exactly. the commenting system here is doing the exact same thing.. I entered HTML and it translated/formatted the my description. For example, the bold “strong” tag formats to bold.. I would like to remove the formatting so I can provide an example of some HTML for a novice user. How would I display the HTML itself, not format the description with HTML? :/

        Reply
        • Please use pastebin.com to paste your code, and put link in your comment ;)

  5. Hi, how to , I show the data in my post area ,

    // first meta box
    $meta_boxes[] = array(
    ‘id’ => ‘imageupload’,
    ‘title’ => ‘Upload Image’,
    ‘pages’ => array(‘post’), // multiple post types
    ‘context’ => ‘normal’,
    ‘priority’ => ‘high’,
    ‘fields’ => array(
    array(
    ‘name’ => ‘Image One’,
    ‘desc’ => ‘First, Upload your image manualy And Paste your image link on the . Click Update for save settings’,
    ‘id’ => $prefix . ‘upload’,
    ‘type’ => ‘text’,

    I want to show data in my post area for this code ‘name’ => ‘Image One’,

    Please some one help me . I am waiting for reply as soon as possible.

    ‘std’ => ”
    )
    )
    );

    Reply
  6. Ah. I see the problem. the filename perimeter in wp_insert_attachment() has to be an absolute path. Starting as the if statement on line 369…

    			if ($field['type'] == 'file' || $field['type'] == 'image') {
    				if (!empty($_FILES[$name])) {
    					$this->fix_file_array($_FILES[$name]);
    					foreach ($_FILES[$name] as $position => $fileitem) {
    						$file = wp_handle_upload($fileitem, array('test_form' => false));
    						//store upload array in a variable.
    						$upload_dir = wp_upload_dir();
    						$filename = $file['url'];
    						//generate absolute path.
    						$path = $upload_dir['basedir'] . $upload_dir['subdir'] . '/' . basename($filename);
    
    						if (!empty($filename)) {
    							$wp_filetype = wp_check_filetype(basename($filename), null);
    							$attachment = array(
    								'post_mime_type' => $wp_filetype['type'],
    								'post_title' => preg_replace('/\.[^.]+$/', '', basename($filename)),
    								'post_status' => 'inherit'
    							);
    							$attach_id = wp_insert_attachment($attachment, $path, $post->ID);
    							// you must first include the image.php file
    							// for the function wp_generate_attachment_metadata() to work
    							require_once(ABSPATH . 'wp-admin/includes/image.php');
    							$attach_data = wp_generate_attachment_metadata($attach_id, $path);
    							wp_update_attachment_metadata($attach_id, $attach_data);	
    						}
    					}
    				}
    			}
    
    

    Then WP will generate all the native thumbnail sizes and any additionally defined by the theme.

    Reply
  7. Having trouble getting the WP to create the additional image sizes – both native and additional.

    It’s my understanding that wp_generate_attachment_metadata() creates the necessary sizes. However, it’s not returning an array of all the image sizes?

    Just:

        [width] => 394
        [height] => 172
        [hwstring_small] => height='55' width='128'
        [file] =>http://yadayadayada...
    
    Reply
  8. Great tutorial indeed,
    but I think there’s an error/typo in second-last echo of the show() function:
    echo ”<td>,
    ”</tr>”;
    should be:
    echo ”</td>,
    ”</tr>”;

    Anyway this is still a wonderfull piece of code :)

    Reply
    • Hi Orlmente, this part of the tutorial is a little old. I’ve released a completed rewrite code as a plugin here. If you interested in that, you can join us on GitHub ;)

      Reply
  9. Great resource. I used this code already so many times. It’s simple, to the point and well written. Kudos to you. Just saw you had a plugin of this!

    Reply
  10. Holy crap! Thank you a million times over for this. Talk about a time saver! I’ve done write panels a few ways and they are always tedious as hell. This just made it a breeze. Plus it opened my eyes to a few things I haven’t tried in PHP when it comes to OOP. Thanks again.

    Reply
  11. Hi how to display the selected metabox values .I used echo get_post_meta($post->ID,’delivary’,true); for a multiple choice metabox value. But it echos out the word ‘array’ .So how to display the selected value.

    Reply
  12. I am getting the following error when using your code:

    Warning: Invalid argument supplied for foreach()

    and it is at this line:

    /// Add meta box for multiple post types
    function add() {
    foreach ($this->_meta_box['pages'] as $page) {
    add_meta_box($this->_meta_box['id'], $this->_meta_box['title'], array(&$this, ‘show’), $page, $this->_meta_box['context'], $this->_meta_box['priority']);
    }
    }

    Any suggestions?

    Reply

Leave a Reply