OOPost Types: Methods Part 2 – Object Oriented WordPress 3.0 App
Second Day Of Classes And OOPost Types
In the first tutorial - Classes part 1 of OOPost Types, I showed you how to start building the class for a custom post type to submit sites, essentially making a type of social bookmarking sort of post type. If you didn't read through that post and just skipped to this one I would recommend that you go back and review it first. Otherwise here is the code that I left off with in posttypes.php.
<?php
// Create a post type class for site posts
// To use as a bookmarking post type for sites you want to save/share.
class TypeSites {
public $meta_fields = array( 'title', 'description', 'siteurl', 'category', 'post_tags' );
public function TypeSites() {
$siteArgs = array(
'labels' => array(
'name' => __( 'Sites', 'post type general name' ),
'singular_name' => __( 'Site', 'post type singular name' ),
'add_new' => __( 'Add New', 'site' ),
'add_new_item' => __( 'Add New Site' ),
'edit_item' => __( 'Edit Site' ),
'new_item' => __( 'New Site' ),
'view_item' => __( 'View Site' ),
'search_items' => __( 'Search Sites' ),
'not_found' => __( 'No sites found in search' ),
'not_found_in_trash' => __( 'No sites found in Trash' ),
),
'public' => true, 'show_ui' => true,
'_builtin' => false,
'capability_type' => 'post',
'hierarchical' => false,
'rewrite' => array('slug' => 'site'), // Permalinks. Fixes a 404 bug
'query_var' => 'site',
'taxonomies' => array('category', 'post_tag'), // Add tags and categories taxonomies
'supports' => array('title','editor','author','comments')
);
register_post_type( 'site', $siteArgs );
}
} // end of TypeSites{} class
?>
Adding Methods To The Class Madness
Starting where I left off, let's continue building our class by adding some methods, or functions as many people call them. First let's set up some custom columns for the Sites manage page. You've probably seen this code before but here it is again.
UPDATED!
// Create the columns and heading title text
public function site_edit_columns($columns) {
$columns = array(
'cb' => '<input type="checkbox" />',
'title' => 'Site Title',
'url' => 'URL',
'category' => 'Category',
'post_tags' => 'Tags',
'siteurl' => 'Screenshot',
);
return $columns;
}
// switching cases based on which $column we show the content of it
public function site_custom_columns($column) {
global $post;
switch ($column) {
case "title" : the_title();
break;
case "url" : $m = $this->mshot(150); echo '<a href="'.$m[0].'" target="_blank">'.$m[0].'</a>';
break;
case "category" : the_category();
break;
case "post_tags" : the_tags('',', ');
break;
case "siteurl" : $m = $this->mshot(150); echo $m[1];
break;
}
}
The difference with this custom column code compared to other custom columns codes is that it is invoking a method to generate the content displayed in two of the columns. This is a method that I have added below as an update to this post.
It sets the variable $m to the method mshot() with a size of 150. The number is used to make the image size. It is required, but will not affect the output when just getting the Url. The mshot() function returns an array containing the values of the url and image output. In order to retrieve them, we echo $m[0] for Url, and $m[1] for the auto generated site screenshot. This might be difficult to follow, as it is hard to explain. It took me a while to figure out how to get it to work, hence the lateness in my update.
Now let's add two more functions. First is a function for hooking into the template redirect action in WordPress, which will be used for setting up use of custom templates for Sites, which I'll explain more when we create new objects of this class.
UPDATE: $wp variable should be $wp_query or you get Notice: Undefined index 'post_type' in... I just realized this sorry.
// Template redirect for custom templates
public function template_redirect() {
global $wp_query;
if ($wp_query->query_vars['post_type'] == 'site') {
include(TEMPLATEPATH . '/single-site.php'); // a custom single-slug.php template
die();
} else {
$wp_query->is_404 = true;
}
}

This function is used for inserting new Site posts using the wp_insert_post() function now available in WordPress 3.0. It will be extremely useful later on in the series, too.
// For inserting posts
public function wp_insert_post($post_id, $post = null) {
if ($post->post_type == "site") {
foreach ($this->meta_fields as $key) {
$value = @$_POST[$key];
if (empty($value)) {
delete_post_meta($post_id, $key);
continue;
}
if (!is_array($value)) {
if (!update_post_meta($post_id, $key, $value)) {
add_post_meta($post_id, $key, $value);
}
} else {
delete_post_meta($post_id, $key);
foreach ($value as $entry) add_post_meta($post_id, $key, $entry);
}
}
}
}
Notice that the wp_insert_post() function uses the $meta_fields variable we've set up in the first tutorial. It is referred to using $this->meta_fields since it is being used inside the class method.
Adding Custom Meta Boxes
Using the add_meta_box() function you are able to hook into the posting page and add form fields. I plan to do a separate post for meta boxes as part of this post series so I am not going to go into much detail here.
Basically what is going on here is the admin_init() function initializes the meta_options() function. The meta_options() function gets the Url if one exists in the input field, and runs it through a wacky little script I created for allowing submitted Urls to work whether they include the http:// part of the Url or not. So http://new2wp.com AND new2wp.com will both be accepted, and work with the following.
The Url is then used to create a new Url that is encoded and appended to http://s.wordpress.com/mshots/v1/ to create a live screenshot of the page which the Url points to.
The input fields are then added. The first one is where you enter the Url, and the second one is just for making the new Url. The screenshot or properly known as, mshot, image it generates is then echoed out below the two input fields with the '?w=250' parameter appended to the end of it. This is to tell the mshot image it should have a width of 250 pixels. This code took me quite some time to compile and get working so enjoy it.
// Add meta box
function admin_init() {
add_meta_box("siteS-meta", "Site", array(&$this, "meta_options"), "site", "side", "high");
}
// Admin post meta contents
public function meta_options() {
global $post, $url;
$custom = get_post_custom($post->ID);
$url = $custom["siteurl"][0];
$myurl = trailingslashit( get_post_meta( $post->ID, 'siteurl', true ) );
if ( $myurl != '' ) {
if ( preg_match( "/http(s?):\/\//", $myurl )) {
$siteurl = get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( $myurl );
} else {
$siteurl = 'http://' . get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( 'http://' . $myurl );
}
$imgsrc = '<img src="' . $mshoturl . '?w=250" alt="' . $title . '" title="' . $title . '" />';
} ?>
<p><label>Clean Url: <input id="siteurl" size="26" name="siteurl" value="<?php echo $url; ?>" /></label></p>
<p><label>Mshot Url: <input id="mshoturl" size="26" name="mshoturl" value="<?php echo $mshoturl; ?>" /></label></p>
<p><?php echo '<a href="'.$siteurl.'">'.$imgsrc.'</a>'; ?></p>
<?php
} // end meta options
UPDATE: This Is A Newly Added Method
I apologize for not including this originally, my bad. Here is one more method to include after the meta_options function. This will output either the Url of a site post, or the image source including the size of the image by passing the object a number for the size. An example of for how to use this, refer to the custom columns code above. You will notice that the Url and image are output to create the separate columns for each site post. $m = $this->mshot(150); echo $m[1];
public function mshot($mshotsize) {
global $post, $url;
$imgWidth = $mshotsize;
$myurl = get_post_meta($post->ID, 'siteurl', true);
if ( $myurl != '' ) {
if ( preg_match( "/http(s?):\/\//", $myurl )) {
$siteurl = get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( $myurl );
} else {
$siteurl = 'http://' . get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode('http://'.$myurl );
}
}
$mshotimg = '<img src="'.$mshoturl.'?w='.$imgWidth.'" alt="'.get_the_title().'" title="'.get_the_title().'" />';
return array( $siteurl, $mshotimg );
}
Initializing it for Takeoff
We need to initialize the class functions, and add them to the dashboard admin, and then instantiate the class with a new object of it for hooking into the 'init' action. To do that you can add the following to your 'register_post_type() function just before the closing bracket.
UPDATED! The correct hook for the 'site_edit_columns' should be manage_edit-site_columns().
add_action( 'admin_init', array(&$this, 'admin_init') ); // this must be first
add_action( 'template_redirect', array(&$this, 'template_redirect') );
add_action( 'wp_insert_post', array(&$this, 'wp_insert_post'), 10, 2 );
// add custom columns
add_filter( 'manage_posts_custom_column', array( &$this, 'site_custom_columns' ));
add_action( 'manage_edit-site_columns', array ( &$this, 'site_edit_columns' )); // manage_edit-{post_type}_columns used for custom post types
To make custom columns for custom post types, WordPress manage_post_custom_columns() also uses manage_edit-{post_type}_columns to edit the custom post type manage post page columns. Just using manage_post_custom_columns for custom post types will cause your 'post' manage page to be overwritten with your custom columns.
After that you need to create a new object of the class to instantiate the class. This function will be used to make a new instance of the class. The function is then added to the init hook for WordPress.
/* Initialize Post Types */
add_action('init', 'pTypesInit');
function pTypesInit() {
global $sites;
$sites = new TypeSites();
}
The pTypesInit() function will be used to instantiate more post type classes later on in this series. By the time the series is over, the posttypes.php will be a work of art if ever one existed for custom post types.
The Full Sites Custom Post Type Class
This class is now complete. Of course you can always add or alter it however you like. Here is the full complete code.
<?php
// Initialize the Class and add the action
add_action('init', 'pTypesInit');
function pTypesInit() {
global $sites;
$sites = new TypeSites();
}
// Create a post type class for 'Site' posts
// To use as a bookmarking post type for sites you want to save/share.
class TypeSites {
// Store the data
public $meta_fields = array( 'title', 'description', 'siteurl', 'category', 'post_tags' );
// The post type constructor
public function TypeSites() {
$siteArgs = array(
'labels' => array(
'name' => __( 'Sites', 'post type general name' ),
'singular_name' => __( 'Site', 'post type singular name' ),
'add_new' => __( 'Add New', 'site' ),
'add_new_item' => __( 'Add New Site' ),
'edit_item' => __( 'Edit Site' ),
'new_item' => __( 'New Site' ),
'view_item' => __( 'View Site' ),
'search_items' => __( 'Search Sites' ),
'not_found' => __( 'No sites found in search' ),
'not_found_in_trash' => __( 'No sites found in Trash' ),
),
'public' => true, 'show_ui' => true,
'_builtin' => false,
'capability_type' => 'post',
'hierarchical' => false,
'rewrite' => array('slug' => 'site'), // Permalinks. Fixes a 404 bug
'query_var' => 'site',
'taxonomies' => array('category', 'post_tag'), // Add tags and categories taxonomies
'supports' => array('title','editor','author','comments')
);
register_post_type( 'site', $siteArgs );
// Initialize the methods
add_action( 'admin_init', array(&$this, 'admin_init') );
add_action( 'template_redirect', array(&$this, 'template_redirect') );
add_action( 'wp_insert_post', array(&$this, 'wp_insert_post'), 10, 2 );
// add custom columns
add_filter( 'manage_posts_custom_column', array( &$this, 'site_custom_columns' ));
add_action( 'manage_edit-site_columns', array ( &$this, 'site_edit_columns' )); // manage_edit-{post_type}_columns used for custom post types
}
// Create the columns and heading title text
public function site_edit_columns($columns) {
$columns = array(
'cb' => '<input type="checkbox" />',
'title' => 'Site Title',
'url' => 'URL',
'category' => 'Category',
'post_tags' => 'Tags',
'siteurl' => 'Screenshot',
);
return $columns;
}
// switching cases based on which $column we show the content of it
public function site_custom_columns($column) {
global $post;
switch ($column) {
case "title" : the_title();
break;
case "url" : $m = $this->mshot(100); echo '<a href="'.$m[0].'" target="_blank">'.$m[0].'</a>';
break;
case "category" : the_category();
break;
case "post_tags" : the_tags('',', ');
break;
case "siteurl" : $m = $this->mshot(150); echo $m[1];
break;
}
}
// Template redirect for custom templates
public function template_redirect() {
global $wp_query;
if ( $wp_query->query_vars["post_type"] == "site" ) {
include( TEMPLATEPATH . "/single-site.php" ); // a custom single-slug.php template
die();
} else {
$wp_query->is_404 = true;
}
}
// For inserting new 'site' post type posts
public function wp_insert_post($post_id, $post = null) {
if ($post->post_type == "site") {
foreach ($this->meta_fields as $key) {
$value = @$_POST[$key];
if (empty($value)) {
delete_post_meta($post_id, $key);
continue;
}
if (!is_array($value)) {
if (!update_post_meta($post_id, $key, $value)) {
add_post_meta($post_id, $key, $value);
}
} else {
delete_post_meta($post_id, $key);
foreach ($value as $entry) add_post_meta($post_id, $key, $entry);
}
}
}
}
// Add meta box
function admin_init() {
add_meta_box( "sites-meta", "Site", array( &$this, "meta_options" ), "site", "side", "low" );
}
// Admin post meta contents
public function meta_options() {
global $post, $url;
$custom = get_post_custom($post->ID);
$url = $custom["siteurl"][0];
$myurl = trailingslashit( get_post_meta( $post->ID, 'siteurl', true ) );
if ( $myurl != '' ) {
// Check if url has http:// or not so works either way
if ( preg_match( "/http(s?):\/\//", $myurl )) {
$siteurl = get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( $myurl );
} else {
$siteurl = 'http://' . get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( 'http://' . $myurl );
}
$imgsrc = '<img src="' . $mshoturl . '?w=250" alt="' . $title . '" title="' . $title . '" />';
} ?>
<p><label>Clean Url: <input id="siteurl" size="26" name="siteurl" value="<?php echo $url; ?>" /></label></p>
<p><?php echo '<a href="' . $siteurl . '">' . $imgsrc . '</a>'; ?></p>
<?php
} // end meta options
// Generate output for the url or image source for each site post
public function mshot($mshotsize) {
global $post, $url;
$imgWidth = $mshotsize;
$myurl = get_post_meta($post->ID, 'siteurl', true);
if ( $myurl != '' ) {
if ( preg_match( "/http(s?):\/\//", $myurl )) {
$siteurl = get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( $myurl );
} else {
$siteurl = 'http://' . get_post_meta( $post->ID, 'siteurl', true );
$mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode('http://'.$myurl );
}
}
$mshotimg = '<img src="'.$mshoturl.'?w='.$imgWidth.'" alt="'.get_the_title().'" title="'.get_the_title().'" />';
return array( $siteurl, $mshotimg );
}
} // end of TypeSites{} class
?>
This concludes the building of your first fully functional and intuitive Php Class for constructing a WordPress 3.0 custom post type. Save the posttypes.php file, and make sure to include it into your functions.php file. Then go to your dashboard to check out the new post type that should be added and working as a new section of the site. Please let me know of any mistakes I may have made or bugs that might be found. Cutting this code up and disecting it makes it easy for this to happen.

We will be using much of this same code with some minor changes, and replacing certain functions with others according to the different post type classes. But first let's try out this new class by adding some objects to a couple custom WordPress templates.
The next tutorial shows how to instantiate the class, and build a custom post type WordPress loop to query the custom post types posts that have been submitted.







User Comments
( ADD YOURS )Clive Thomas July 22
Fascinating series of articles. Iam learning a lot here, but I am having a problem with line 58 of the full listing, it is producing an error:
"Parse error: syntax error, unexpected T_ECHO in \functions\posttypes.php on line 58". Any ideas?
Jared July 22
UPDATED: I've updated the code which should work fine at this point
Im glad you like it and it is helpful for you.
I made a mistake in copying/pasting the columns code, I am surprised I managed to not make more mistakes than just this so far.
Here's the fixed code that works
// Create the columns and heading title text public function site_edit_columns($columns) { $columns = array( 'cb' => ' <input type="checkbox" />', 'title' => 'Site Title', 'url' => 'URL', 'category' => 'Category', 'post_tags' => 'Tags', 'siteurl' => 'Screenshot', ); return $columns; } // switching cases based on which $column we show the content of it public function site_custom_columns($column) { global $post; switch ($column) { case "title" : the_title(); break; case "url" : $m = $this->mshot(100); echo '<a href="'.$m[0].'" target="_blank">'.$m[0].'</a>'; break; case "category" : the_category(); break; case "post_tags" : the_tags('',', '); break; case "siteurl" : $m = $this->mshot(150); echo $m[1]; break; } }After looking at this again I don't know what I was thinking, but it was way off so I apologize. I have so many post type codes I am pulling this code from which I've been working on and writing for a while now. Easy to get lost in it all. Let me know if you still have problems.
Jared July 22
Compare your old code with this code, you'll see the differences, and just how far off I was. :}
Clive Thomas July 23
thx Jared, that fixed it. Looking forward to the next instalment :)
Suso Guez August 7
Thanks for this articles!
I found an error: the custom columns don't work. Realized that the filter used is manage_edit-portfolio_columns, instead of manage_edit-site_columns. Also the functions used with this filter and manage_posts_custom_column need to swap.
I understand you copy from many codes, and appreciate the time that you spend making this series, but would be amazing if you could test it as well.
Any date in mind for part 3?
Thanks!
Jared August 7
Thanks, I remember fixing that filter before in this post with a code that I had tested. I must have somehow re-replaced it again. Apologies for the misinformation, and to make it up to you I've updated the post with some a new function, which is used to generate the custom columns, and that will be used in the next post.
The updated code changes and additions in this post include the correct filters:
The new and improved custom columns code:
This invokes the newly added mshot() method to display and link the url of the site which is included in the post, and makes a column which shows a live screenshot of the site, simply with the use of the entered url.
// Create the columns and heading title text public function site_edit_columns($columns) { $columns = array( 'cb' => ' <input type="checkbox" />', 'title' => 'Site Title', 'url' => 'URL', 'category' => 'Category', 'post_tags' => 'Tags', 'siteurl' => 'Screenshot', ); return $columns; } // switching cases based on which $column we show the content of it public function site_custom_columns($column) { global $post; switch ($column) { case "title" : the_title(); break; case "url" : $m = $this->mshot(100); echo '<a href="'.$m[0].'" target="_blank">'.$m[0].'</a>'; break; case "category" : the_category(); break; case "post_tags" : the_tags('',', '); break; case "siteurl" : $m = $this->mshot(150); echo $m[1]; break; } }And a whole new function that makes the magic happen.
This returns an array containing the url and image with the size of the image, which is passed to the function.
public function mshot($mshotsize) { global $post, $url; $imgWidth = $mshotsize; $myurl = get_post_meta($post->ID, 'siteurl', true); if ( $myurl != '' ) { if ( preg_match( "/http(s?):\/\//", $myurl )) { $siteurl = get_post_meta( $post->ID, 'siteurl', true ); $mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode( $myurl ); } else { $siteurl = 'http://' . get_post_meta( $post->ID, 'siteurl', true ); $mshoturl = 'http://s.wordpress.com/mshots/v1/' . urlencode('http://'.$myurl ); } } $mshotimg = '<img src="'.$mshoturl.'?w='.$imgWidth.'" alt="'.get_the_title().'" title="'.get_the_title().'" />'; return array( $siteurl, $mshotimg ); }Again, sorry for copying codes from other sites, I'm clearly better off just writing my own codes. Who cares anymore about how to make a portfolio site using custom post types anyways, since there's enough tuts out there that show how to, in exactly the same way.
The code I had 'thought' I posted before, had been tested and working, though in writing, cutting and pasting this tutorial somehow a mistake or two was made. Guess I am human after all.
I am hoping to get the next post in the series done early this week, I have been extremely busy lately working 3 jobs so I just haven't had the time to do it. But it will be soon, I promise.
Jared August 9
I have made a couple of fixes.
In the template_redirect function, the variable $wp should be $wp_query, so I changed where it said $wp.
The manage_post_custom_columns() function call should be manage_edit-site_columns(). I have written about this more above where the add_actions() are called for custom columns. Sorry for my poorly written post, I will be sure to triple check and test further posts for this.
It's not easy to find much info on any of this as you may know, so it's easy to make mistakes.
Anton Zaroutski October 12
Hi man, great article. I would love to see the shift towards a more generalised class where you don't specify custom type name within the class and its methods but allow define them at the time of instantiating of the class like $bookCustomType = new CustomPostType('Books', 'Book', 'book)....... $bookCustomType->enableCoreTaxonomy('categories')..etc.
Jared October 12
Thanks. Yeah for a long while I worked on a way to try and abstract the class and use child classes which utilize the main functions in the parent class. I wasn't ever successful in my efforts, and since this worked I figured I'd share it.
But I now know how to do that with some help. You should check this out, since it's pretty much what you're talking about, and an overall better way to do it OO. http://somadesign.ca/projects/smarter-custom-post-types/
Anton Zaroutski October 12
Nice one! Thanks for the link. I visit your site often - keep up great work, man!
Jared October 12
Thanks :) You should check out my newest big project site too if you haven't already.
2010 WPhonors - http://2010.wphonors.com
You can join, post, vote, and have a chance to be randomly chosen to win one of the 58 prizes that will be given away too.
James M December 30
I've really enjoyed this series, it really covers all the pieces of using custom post types in one. However, i'm running into issues trying to create two different custom post types, duplicate class/variable names. I'm trying to duplicate and rename the duplicates, but it's hard for me to know which is which.
Do you have any recommendations to creating two different custom post types using your OO method here.
Jared January 1
What I have always done is just copy the entire class and paste it below, then do find+relpace for all the things that you'd need to change.
Then in the init function where you create variables
global $site;
$site = new TypeSite();
just add another variable for each new post type class you create, with the name for it and then instantiate the class with the $variable = new TypeWhatever();
That will initialize each of your classes.
Trackbacks