For a lot of WordPress projects these days we use custom post types. The WordPress development team created some handy methods to integrate them into your projects. But when you use custom post types, taxonomies and meta boxes frequently, it's quite probable that you're going to repeat yourself. That's why we are going to use the power of these WordPress functions to build a more powerful class, which we can use to quickly register post types, taxonomies and meta boxes.
Call Our Class
This is how we call our class when it's done.
include('custom-post-type.php'); $book = new Custom_Post_Type( 'Book' ); $book->add_taxonomy( 'category' ); $book->add_taxonomy( 'author' ); $book->add_meta_box( 'Book Info', array( 'Year' => 'text', 'Genre' => 'text' ) ); $book->add_meta_box( 'Author Info', array( 'Name' => 'text', 'Nationality' => 'text', 'Birthday' => 'text' ) );
Step 1 The Class, Properties and Methods
We start off with creating the class, main properties, constructor and methods. In this tutorial we will fill them with our programming logic.
class Custom_Post_Type { public $post_type_name; public $post_type_args; public $post_type_labels; /* Class constructor */ public function __construct() { } /* Method which registers the post type */ public function register_post_type() { } /* Method to attach the taxonomy to the post type */ public function add_taxonomy() { } /* Attaches meta boxes to the post type */ public function add_meta_box() { } /* Listens for when the post type being saved */ public function save() { } }
Step 2 The Constructor
Within the constructor we create some important variables, which are used within the entire class. We also call add_action
to register the post type and we listen for when the post type is being saved, so we can save our post's meta data. If the post type exists, the add_action
is not called, but the $post_type_name
is set, so we can add taxonomies and meta boxes to it.
public function __construct( $name, $args = array(), $labels = array() ) { // Set some important variables $this->post_type_name = strtolower( str_replace( ' ', '_', $name ) ); $this->post_type_args = $args; $this->post_type_labels = $labels; // Add action to register the post type, if the post type does not already exist if( ! post_type_exists( $this->post_type_name ) ) { add_action( 'init', array( &$this, 'register_post_type' ) ); } // Listen for the save post hook $this->save(); }
Step 3 Register the Post Type
Within the register_post_type
method, which gets called by the add_action
in the constructor, we first determine the name (capitalized) and the plural. With this name and plural we build our labels for the post type and overwrite (and merge) them with the given labels from the $this->post_type_labels
variable. Then we create our arguments with the same principle.
public function register_post_type() { //Capitilize the words and make it plural $name = ucwords( str_replace( '_', ' ', $this->post_type_name ) ); $plural = $name . 's'; // We set the default labels based on the post type name and plural. We overwrite them with the given labels. $labels = array_merge( // Default array( 'name' => _x( $plural, 'post type general name' ), 'singular_name' => _x( $name, 'post type singular name' ), 'add_new' => _x( 'Add New', strtolower( $name ) ), 'add_new_item' => __( 'Add New ' . $name ), 'edit_item' => __( 'Edit ' . $name ), 'new_item' => __( 'New ' . $name ), 'all_items' => __( 'All ' . $plural ), 'view_item' => __( 'View ' . $name ), 'search_items' => __( 'Search ' . $plural ), 'not_found' => __( 'No ' . strtolower( $plural ) . ' found'), 'not_found_in_trash' => __( 'No ' . strtolower( $plural ) . ' found in Trash'), 'parent_item_colon' => '', 'menu_name' => $plural ), // Given labels $this->post_type_labels ); // Same principle as the labels. We set some defaults and overwrite them with the given arguments. $args = array_merge( // Default array( 'label' => $plural, 'labels' => $labels, 'public' => true, 'show_ui' => true, 'supports' => array( 'title', 'editor' ), 'show_in_nav_menus' => true, '_builtin' => false, ), // Given args $this->post_type_args ); // Register the post type register_post_type( $this->post_type_name, $args ); }
Step 3 Add Some Taxonomies
First we check if the $name
parameter is empty. When it is, we do nothing. When it's not, we create three variables in which we store the information for the taxonomy: $taxonomy_name
, $taxonomy_labels
and $taxonomy_args
.
public function add_taxonomy( $name, $args = array(), $labels = array() ) { if( ! empty( $name ) ) { // We need to know the post type name, so the new taxonomy can be attached to it. $post_type_name = $this->post_type_name; // Taxonomy properties $taxonomy_name = strtolower( str_replace( ' ', '_', $name ) ); $taxonomy_labels = $labels; $taxonomy_args = $args; /* More code coming */ } }
After we've done the first checks and then set some variables, we're going to register the post type. But first we check if the taxonomy already exists.
if( ! taxonomy_exists( $taxonomy_name ) ) { /* Create taxonomy and attach it to the object type (post type) */ } else { /* The taxonomy already exists. We are going to attach the existing taxonomy to the object type (post type) */ }
If the taxonomy doesn't exist, we register it. We use an add_action
, but not in the normal way. Normally, the second parameter of the add_action
is the name of a function, but since we use different parameters each time, we are going to pass a nameless function (Note: this feature requires PHP 5.3+) and use the use()
function. With the use()
function we can pass variables to the nameless function. This time we need to pass $taxonomy_name
, $post_type_name
and $taxonomy_args
to register the taxonomy.
//Capitilize the words and make it plural $name = ucwords( str_replace( '_', ' ', $name ) ); $plural = $name . 's'; // Default labels, overwrite them with the given labels. $labels = array_merge( // Default array( 'name' => _x( $plural, 'taxonomy general name' ), 'singular_name' => _x( $name, 'taxonomy singular name' ), 'search_items' => __( 'Search ' . $plural ), 'all_items' => __( 'All ' . $plural ), 'parent_item' => __( 'Parent ' . $name ), 'parent_item_colon' => __( 'Parent ' . $name . ':' ), 'edit_item' => __( 'Edit ' . $name ), 'update_item' => __( 'Update ' . $name ), 'add_new_item' => __( 'Add New ' . $name ), 'new_item_name' => __( 'New ' . $name . ' Name' ), 'menu_name' => __( $name ), ), // Given labels $taxonomy_labels ); // Default arguments, overwritten with the given arguments $args = array_merge( // Default array( 'label' => $plural, 'labels' => $labels, 'public' => true, 'show_ui' => true, 'show_in_nav_menus' => true, '_builtin' => false, ), // Given $taxonomy_args ); // Add the taxonomy to the post type add_action( 'init', function() use( $taxonomy_name, $post_type_name, $args ) { register_taxonomy( $taxonomy_name, $post_type_name, $args ); } );
When the taxonomy doesn't exist, we only attach it to our post type. Just like before, we use a nameless function and the use()
function. This time we only need to pass $taxonomy_name
and $post_type_name
.
add_action( 'init', function() use( $taxonomy_name, $post_type_name ) { register_taxonomy_for_object_type( $taxonomy_name, $post_type_name ); } );
Step 4 Meta Boxes
For registering meta boxes, we need the post type name, so first we determine that. After that we need some variables for the meta box itself and we make the custom meta fields global, so we can access them in the save hook. We won't cover too much detail here, because Tammy Hart made a very useful tutorial about reusable meta boxes already.
public function add_meta_box( $title, $fields = array(), $context = 'normal', $priority = 'default' ) { if( ! empty( $title ) ) { // We need to know the Post Type name again $post_type_name = $this->post_type_name; // Meta variables $box_id = strtolower( str_replace( ' ', '_', $title ) ); $box_title = ucwords( str_replace( '_', ' ', $title ) ); $box_context = $context; $box_priority = $priority; // Make the fields global global $custom_fields; $custom_fields[$title] = $fields; /* More code coming */ } }
When we set the variables and globals, we register the meta box with an add_action
. Like before, we use a nameless function. This time we need $box_id
, $box_title
, $post_type_name
, $box_context
, $box_priority
and $fields
.
add_action( 'admin_init', function() use( $box_id, $box_title, $post_type_name, $box_context, $box_priority, $fields ) { add_meta_box( $box_id, $box_title, function( $post, $data ) { global $post; // Nonce field for some validation wp_nonce_field( plugin_basename( __FILE__ ), 'custom_post_type' ); // Get all inputs from $data $custom_fields = $data['args'][0]; // Get the saved values $meta = get_post_custom( $post->ID ); // Check the array and loop through it if( ! empty( $custom_fields ) ) { /* Loop through $custom_fields */ foreach( $custom_fields as $label => $type ) { $field_id_name = strtolower( str_replace( ' ', '_', $data['id'] ) ) . '_' . strtolower( str_replace( ' ', '_', $label ) ); echo '<label for="' . $field_id_name . '">' . $label . '</label><input type="text" name="custom_meta[' . $field_id_name . ']" id="' . $field_id_name . '" value="' . $meta[$field_id_name][0] . '" />'; } } }, $post_type_name, $box_context, $box_priority, array( $fields ) ); } );
Step 5 Save the Post's Meta Data
Save all the post's meta data. We loop through them, using the global $custom_fields
. This is also a quick coverage, see Tammy Hart's tutorial about reusable meta boxes.
public function save() { // Need the post type name again $post_type_name = $this->post_type_name; add_action( 'save_post', function() use( $post_type_name ) { // Deny the WordPress autosave function if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) return; if ( ! wp_verify_nonce( $_POST['custom_post_type'], plugin_basename(__FILE__) ) ) return; global $post; if( isset( $_POST ) && isset( $post->ID ) && get_post_type( $post->ID ) == $post_type_name ) { global $custom_fields; // Loop through each meta box foreach( $custom_fields as $title => $fields ) { // Loop through all fields foreach( $fields as $label => $type ) { $field_id_name = strtolower( str_replace( ' ', '_', $title ) ) . '_' . strtolower( str_replace( ' ', '_', $label ) ); update_post_meta( $post->ID, $field_id_name, $_POST['custom_meta'][$field_id_name] ); } } } } ); }
Step 6 Optimize
As you can see we use strtolower( str_replace( ' ', '_', $string ) )
and ucwords( str_replace( '_', ' ', $string ) )
a number of times. The reason of creating this class is that we don't repeat ourselves, so we don't want to do that in this part either. That's why we create some helper methods. In this way we can do this: $name = self::beautify( $string );
instead of $name = strtolower( str_replace( ' ', '_', $title ) );
public static function beautify( $string ) { return ucwords( str_replace( '_', ' ', $string ) ); } public static function uglify( $string ) { return strtolower( str_replace( ' ', '_', $string ) ); }
Another point is the plural forms we create. We just create them by adding an 's' to the word. But what happens when the word ends with a 'y'? For this reason we create a helper method to determine the plural form of a word. Now we can easily do this: $plural = self::pluralize( $string )
and the plural form of our word will be determined.
public static function pluralize( $string ) { $last = $string[strlen( $string ) - 1]; if( $last == 'y' ) { $cut = substr( $string, 0, -1 ); //convert y to ies $plural = $cut . 'ies'; } else { // just attach an s $plural = $string . 's'; } return $plural; }
Wrap up
And now we're done. You can now use this class to easily register post types, taxonomies and meta boxes. If you have any suggestions or questions, just leave a comment, so we can talk about it. Hope to see you next time!
Also, I would like to give some credit to Jeffrey Way. I used his class as inspiration for my class and for this tutorial. Also, I would like to give some credit to Tammy Hart, for building the reusable meta boxes tutorial. Take a look at their work.
Comments