Mastering Custom Post Types - WordCamp Atlanta 2012

Preview:

DESCRIPTION

Presentation of WordCamp Atlanta 2012 - Covers a collection of PHP-based techniques for using Custom Post Types to get the most out of WordPress.

Citation preview

Mastering WordPressCustom Post TypesBy Mike Schinkel – @mikeschinkelFor WordCamp Atlanta Feb 2012

Slides Available Now

On SpeakerDeck (PDF): http://speakerdeck.com/u/mikeschinkel/

On SlideShare (PPTX): http://www.slideshare.net/mikeschinkel/

The Code (either one): https://gist.github.com/1728805 http://bit.ly/mastering-wordpress-custom-post-ty

pes-wordcamp-atlanta-2012

416style

Who Am I?

Conflicted: ½ Entrepreneur, ½ Web Developer

Founder & Prez of NewClarity in Atlanta – We: Build Plugins for Distribution via WordPress.org Extend WordPress for Vertical Market CMS

Active Teacher 350+ Answers on SE’s WordPress Answers Former Meetup Organizer & Presenter Former Developer Trainer

What do I know?

Tends to develop deep expertise in narrow areas.

2010-2012 – WordPress + PHP + MySQL + jQuery

2007-2009 – Drupal + PHP + MySQL

1995-2006 – ASP + VBScript + MS SQL Server

1993-1994 – Visual Basic + Access Database

1987-1993 – Clipper for DOS

1985-1986 – dBase II & dBase III

1983-1984 – Turbo Pascal

My Primary ToolsetMostly commercial because they are worth it.

PhpStorm ($99) + Zend Debugger (free)

Navicat for MySQL ($79)

VirtualHostX ($40)

Transmit for FTP ($34)/FileZilla (free)

HTTPScoop ($15)

Apache+PHP (on Mac OS X), MySQL (free)

What We’ll Cover TodayEverything will be in PHP

1. Define a Custom Post Type in PHP

2. Custom Form ("MetaBox") + Custom Fields

3. Theme Template for Custom Post Type

4. Custom Columns in Admin Post List

5. Custom Taxonomies for any Post Type

Today (cont’d)

6. Configure Edit Screens for Posts and Pages

7. Parent Post Field in a Post Editor Metabox

8. Querying Custom Post Types

9. Hierarchical URLs for Custom Post Types

10.Custom Post Type Filters in Admin Post List

Recognize and Bypass the Various Gotchas!

What Today’s Post Type?

Example Today:

“PressedComics”

Inspiration for our example:

Small World Comics

Fire up PhpStorm!

Create a Child Theme

/wp-content/themes/mikes_theme/styles.css

/*Theme Name: Mike's ThemeDescription: Child Theme of Twenty ElevenTemplate: twentyeleven*/@import url("../twentyeleven/style.css");

Add a functions.php file

/wp-content/themes/mikes_theme/functions.php<?php/* * functions.php for mikes_theme * */

Part 1

Add a Custom Post TypeCalled "Comics"

Register Post Type

<?php/* * functions.php for mikes_theme * */

register_post_type( 'comic' );

Must Use "init" Hook

<?php

add_action( 'init', 'mikes_theme_init' );function mikes_theme_init() { register_post_type( 'comic' );}

Must Make "public"

function mikes_theme_init() { register_post_type( 'comic', array( 'public' => true, ));}

But!

Must Give "label"

function mikes_theme_init() { register_post_type( 'comic', array( 'public' => true, 'label' => 'Comics', ));}

Now!

Y URL NO LOAD COMIC?!?

Refresh Permalinks

Like Candy from a Baby!

Need "with_front" to omit

/blog/ from URL: function mikes_theme_init() { register_post_type( 'comic', array( 'public' => true, 'label' => 'Comics', 'rewrite' => array( 'with_front' => false, ), ));}

Better, but quite right:

Need "slug" to make URL start with /comics/

(pural): function mikes_theme_init() { register_post_type( 'comic', array( 'public' => true, 'label' => 'Comics', 'rewrite' => array( 'with_front' => false, 'slug' => 'comics', ), ));} Perfecto!

THE COMIC POST LISTIN THE ADMIN:

THE COMIC POST EDIT SCREEN:

Part 2

Add aCustom Form (a "Metabox")

and Custom Fields

The "Comic Information"Custom Fields Metabox

Add a Custom Metabox:

add_action('add_meta_boxes','mikes_theme_add_meta_boxes');function mikes_theme_add_meta_boxes( $post_type ) { if ( 'comic' == $post_type ) add_meta_box( 'comic_info_box', // $id 'Comic Information', // $title 'mikes_theme_comic_meta_box', // $callback 'comic', // $page, 'normal', // $context, 'high' ); // $priority }

Add a Custom Metabox Callback Function

function mikes_theme_comic_meta_box( $post ) { $cartoonist = ''; $since= ''; $website = ''; $html =<<<HTML<table> <tr> <th><label for="cartoonist">Cartoonist:</label></th> <td><input type="text" name="cartoonist" value="{$cartoonist}" size="25" /></td> </tr> <tr> <th><label for="since">Since:</label></th> <td><input type="text" name="since" value="{$since}" size="15" /></td> </tr> <tr> <th><label for="website">Website:</label></th> <td><input type="text" name="website" value="{$website}" size="25" /></td> </tr></table>HTML; echo $html;}

Update Custom Fields:

add_action( 'save_post', 'mikes_theme_save_post');function mikes_theme_save_post( $post_id ) { if ( 'comic' == $post_type ) { update_post_meta($post_id,'_cartoonist',$_POST['cartoonist']); update_post_meta($post_id,'_since',$_POST['since']); update_post_meta($post_id,'_website',$_POST['website']); }}

Note the leading underscores on the post meta 'key' names (2nd

parameter)

Load Custom Fields:

function mikes_theme_comic_meta_box( $post ) { $cartoonist = get_post_meta( $post->ID, '_cartoonist', true ); $since = get_post_meta( $post->ID, '_since', true ); $website = get_post_meta( $post->ID, '_website', true ); $html =<<<HTML<table>

Part 3

Create a Theme Template File

for a Custom Post Type

Simple Example Theme Template for Custom Post

Type

/wp-content/themes/

your_theme/single-comic.php:

<?php //Template for displaying single Comic Post Type.get_header();if ( have_posts() ): the_post(); ?><div id="content"><div id="post-<?php the_ID(); ?>" <?php post_class(); ?>><h1 class="entry-title"><?php the_title(); ?></h1>Cartoonist: <?php echo get_post_meta( $post->ID, '_cartoonist', true); ?><br/>Since: <?php echo get_post_meta( $post->ID, '_since', true ); ?><br/>Website: <?php echo get_post_meta( $post->ID, '_website', true ); ?><br/><br/><?php the_content(); ?></div></div><?php endif;get_footer(); ?>

Part 4

Add Admin Columns for your

Custom Post Type

Admin Columns for the "Comics" Custom Post Type

Use hook"manage_edit-{$post_type}_columns"

add_filter( 'manage_edit-comic_columns', 'mikes_theme_manage_edit_comic_columns'

);function mikes_theme_manage_edit_comic_columns( $columns ) { $columns['cartoonist'] = 'Cartoonist'; $columns['website'] = 'Website'; return $columns;}

Use hook"manage_{$post_type}_posts_custom_columns"

add_filter( 'manage_comic_posts_custom_column', 'mikes_theme_manage_comic_posts_custom_column', 10, 2 );function mikes_theme_manage_comic_posts_custom_column( $column_name, $post_id) { switch ( $column_name ) { case 'cartoonist': echo get_post_meta( $post_id, "_cartoonist", true ); break; case 'website': $website = get_post_meta( $post_id, "_website", true ); $domain = trim( str_replace( 'http://', '', $website ), '/' ); echo "<a href=\"{$website}\">{$domain}</a>"; break; }}

Better Admin Columns for the "Comics" Custom Post Type

function mikes_theme_manage_edit_comic_columns( $columns ) { $new_columns = array(); foreach( $columns as $key => $value ) { if ( 'date' == $key ) { $new_columns['cartoonist'] = 'Cartoonist'; $new_columns['website'] = 'Website'; } $new_columns[$key] = $value; } return $new_columns;}

Part 5

Adding a Custom Taxonomy

to a Post Type

An "Art Style" Taxonomyfor the Comics Post Type

function mikes_theme_init() { ... ... register_taxonomy( 'art-style', 'comic', array( 'hierarchical' => true, 'label' => 'Art Styles', 'rewrite' => array( 'slug' => 'art-styles', 'with_front' => false ), ));

Part 6

Configure Post Edit Screens

(including Posts and Pages)

'supports' => array('title','excerpt','thumbnail')

gets this:

Configure an "Episodes" Post Type Specify what it

"supports"

function mikes_theme_init() { ... ... register_post_type( 'episode', array( 'label' => 'Episodes', 'public' => true, 'rewrite' => array( 'slug' =>'episodes', 'with_front' => false ), 'supports' => array( 'title', 'thumbnail', 'excerpt' ), ));

Options for "supports"

'title' – Includes permalink 'editor' – WYSIWYG+HTML content 'author' 'thumbnail' – Featured, theme must support post-thumbnails 'excerpt' 'trackbacks' 'custom-fields' 'comments – Will add comment count balloon on edit screen 'revisions' – Will store revisions 'page-attributes' – Menu order, Parent if hierarchical=true 'post-formats' – Add post formats

Part 7

Parent Post Field in a Post Editor Metabox

(useful for non-same post types)

Create a Metabox to Associate an Episode with a Comic: 'parent_id' is

key

function mikes_theme_add_meta_boxes($post_type) { ... if ( 'episode' == $post_type ) add_meta_box( 'episode_info_box', 'Episode Info', 'mikes_theme_episode_meta_box', 'episode','side','low' );}function mikes_theme_episode_meta_box( $post ) { $html =<<<HTML<table><tr><th><label for="parent_id">Comic:</label></th><td><input type="text" name="parent_id" value="{$post->post_parent}" /></td></tr></table>HTML; echo $html;}

Part 8

Querying Custom Post

Types

Use the "Comic" Drop Down within the hook 'mikes_theme_episode_meta_box'

function mikes_theme_episode_meta_box( $post ) { $select_html = mikes_theme_comic_dropdown( $post->post_parent ); $html =<<<HTML<table><tr><th><label for="parent_id">Comic:</label></th><td>{$select_html}</td></tr></table>HTML; echo $html;}

Use WP_Query() to create a "Comic" Drop Down for "Episode" Parent

Selection

function mikes_theme_comic_dropdown( $selected_id ) { $query = new WP_Query( 'post_type=comic&posts_per_page=-1' ); $comics = array(); foreach( $query->posts as $comic ) { $title = get_the_title( $comic->ID ); $selected = $comic->ID == intval( $selected_id ) ? ' selected' : ''; $comics[ $comic->ID ] = <<<HTML<option value="{$comic->ID}"{$selected}>{$title}</option>HTML; } $comics = implode( '', $comics ); $html =<<<HTML<select name="parent_id"><option value="0">None selected</option>{$comics}</select>HTML; return $html;}

Part 9

Hierarchical URLs for Custom Post Types

Hierarchical URLs:

Enable /comics/{$comic}/{$episode}/such as /comics/small-world/xmas-2012/

1. Call add_rewrite_rule() in 'init' hook.

2. Add 'query_var' arguments to 'comic' and 'episode' post types named 'comic_qv' and 'episode_qv', respectively.

3. Add 'post_type_link' hook.

4. Add 'request' hook.

Call add_rewrite_rule() in 'init' hook.And add 'query_var' arguments

add_rewrite_rule( '^comics/([^/]*)/([^/]*)/?', 'index.php?post_type=episode&comic_qv=$matches[1]&episode_qv=$matches[2]', 'top');register_post_type( 'comic', array( ... 'query_var' => 'comic_qv', ...));register_post_type( 'episode', array( ... 'query_var' => 'episode_qv', ...));

Add 'post_type_link' hook.

add_action( 'post_type_link', 'mikes_theme_post_type_link', 10, 4 );function mikes_theme_post_type_link( $link, $post, $leavename, $sample ) { if ( 'episode' == $post->post_type ) { $parent = get_post( $post->post_parent ); $comic_slug = isset( $parent->post_name ) ? $parent->post_name ' : '%comic%'; $episode_slug = $sample ? '%postname%' : $post->post_name; $link = preg_replace( '#^(https?://[^/]+/).*$#', "$1comics/{$comic_slug}/{$episode_slug}/", $link ); } return $link;}

Add 'request' hook.

add_action( 'request', 'mikes_theme_request' );function mikes_theme_request( $query_vars ) { global $wp; if ( ! is_admin() && isset( $query_vars['post_type'] ) && 'episode' == $query_vars['post_type'] ) { $comic = get_page_by_path( $query_vars['comic_qv'], OBJECT, 'comic' ); $episode_query = new WP_Query( array( 'name' => $query_vars['episode_qv'], 'post_type' => 'episode', 'fields' => 'ids', 'post_parent' => $comic->ID, 'posts_per_page' => 1, 'suppress_filters' => true, )); if ( 0 == count( $episode_query->posts ) ) { $query_vars = array( 'error' => '404' ); } } return $query_vars;}

To get our "Episodes" Page:

/wp-content/themes/

your_theme/single-episode.php:

<?phpget_header();if ( have_posts() ): the_post(); ?><div id="content"> <div id="post-<?php the_ID(); ?>" <?php post_class(); ?>> <?php edit_post_link( 'Edit', '<div class="edit-link">', '</div>' ); ?> <?php previous_post_link( '%link', '&lt;&lt;&lt;Previous' ); ?> &nbsp;&nbsp;&nbsp; <?php next_post_link( '%link', 'Next&gt&gt&gt;' ); ?> <h2 class="entry-parent">Comic: <?php echo get_the_title( $post->post_parent ); ?></h2> <h1 class="entry-title"><?php the_title(); ?></h1> <?php the_content(); ?> <?php if ( has_post_thumbnail( $post->ID ) ) $image_html = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' ); ?> <img src="<?php echo $image_html[0]; ?>"> </div></div><?php endif;get_footer(); ?>

Part 10

Custom Post Type Filters in Admin Post List

Filtering our "Episodes:"

Or Not:

Enable /comics/{$comic}/{$episode}/such as /comics/small-world/xmas-2012/

1. Make mikes_theme_comic_dropdown() reusable.

2. Add 'restrict_manage_posts' hook.

3. Add 'pre_get_posts' hook.

Make mikes_theme_comic_dropdown() reusable

function mikes_theme_comic_dropdown( $selected_id, $name = 'parent_id' ) { $query = new WP_Query( 'post_type=comic&posts_per_page=-1' ); $comics = array(); foreach( $query->posts as $comic ) { $title = get_the_title( $comic->ID ); $selected = $comic->ID == intval( $selected_id ) ? ' selected' : ''; $comics[ $comic->ID ] = <<<HTML<option value="{$comic->ID}"{$selected}>{$title}</option>HTML; } $comics = implode( '', $comics ); $html =<<<HTML<select name="{$name}"><option value="0">None selected</option>{$comics}</select>HTML; return $html;}

Add 'restrict_manage_posts' hook.And add 'pre_get_posts' hook.

add_action( 'restrict_manage_posts', 'mikes_theme_restrict_manage_posts' );function mikes_theme_restrict_manage_posts() { $comic_id = empty( $_GET['comic_id'] ) ? 0 : $_GET['comic_id']; echo 'Comic: ' . mikes_theme_comic_dropdown( $comic_id, 'comic_id' );}

add_filter('pre_get_posts','mikes_theme_pre_get_posts');function mikes_theme_pre_get_posts( $query ) { global $pagenow, $typenow, $wp_the_query; if ( 'edit.php' == $pagenow && 'episode' == $typenow && $query === $wp_the_query && ! empty( $_GET['comic_id'] ) ) { $query->set( 'post_parent', intval( $_GET['comic_id'] ) ); }}

Takeaway

You can do practically anything you put your mind to with

WordPress' CPTs and a little PHP!

But Wait!

There's More…

Look for “Sunrise”

A Platform Extension for WordPress

Targeting needs of Professional Site Builders

To be GPL and freely distributed

Designed to be Modular

Goal: To Have Community of Module Contributors

Timeline: Pre-Alpha now Closed Beta – Q1 2012 Open Beta – Q2 2012 Release – Hmm…

Thank You

To Contact Me:Twitter: @mikeschinkelhttp://about.me/mikeschinkel