Blog Pile, Wordpress

Setting up Custom Content Types in WordPress 3.0


cc licensed flickr photo shared by TakenByTina

My previous post just outlined the kinds of things I put into a new site created with a beta version of WordPress 3 (I started with the first beta and honestly, it had more polish than most finished products) – I actually did not tell you much. Now it’s time to get out and start hacking.

In this post, I’ll detail what I did to create three content types on the MIDEA site. You will see code, raw PHP out in the open.

While there is an excellent plugin for creating custom content types (I did try it out and also parsed through the code to see what it did), it only did about 15% of what I wanted. Creating the content types are easy. But the plugin does nothing to help you add the form elements to create, edit the extra meta data I needed to add to the content.

Please put your safety goggles on now.

It’s All About the functions.php, Baby

This was a huge, and seemingly “doh”-like realization of the power of a single file inside most “beyond the basic” WordPress templates. The purpose of functions,php is to have a place to put functions you can call from anywhere in your templates. I’d seen it in there and noted that it typically did a bunch of formatting tasks for a template, or some custom things to handle the way a template needed to fish through Pages.

But I’d done a of of template hacking and left a lot of code in the templates that would have been cleaner and more re-useable in this file. But we can do it for much more, and this is where we will put the code to create my three content types.

I will admit that what I am spelling out here is a mashup of some code I found elsewhere and mostly what I kept banging on til it works. I know nothing about the WordPress process flow, and something I did may not be fullykosher or optimal.

Thats my disclaimer.

We start simple- to create our content types we need to add a few “hooks” or places in the WordPress normal flow opf whatever it does, that it does a few extra things for us.


All we are saying is that when WordPress gets going (init) we want to add a “hook” to run the code we will out in a function named midea_custom_init. Its good practice to prefix your functions in this file with something unique that may collide with other WordPress code. I’ve gone the route of naming all my code functiions (well, most of them) with a “midea” prefix.

It’s in this new function that we launch our new content types. We need to do a few things for each custom content type:

  • register it with wordpress (turn it on)
  • set up any taxonomy (tagging capability) we may want. I set this up, but have yet to implement it.
  • activate rewrite rules so WordPress hands off URLs correctly for the new content type.
  • set up the template redirects so our content types have their own templates.

To our code above, we will add our midea_custom_init function, first to set up the content type for organizations

 __('Organizations'),
		'singular_label' => __('Organization'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "org"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'org' , $args );

    register_taxonomy( 
		'mtype', 
		'org', 
		array ('hierarchical' => false, 'label' => __('Museum Types'), 
		'singular_label' => __('Museum Type'),
'query_var' => 'mtype')
	);
	add_new_rules('org');
}
?>

The steps are to set up $args as a holder for the arguments we pass next to register_post_type that tells WordPress, “hey, set this up.” In args, they key things to change for your own are the labels, define a “slug” or the part of the url that will preface whatever content is created (e.g. for MIDEA, all organizations will have URLs like http://midea.nmc.org/org/xxxxxxxx). The list of things for supports are the bits of a basic blog post that we will use, in this case we are using the title, editor (to create the body text), and thumbnail (icon to represent the content). See the documentation to see the other things you can use.

When I call register post type, the first parameter (‘org’) is how the content will be marked in the database (to differentiate from blog posts as type “post’ and Pages as ‘page’). I included the code to set up a taxonomy for my content, tags that could be applied in the editor (I have yet to use this, but added it just as wel. I then have a call to a new function we ‘ve not listed yet, add_new_rules which nudges wordpress to resolve URLs correctly.

That;s one content type, I add in similar code to fill out the function for my other two content types.

 __('Organizations'),
		'singular_label' => __('Organization'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "org"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')

    );

    register_post_type( 'org' , $args );
    
	register_taxonomy( 
		'mtype', 
		'org', 
		array ('hierarchical' => false, 'label' => __('Museum Types'), 
		'singular_label' => __('Museum Type'),
'query_var' => 'mtype')
	);
	
	add_new_rules('org');

	// create PROJECTS
    $args = array(
		'label' => __('Projects'),
		'singular_label' => __('Project'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "proj"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'proj' , $args );
    
	register_taxonomy( 
		'ptype', 
		'proj', 
		array ('hierarchical' => false, 'label' => __('Project Types'), 
		'singular_label' => __('Project Type'),
'query_var' => 'ptype')
	);
	
	add_new_rules('proj');

	// create EVENTS
    $args = array(
		'label' => __('Events'),
		'singular_label' => __('Event'),
		'public' => true,
		'show_ui' => true, // UI in admin panel
		'_builtin' => false, // It's a custom post type, not built in!
		'_edit_link' => 'post.php?post=%d',
		'capability_type' => 'post',
		'hierarchical' => false,
		'rewrite' => array("slug" => "event"), // Permalinks format
		'supports' => array('title', 'editor', 'thumbnail')
    );

    register_post_type( 'event' , $args );
	
	// set up URL redirects
	add_new_rules('event');

        // set up redirects
	add_action("template_redirect", 'midea_template_redirect');
}

(note I did not set up taxonomies for events, no need seen). At the very end is one more step, another WordPress hook to tell WordPress how to handle templates for my content types.

What we have to add next are some of the functions used in the above code.

First, we must have WordPress track the URLs it sees coming in by adding “redirects”, things that take a public URL like http://midea.nmc.org/proj/some-groovy-proj and turn it into something WordPress knows how to handle. We’ve called it once for each content type, passing the content type as a parameter.

I honestly cannot explain fully what this does, I adapted it from one of the articles I read before starting out.

function add_new_rules($ptype){

	global $wp_rewrite;

	$rewrite_rules = $wp_rewrite->generate_rewrite_rules("$ptype/");
	$rewrite_rules["$ptype/?$"] = 'index.php?paged=1';

	foreach($rewrite_rules as $regex => $redirect)
	{
		if(strpos($redirect, 'attachment=') === false)
			{
				$redirect .= "&post_type=$ptype";
			}
		if(0 < preg_match_all('@\$([0-9])@', $redirect, $matches))
			{
				for($i = 0; $i < count($matches[0]); $i++)
				{
					$redirect = str_replace($matches[0][$i], '$matches['.$matches[1][$i].']', $redirect);
				}
			}
		$wp_rewrite->add_rule($regex, $redirect, 'top');
	}
}

And the last function to add sets up the templates for our content types. What this will do is for my content type named proj it creates an expectation that there will be a template to display all the projects named proj.php and another template to display a single project called single-proj.php

function midea_template_redirect()
{
	global $wp;
	
	$midea_custom_types = array('proj', 'org', 'event');
	
	if (in_array($wp->query_vars["post_type"], $midea_custom_types)) {
	
		if ( is_robots() ) :
			do_action('do_robots');
			return;
		elseif ( is_feed() ) :
			do_feed();
			return;
		elseif ( is_trackback() ) :
			include( ABSPATH . 'wp-trackback.php' );
			return;
		elseif($wp->query_vars["name"]):
			include(TEMPLATEPATH . "/../midea/single-".$wp->query_vars["post_type"].".php");
			die();
		else:
			include(TEMPLATEPATH . "/../midea/".$wp->query_vars["post_type"].".php");
			die();
		endif;
	}
}

Notes- I lifted this from another site, but found I needed to add a global $wp; statement to get it to work. Also you may note the curious paths in the latter two statements; this is because (to be written up in a later post) I have made a midea theme that is a child of the primary theme I am using. TEMPLATEPATH points to the location of my Modularity template, so the “/../midea” goes up a directory and back down.

And I can spot some sloppiness in that last function- I have the three content types hard coded in as an array; I should have made midea_template_redirect() a function I passed an argument to like I did for add_new_rules($arg). It’s always something.

If all goes well and I;ve not left off a critical piece, we should be able to see our new content types appear in your WordPress dashboard with the same edit/create buttons as we have for blog posts and pages, plus taxonomy tabs for the 2 content types we enabled. We could even create content, but at this point, they have nothing different from posts/pages.

The next installment will go even deeper and get us to the point of having snazzy menus, fields, etc attached to our custom content types for entering/editing the custom meta data. I’ve attached a copy of the functions.php you would have done at this point.

So who’s with me up to here?

It gets more fun as we go deeper, but I’ll be taking a break next few days, and will return with a freshly oiled chainsaw.

You can find all the posts in this series at http://cogdogblog.com/2010/07/23/roundup-wp3/

If this kind of stuff has value, please support me by tossing a one time PayPal kibble or monthly on Patreon
Become a patron at Patreon!
Profile Picture for CogDog The Blog
An early 90s builder of web stuff and blogging Alan Levine barks at CogDogBlog.com on web storytelling (#ds106 #4life), photography, bending WordPress, and serendipity in the infinite internet river. He thinks it's weird to write about himself in the third person. And he is 100% into the Fediverse (or tells himself so) Tooting as @cogdog@cosocial.ca

Comments

    1. This has been wonderful, and I have to thank you (and Jim Groom, and Martha Burtis). I have one question: I’d like to customize the nature of the comments for this custom post type. We’ve developed a really simply Classfied ad custom post type. I’d like to allow comments (in this case, inquiries about the product), but have the option to have them sent privately to the author, not displayed on the Web site. I’m thinking that I can just suppress the display with css, but that seems clunky. Any ideas?

  1. I’m up with you, and I am loving it. This is a tour de force series, that may very change the way we use WOrdPress on UMW Blogs in the coming year. Working with faculty on sites where we can customize content to this degree, and truly frame the publishing space for their ideas and projects could very well make this platform even more popular than it already is—which is crazy to think about.

    What’s more, your play-by-play is crystal clear, and I am hacking my own site/template along side this series so that I have a sense of the code. I also like that I am finally being forced to figure out what child themes are, saw that in the /../../midea code, and glad you are giving me a crash course in WP 3.0—dog, you rule!

    1. Actually drupal has had this capability for a while. But my thoughts on drupal will come at the end of the series. As Boris Mann kept saying at Northern Voice, “It’s fire!”

      Doing a custom feed format is not a big deal; just another template

  2. Drupal only sort of has the capability, with CCK. But the implementation is very awkward, and when I tried it a while back it couldn’t support very much traffic. That may be an issue with the WordPress functionality as well, I don’t know.

    Producing a custom RSS feed is easy; as you say, just another template. Consuming it and storing the data properly is a different matter. Though if that too is easy with WordPress I would be very happy.

  3. Thanks for the write-up so far!
    I’m looking forward to the rest of the series.
    I can’t think of a site we’ve built that this wouldn’t have saved us hours of headache.

    I find it curious that there are already several plugins for WP-3 that add a UI for this step but don’t really address creating proper custom fields to accompany the new custom post types.

    Thanks again.

  4. I’m very new to wordpress, and I’m a little stuck following the instructions. I’m trying to implement the above for some custom content types of my own. when I update functions.php I get an error:

    Call to undefined function add_action()

    The error is referring to this line in my code:

    add_action(‘init’, ‘cdem_custom_init’);

    Am I doing something wrong?

    Cheers

  5. Alan,

    Excellent stuff here. I think you left out two functions from the functions.php.

    function midea_proj_options() {
      // add meta data fields for a MIDEA organization content type
      global $post;

      // Use nonce for verification
      echo '<input type="hidden" name="midea_noncename" id="midea_noncename" value="' .
        wp_create_nonce( 'MyNonceCode' ) . '" />';

      $my_fields = midea_proj_fields(); // fields for this content type

      foreach ($my_fields as $key => $value) {
        // load values into our array
        $my_fields[$key] = get_post_meta($post->ID, 'midea-proj-' . $key, true);
      }  

      echo '<p><strong>Organization</strong></p><p><label>name:</label><br /> <input name="midea-proj-org" size="60" value="' . $my_fields['org'] . '" /></p>' . "\n";
      echo '<p><label>web site</label>:<br /> <input name="midea-proj-url" size="60" value="' . $my_fields['url'] . '" /></p>' . "\n";
      echo '<p><label>rating</label>:<br /> <input name="midea-proj-rating" size="60"  value="' . $my_fields['rating'] . '" /></p>' . "\n";
    }

    function midea_event_options() {
      // add meta data fields for a MIDEA organization content type
      global $post;

      // Use nonce for verification
      echo '<input type="hidden" name="midea_noncename" id="midea_noncename" value="' .
        wp_create_nonce( 'MyNonceCode' ) . '" />';

      $my_fields = midea_event_fields(); // fields for this content type

      foreach ($my_fields as $key => $value) {
        // load values into our array
        $my_fields[$key] = get_post_meta($post->ID, 'midea-event-' . $key, true);
      }  

      echo '<p><p><label>start:</label><br /> <input name="midea-event-start" size="60" value="' . $my_fields['start'] . '" /></p>' . "\n";
      echo '<p><p><label>end:</label><br /> <input name="midea-event-end" size="60" value="' . $my_fields['end'] . '" /></p>' . "\n";
      echo '<p><label>web site</label>:<br /> <input name="midea-event-url" size="60" value="' . $my_fields['url'] . '" /></p>' . "\n";
      echo '<p><strong>Location</strong></p><p><label>address</label>:<br /><textarea name="midea-event-location" rows="2" cols="55">' . $my_fields['address'] . '</textarea></p>' . "\n";

    I was getting an error about expecting a valid callback function or something until I added these to the functions.php file. Now to figure out how to allow for another rich text editing box.

Leave a Reply to Cathy Finn-Derecki Cancel reply

Your email address will not be published. Required fields are marked *