Table of Contents for the series

So in our last episode, we got the basic framework for a plugin in place. It was a singularly boring plugin – not even as functional as WordPress’ “Hello Dolly” plugin, but it showed up on the WordPress console. Let’s add some functionality now.

We’re going to create a custom post type for people. I’m going to use this (with a bunch of bells and whistles) to store information about people and their relationships. Because I’m going to add a variety of other things, we’ll separate the code related to the post type into its own class – PersonCustomPostType.

Let’s start with the basic code to register a custom post type, taken pretty much verbatim from the WordPress Codex:

namespace HFHD;

class PersonCustomPostType {
	const POST_TYPE	= "hfhd-person-cpt";
	const SLUG 		= "person";
	
	public function registerPostType() {
		
		// UI labels for Custom Post Type
		$labels = array(
			'name'                => _x( 'People', 				'Person CPT plural name', 		'hfhd' ),
			'singular_name'       => _x( 'Person', 				'Person CPT singular_name', 	'hfhd' ),
			'menu_name'           => _x( 'People', 				'Person CPT menu_name', 		'hfhd' ),
			'parent_item_colon'   => _x( 'Parent Person:', 		'Person CPT parent_item_colon',	'hfhd' ),
			'all_items'           => _x( 'All People', 			'Person CPT all_items', 		'hfhd' ),
			'view_item'           => _x( 'View Person', 		'Person CPT view_item', 		'hfhd' ),
			'add_new_item'        => _x( 'Add New Person', 		'Person CPT add_new_item', 		'hfhd' ),
			'add_new'             => _x( 'Add New', 			'Person CPT add_new', 			'hfhd' ),
			'edit_item'           => _x( 'Edit Person', 		'Person CPT edit_item', 		'hfhd' ),
			'update_item'         => _x( 'Update Person', 		'Person CPT update_item', 		'hfhd' ),
			'search_items'        => _x( 'Search Person', 		'Person CPT search_items', 		'hfhd' ),
			'not_found'           => _x( 'Not Found', 			'Person CPT not_found', 		'hfhd' ),
			'not_found_in_trash'  => _x( 'Not found in Trash',	'Person CPT not_found_in_trash','hfhd' ),
		);
	
		// Options for Custom Post Type
		$args = array(
				'labels'             => $labels,
				'public'             => true,
				'publicly_queryable' => true,
				'show_ui'            => true,
				'show_in_menu'       => true,
				'query_var'          => true,
				'rewrite'            => array( 'slug' => PersonCustomPostType::SLUG ),
				'capability_type'    => 'post',
				'has_archive'        => false,
				'hierarchical'       => false,
				'menu_position'      => 5,
				'supports'           => array(	'title',
												'editor',
												'thumbnail',
												'revisions',
												),
		);
		
		register_post_type( PersonCustomPostType::POST_TYPE, $args );
	}

On lines 4 and 5, we set up constants that we’ll use. POST_TYPE is the string by which WordPress will identify these posts. I’ve made it nice and (hopefully) unique by including a hfhd- prefix. Chances for a collision with that are low. SLUG is the URL string that will be used to preface posts of this type. Being somewhat more generic, there’s always the possibility that this could collide with some other plugin or something like that. In theory, we could make this configurable, but we’ll save that for another time.

The registerPostType() method takes care of the registration process.

The $labels array defines the various strings that will be used to describe the custom post type. As you can see, I’ve used the __x() function to enable translation. A fine point here – I see quite a few people who would write their plugins to use a constant as the third argument (the translation domain) instead of an explicit string. The problem with that is that the translation tools are a bit stupid – they operate on the source code, not on the compiled PHP. As such, if you want the tools to work, you really need to use the domain string. Over. And over. And over. Oh well.

The $args array then has the parameters for the call to register_post_type. You can obviously look up the meaning of each of the items in the Codex. I just want to call attention to a few of them:

  • menu_position
    This controls where on the administration panel the new post type will show up. I want it relatively close to the top – this will put it right underneath “Posts”. The default is somewhat lower down, which I didn’t like.
  • rewrite
    This takes care of telling WordPress that I want to use the SLUG as part of the path to people. If I didn’t include this, the POST_TYPE would be used, which is a bit less pretty than I’d like.
  • supports
    This is an array indicating the various elements that should be on the “People” screen when creating or editing a person. title is pretty obvious, editor provides the standard content editor, thumbnail enables the “Featured Image” control, and revisions provides the enhanced “when should it be published” control.
  • hierarchical
    When you create a custom post type, you can make it behave like a page, which can have a parent and be in part of a tree structure, or a post, which doesn’t. I’ve chosen the latter, so hierarchical is set to false.

Note that a number of these items are set to their default values – I don’t always remember the defaults, so…

We have our function all ready to go, so we just need to get it called. The normal time that you register a post type is in response to the 'init' action hook. So, we’ll do that in the constructor for this class:

	function __construct() {
		add_action( 'init', array( $this, 'registerPostType' ) );
	}

Then we’ll modify the HFHDPlugin class so that it creates an instance of PersonCustomPostType when it’s created:

class HFHDPlugin {
	private $personCustomPostType;

	function __construct() {
		$this->personCustomPostType = new PersonCustomPostType();
	}
}

So the sequence will be:

  1. WordPress will execute the hfhd-plugin.php file.
  2. As part of that, an instance of HFHDPlugin gets created.
  3. As part of that, an instance of PersonCustomPostType gets created.
  4. The 'init' action hook gets registered.
  5. Later, WordPress fires the 'init' action.
  6. The hook we registered will execute the registerPostType method on the PersonCustomPostType instance.

And voilà – our custom post type is created.

With all of this new code added and our plugin activated (if it wasn’t before), we can go over to the WordPress admin screen, and sure enough, our custom post type shows up nicely, right under “Posts”:
Custom Post Type in WordPress admin screen

Now, my plan is to use the person’s full name in the spot where a post’s title would normally go. After all, that’s the closest thing to a “title” for a person. (Unless you’re royalty, of course.) If we go to add a person, however, the screen says “Enter title here,” not “Enter full name”

Screen Shot 2015-01-26 at 7.40.23 PM

That can be corrected, however. WordPress has a filter hook for this called (very originally) 'enter_title_here'. To override that string, we can add the following method to our class:

	public function changeEnterTitleHere( $title ) {
		
		$screen = get_current_screen();
		
		if  ( PersonCustomPostType::POST_TYPE === $screen->post_type ) {
			$title = _x( 'Enter full name', 'Person CPT enter_title_here', 'hfhd');
		}
		
		return $title;
	}

and then add the filter hook in the constructor

	function __construct() {
		add_action( 'init', 			array( $this, 'registerPostType' ) );
		add_filter( 'enter_title_here', array( $this, 'changeEnterTitleHere' ));
	}

Thus, our changeEnterTitleHere method will get called every time a new post or page screen gets displayed. Thus, we need to test to see if the post type is one of ours and only then change the title. With this in place, however, things look nicer:
Screen Shot 2015-01-26 at 8.40.45 PM

The last problem we may run into is that, depending on your WordPress setup, it’s possible that you can create a Person, press the “View Person” button on the form, and end up staring at a 404 error. WordPress uses a series of “rewrite rules” in order to help it serve URL’s like http://mysite.com/people/my-name-here. When we added the custom post type and gave WordPress the new people slug, it’s possible that WordPress won’t have the rewrite rules that it needs in place to support this.

There are two solutions to this:

  1. After activating the plugin, you can go over to the Permalink page within WordPress and re-save the settings. This will cause WordPress to update its rules, and things should then work. This is not a pretty thing for your end user, however, to have to remember.
  2. Better, WordPress has a function called flush_rewrite_rules() that takes care of this for you. Thus, the superior solution is to call this on behalf of your user.

The WordPress Codex has this to say about flush_rewrite_rules():

  • Flushing the rewrite rules is an expensive operation, there are tutorials and examples that suggest executing it on the ‘init’ hook. This is bad practice.
  • Flush rules only on activation or deactivation, or when you know that the rewrite rules need to be changed. Don’t do it on any hook that will triggered on a routine basis.

Note that the bold text is theirs, not mine. Any time someone writes bad practice in bold, that’s usually a clue for a Very Bad Thing. Don’t cross the streams. Realistically, it turns out that if you violate this, you’ll kill the performance of your site, and may even corrupt it.

So:

  1. We want to call flush_rewrite_rules() when our plugin is activated.
  2. Our custom post type must have been registered before we call flush_rewrite_rules(), otherwise it won’t do any good.
  3. To be good citizens, we also should call flush_rewrite_rules() when our plugin is deactivated, since that removes our person slug.

So we’ll skin this cat as follows:

First, we’ll implement a couple of methods on our PersonCustomPostType class:

	public function onPluginActivated() {
		$this->registerPostType();
		flush_rewrite_rules();
	}
	
	public function onPluginDeactivated() {
		flush_rewrite_rules();
	}

I mentioned that we had to be sure that our custom post type was registered before calling flush_rewrite_rules(). We need the extra call to registerPostType because the activation hook is called before the 'init' hook.

To get these functions called, we modify our HFHDPlugin class by adding:

	public function onPluginActivated() {
		$this->personCustomPostType->onPluginActivated();
	}
	
	public function onPluginDeactivated() {
		$this->personCustomPostType->onPluginDeactivated();
	}

And to get those called, we modify our hfhd-plugin.php file so that the creation of the HFHDPlugin changes from:

$GLOBALS ['HFHD\\HFHD_PLUGIN'] = new HFHDPlugin ();

to:

$hfhd_plugin = new HFHDPlugin ();
$GLOBALS ['HFHD\\HFHD_PLUGIN'] = $hfhd_plugin;
register_activation_hook( __FILE__, array( $hfhd_plugin, 'onPluginActivated' ));
register_deactivation_hook( __FILE__, array( $hfhd_plugin, 'onPluginDeactivated' ));
unset ( $hfhd_plugin );

Thus, we create the plugin object, save it, and then register the activation and deactivation hooks against it.

Normally, I’d have tried to hide the activation and deactivation hooks inside the HFHDPlugin class. The catch is that the register_activation_hook and register_deactivation_hook functions need the __FILE__ argument, which has to be the main plugin file. So I would have had to either move the HFHDPlugin class declaration up to this file, or else take the approach I did.

A last little “cleanliness” point. Note the call to unset at the end. Our $hfhd_plugin variable got created in the global scope. I don’t want to leave my trash around to possibly affect code elsewhere, so after I set it and used it, I unset it again. Turns out WordPress does a lot of this in their own files, so I’m trying to follow their lead.

Code for this post can be found at https://github.com/SilverBayTech/WordpressCustomization/tree/master/hfhd-plugin/v2.

 

WordPress Customization Series – Custom Post Type originally appeared on http://www.silverbaytech.com/blog/.