Categories
How To Plugins WordPress

Creating Presentations With Custom Post Types

I wanted to come up with a cool way to showcase how powerful custom post types were for the Boston WordPress meetup. So I put together a quick little plugin that showcases:

  • Creating custom post types
  • Creating custom taxonomies
  • Creating custom labels for your custom post type
  • Creating contextual help for your custom post type
  • Using a custom template in a plugin for your custom post type
  • Sorting and displaying content in our custom post types in a unique way

What I came up with was a presentation plugin that uses Google’s html5slide template.

The Design

Our plugin will use a custom post type to hold the slides and a custom taxonomy to bind them all together. Our plugin will use a custom template that we will conditionally load when viewing a taxonomy archive. For usability sake, we will redirect individual links to slides to the appropriate slide within the presentation view.

Create Our Custom Post Types

We’ll begin our plugin with our custom post type to hold our slides.

function cptslides_init() {
    $labels = array(
        'name' => 'Slides',
        'singular_name' => 'Slide',
        'add_new' => 'Add New', 'slide',
        'add_new_item' => 'Add New Slide',
        'edit_item' => 'Edit Slide',
        'new_item' => 'New Slide',
        'all_items' => 'All Slides',
        'view_item' => 'View Slide',
        'search_items' => 'Search Slides',
        'not_found' => 'No slides found',
        'not_found_in_trash' => 'No slides found in Trash',
        'parent_item_colon' => '',
        'menu_name' => 'Slides'
    );
    $args = array(
        'labels' => $labels,
        'public' => true,
        'publicly_queryable' => true,
        'show_ui' => true,
        'show_in_menu' => true,
        'query_var' => true,
        'rewrite' => true,
        'capability_type' => 'post',
        'has_archive' => true,
        'hierarchical' => false,
        'menu_position' => null,
        'supports' => array('title', 'editor', 'author', 'page-attributes')
    );

    register_post_type('cptslides', $args);
}
add_action('init', 'cptslides_init');

This creates a custom post type named Slides. Notice within register_post_type(), we prefixed our custom post type with ‘cpt’. This is so our custom post type plugin doesn’t conflict with other plugins that might create a ‘slide’ custom post type.

We also set up the appropriate labels for our custom post type and passed them in to to register_post_type() via the $args array.

Another key in $args lets us tell WordPress which features our custom post type supports. For our plugin, our slides will need a title, some content, an author and page attributes so we can sort our slides.

For good measure, let’s also create some custom admin messages for our custom post type.

function cptslides_updated_messages($messages) {
    global $post, $post_ID;

    $messages['cptslides'] = array(
        0 => '', // Unused. Messages start at index 1.
        1 => sprintf(__('Slide updated. View slide in presentation'), esc_url(get_permalink($post_ID))),
        2 => __('Custom field updated.'),
        3 => __('Custom field deleted.'),
        4 => __('Slide updated.'),
        5 => isset($_GET['revision']) ? sprintf(__('Slide restored to revision from %s'), wp_post_revision_title((int) $_GET['revision'], false)) : false,
        6 => sprintf(__('Slide published. View slide in presentation'), esc_url(get_permalink($post_ID))),
        7 => __('Slide saved.'),
        8 => sprintf(__('Slide submitted. Preview presentation'), esc_url(add_query_arg('preview', 'true', get_permalink($post_ID)))),
        9 => sprintf(__('Slide scheduled for: %1$s. Preview presentation'), date_i18n(__('M j, Y @ G:i'), strtotime($post->post_date)), esc_url(get_permalink($post_ID))),
        10 => sprintf(__('Slide draft updated. Preview presentation'), esc_url(add_query_arg('preview', 'true', get_permalink($post_ID)))),
    );
    return $messages;
}
add_filter('post_updated_messages', 'cptslides_updated_messages');

Create Presentation Taxonomy

Now we can create slides with WordPress for our presentation plugin but we need a way to group slides together into individual presentations. For this we will create a custom taxonomy.

function create_cptslides_taxonomies() {
    // Add new taxonomy, make it hierarchical (like categories)
    $labels = array(
        'name' => _x('Presentations', 'taxonomy general name'),
        'singular_name' => _x('Presentation', 'taxonomy singular name'),
        'search_items' => __('Search Presentations'),
        'all_items' => __('All Presentations'),
        'parent_item' => __('Parent Presentation'),
        'parent_item_colon' => __('Parent Presentation:'),
        'edit_item' => __('Edit Presentation'),
        'update_item' => __('Update Presentation'),
        'add_new_item' => __('Add New Presentation'),
        'new_item_name' => __('New Presentation Name'),
        'menu_name' => __('Presentations'),
    );

    register_taxonomy('cptslides_presentation', array('cptslides'), array(
        'hierarchical' => true,
        'labels' => $labels,
        'show_ui' => true,
        'query_var' => true,
        'rewrite' => array('slug' => 'presentation'),
    ));
}
add_action('init', 'create_cptslides_taxonomies', 0);

Same as with our custom post type, we prefix our custom taxonomy so it won’t conflict with other taxonomies called ‘presentation’.

Add Contextual Help For Our Custom Post Type

Maybe this is just a pet peeve of mine, but I strongly believe if a plugin adds functionality to the WordPress admin, it should provide contextual help that describes how to use that functionality. It’s really easy to do and ultimately creates a better user experience.

function cptslides_help_text($contextual_help, $screen_id, $screen) {
    //$contextual_help .= $screen_id; // use this to help determine $screen->id
    if ('cptslides' == $screen->id) {
        $contextual_help =
                '

' . __('Things to remember when creating custom post types:') . '

' . '
    ' . '
  • ' . __('It is better if you prefix your name with a short "namespace" that identifies your plugin, theme or website that implements the custom post type.') . '
  • ' . '
  • ' . __('Custom post types have little to do with blog posts and are better classified as custom content types.') . '
  • ' . '
  • ' . __('Remember you can change the labels and messaging on your custom post as well as the contextual help (this).') . '
  • '; } elseif ('edit-cptslides' == $screen->id) { $contextual_help = '

    ' . __('These slides are all part of individual presentations grouped by taxonomies.') . '

    '; } else if ('edit-cptslides_presentation' == $screen->id) { } return $contextual_help; } add_action('contextual_help', 'cptslides_help_text', 10, 3);

Add Custom Columns To Custom Post Type List

Managing our slides from our new custom post type post list can become cumbersome to manage as the number of slides increase. Something I try to do with all of my custom post types is to tailor the columns to better fit the post types purpose. In this case we should probably add the presentation name and the order within the presentation as columns on our slide list.

// Set up our column headers
function cptslides_edit_columns($columns) {
    $columns = array(
        "cb" => "",
        "title" => "Title",
        "presentation" => "Presentation",
        "order" => "Order",
        "author" => "Author",
        "date" => "Date"
    );

    return $columns;
}

// Output data under new column headers
function cptslides_custom_columns($column) {
    global $post;

    switch ($column) {
        case "order":
            if (isset($post->menu_order)) {
                echo $post->menu_order;
            } else {
                echo 0;
            }
            break;
        case "presentation":
            $presentation = get_the_term_list($post->ID, 'cptslides_presentation', '', '', '');
            echo $presentation;
            break;
    }
}

// Modify orderby query to order by our new data
function cptslides_column_orderby($orderby, $wp_query) {
    global $wpdb;

    $wp_query->query = wp_parse_args($wp_query->query);
    if (isset($wp_query->query['orderby'])) {
        if ('order' == @$wp_query->query['orderby'])
            $orderby = "menu_order " . $wp_query->get('order');
    }
    return $orderby;
}

// More complex orderby query to order by taxonomy
// Adapted from: http://scribu.net/wordpress/sortable-taxonomy-columns.html
function cptslides_position_clauses($clauses, $wp_query) {
    global $wpdb;

    if (isset($wp_query->query['orderby']) && 'presentation' == $wp_query->query['orderby']) {

        $clauses['join'] .= <<term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
SQL;

        $clauses['where'] .= " AND (taxonomy = 'cptslides_presentation' OR taxonomy IS NULL)";
        $clauses['groupby'] = "object_id";
        $clauses['orderby'] = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
        $clauses['orderby'] .= ( 'ASC' == strtoupper($wp_query->get('order')) ) ? 'ASC' : 'DESC';
    }
    return $clauses;
}

// Register our new table headers so they are clickable
function cptslides_column_register_sortable($columns) {
    $columns['presentation'] = 'presentation';
    $columns['order'] = 'order';
    return $columns;
}
add_filter('manage_edit-cptslides_sortable_columns', 'cptslides_column_register_sortable');
add_filter('posts_orderby', 'cptslides_column_orderby', 10, 2);
add_filter('posts_clauses', 'cptslides_position_clauses', 10, 2);
add_action("manage_posts_custom_column", "cptslides_custom_columns");
add_filter("manage_edit-cptslides_columns", "cptslides_edit_columns");

There’s a lot going on here, so I’ll break it down by function/filter.

The cptslides_edit_columns function in the manage_edit-cptslides_columns filter sets up our columns and their respective headers.

The cptslides_custom_columns function in the manage_posts_custom_column filter displays our custom column data for each slide. In this case we are making use of the built in menu_order page attribute to sort our slides within our presentations so we can just grab that from the global $post variable. For our custom taxonomy, we just grab the terms from the current post.

Up to this point our columns exist but aren’t sortable. The cptslides_column_register_sortable function in the manage_edit-cptslides_sortable_columns filter basically tells WordPress our column headers for our custom columns should be clickable. Just because they’re clickable, doesn’t mean we can sort anything yet.

The posts_orderby function in the cptslides_column_orderby filter makes our ‘Order’ column sortable. Basically it checks to see what the post list is currently being sorted by, and if it’s being sorted by order, we modify the query for the post list and order by our built in order page attribute.

Sorting custom taxonomies are a bit trickier because they are not inherently part of the query that generates our custom post list. The cptslides_position_clauses function in the posts_clauses filter solves that problem by creating joins on the term tables, grouping and ordering the results. The original author of this bit of code is Silviu-Cristian Burca (source).

Use A Custom Template For Our Custom Post Type

The great thing about custom post types is WordPress can automatically display your content without having to create a custom template. However, WordPress added support for single-type-templates in WP 3.0 and for archive-type-templates in WP 3.1. This allows you to easily create custom templates for your custom post types without any additional coding in our plugin.

But what if you wan’t your template to live within your plugin so it can be moved around independent of your theme? Fortunately this is also pretty easy with the template_include filter.

function cptslides_template($template) {
    global $post;
    if ($post->post_type == 'cptslides') {
        return CPTSLIDES_PATH . 'template.php';
    }
    return $template;
}

add_filter('template_include', 'cptslides_template', 1, 1);

All this does is check to see if we are currently in a taxonomy or single page from our custom post type and pass in our custom template if we are.

The custom template in this case is a single file with the html5slides template code. All I’ve done is swapped in the existing content with the WordPress loop. I also use some query_posts() to modify the loop to display all slides within an archive and sort by menu_order.

Now when we view a presentation archive, we get a completed presentation with all of the slides loaded in the proper order.

Redirect Our Custom Post Type To A Taxonomy

At this point the plugin is pretty much done except for one pesky usability problem for the admin. If the admin tries to view a single slide, we see that slide outside of the context of the presentation. The ideal functionality would be to have that slide take you to that slide within the presentation. Because our plugin is using the html5slides library, we can just redirect the user to the presentation and append a number sign with our slide number to the URL so the presentation automatically jumps to the appropriate slide.

To do this, we hook in to the template_redirect filter.

function cptslides_redirect() {
    global $post;
    // Instead of accessing slides directly, redirect to archive and focus on requested slide
    if (get_post_type() == 'cptslides' && !is_tax()) {
        // Find the slide's presentation link
        $terms = get_the_terms($post->ID, 'cptslides_presentation');
        foreach ($terms as $term) {
            $term_link = get_tag_link($term);
            break;
        }
        // Loop through slides to find the requested slides order in the presentation
        $counter = 1;
        $post_id = $post->ID;
        $args = array(
            'orderby' => 'menu_order',
            'order' => 'ASC',
            'posts_per_page' => -1,
            'cptslides_presentation' => 'custom-post-types',
            'post_status' => 'publish'
        );
        $query = new WP_Query($args);
        if ($query->have_posts())
            while ($query->have_posts()): $query->the_post();
                $counter++;
                if ($post_id == $post->ID) {
                    $term_link .= '#' . $counter;
                    break;
                }
            endwhile;
        // Go to the presentation
        wp_redirect($term_link);
        die();
    }
}

add_action('template_redirect', 'cptslides_redirect');

Once again there are a couple things going on here.

  1. First we check to see if we are trying to load an individual slide
  2. Next we grab the URL for the presentation the slide lives in.
  3. Then we count through all slides in the presentation until we find the slide we were trying to visit
  4. Once we know the slide, we redirect the user to the slide within the presentation
  5. Now we can edit slides within WordPress and then jump directly to that slide within the presentation with no extra clicking around.

    Finished Code

    You can grab the finished Custom Post Type Slides code off of GitHub. Feel free to fork it and make it your own or leave your own ideas for the plugin in the comments.

    Here is a working example of the plugin that I used to give the original presentation. You can watch the video from the presentation on BostonWP.org.

One reply on “Creating Presentations With Custom Post Types”

Leave a Reply

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