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 theSLUG
as part of the path to people. If I didn’t include this, thePOST_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, andrevisions
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, sohierarchical
is set tofalse
.
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:
- WordPress will execute the
hfhd-plugin.php
file. - As part of that, an instance of
HFHDPlugin
gets created. - As part of that, an instance of
PersonCustomPostType
gets created. - The
'init'
action hook gets registered. - Later, WordPress fires the
'init'
action. - The hook we registered will execute the
registerPostType
method on thePersonCustomPostType
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”:
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”
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:
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:
- 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. - 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:
- We want to call
flush_rewrite_rules()
when our plugin is activated. - Our custom post type must have been registered before we call
flush_rewrite_rules()
, otherwise it won’t do any good. - To be good citizens, we also should call
flush_rewrite_rules()
when our plugin is deactivated, since that removes ourperson
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/.