Apply template to custom post type

Today I got a client that wanted custom template for each page and section within.
I proposed Laravel custom sh3t, but he wanted WordPress, as it seems more easy to main( not from my experience ).

.. So far so good. I’m a little confused there.

So I decided that my parent entity would be page, so I can apply different template to each page. Then I have a section within this page, which should be not hardcored. User should have the ability to choose section layout ( template ), and remove or reorder sections within the current page. And
Finally I have posts as they are the smallest entity in the website. Posts would be rendered as columns like in bootstrap ( col-md-2, col-lg-6 ), etc..

I decided to create a custom post type to use it as a section, but then I read that post types cannot have template, only pages can have ones. This compromised my plan so far and spend 5 hours in researching a solution( no exit ). So I need another strategy for doing this.
I need templates for two entities.

Can someone suggest a solution to this problem? ( I will buy you a beer! )

EDIT:

To create my own custom post type, I use plugin in wordpress called ‘Custom Post Type UI’, of course there’s another way, by pasting a short code snippet into your functions.php file, but I’m not covering this here.

4 Answers
4

Typically I wouldn’t follow such a strong answer like the one @Milo provided with a post like this. Since I already had this code written for another project I thought I’d share.

The code below does everything @Milo summarized in his answer and I’ve ported this across projects before with great success.

Here’s the cheat sheet for what’s going on:

1) Hook into the ‘add_meta_boxes’ action to render a new custom meta box on the edit screen (except on the native ‘page’ post type).

2) Hook into the ‘admin_menu’ action to remove the existing ‘Page Attributes’ meta box (except on the native ‘page’ post type).

3) Build out the custom meta box to replace the functionality of the native ‘Page Attributes’ meta box. This includes fields for defining the PARENT, TEMPLATE and ORDER of any custom post type you’ve initialized.

4) Hook into the ‘save_post’ action to save your template selection in the post meta.

5) Hook into the ‘single_template’ filter to load your custom template instead of the default WordPress template.

Here’s is the fully functional copy/paste for you:

/** Custom Post Type Template Selector **/
function cpt_add_meta_boxes() {
    $post_types = get_post_types();
    foreach( $post_types as $ptype ) {
        if ( $ptype !== 'page') {
            add_meta_box( 'cpt-selector', 'Attributes', 'cpt_meta_box', $ptype, 'side', 'core' );
        }
    }
}
add_action( 'add_meta_boxes', 'cpt_add_meta_boxes' );

function cpt_remove_meta_boxes() {
    $post_types = get_post_types();
    foreach( $post_types as $ptype ) {
        if ( $ptype !== 'page') {
            remove_meta_box( 'pageparentdiv', $ptype, 'normal' );
        }
    }
}
add_action( 'admin_menu' , 'cpt_remove_meta_boxes' );

function cpt_meta_box( $post ) {
    $post_meta = get_post_meta( $post->ID );
    $templates = wp_get_theme()->get_page_templates();

    $post_type_object = get_post_type_object($post->post_type);
    if ( $post_type_object->hierarchical ) {
        $dropdown_args = array(
            'post_type'        => $post->post_type,
            'exclude_tree'     => $post->ID,
            'selected'         => $post->post_parent,
            'name'             => 'parent_id',
            'show_option_none' => __('(no parent)'),
            'sort_column'      => 'menu_order, post_title',
            'echo'             => 0,
        );

        $dropdown_args = apply_filters( 'page_attributes_dropdown_pages_args', $dropdown_args, $post );
        $pages = wp_dropdown_pages( $dropdown_args );

        if ( $pages ) { 
            echo "<p><strong>Parent</strong></p>";
            echo "<label class=\"screen-reader-text\" for=\"parent_id\">Parent</label>";
            echo $pages;
        }
    }

    // Template Selector
    echo "<p><strong>Template</strong></p>";
    echo "<select id=\"cpt-selector\" name=\"_wp_page_template\"><option value=\"default\">Default Template</option>";
    foreach ( $templates as $template_filename => $template_name ) {
        if ( $post->post_type == strstr( $template_filename, '-', true) ) {
            if ( isset($post_meta['_wp_page_template'][0]) && ($post_meta['_wp_page_template'][0] == $template_filename) ) {
                echo "<option value=\"$template_filename\" selected=\"selected\">$template_name</option>";
            } else {
                echo "<option value=\"$template_filename\">$template_name</option>";
            }
        }
    }
    echo "</select>";

    // Page order
    echo "<p><strong>Order</strong></p>";
    echo "<p><label class=\"screen-reader-text\" for=\"menu_order\">Order</label><input name=\"menu_order\" type=\"text\" size=\"4\" id=\"menu_order\" value=\"". esc_attr($post->menu_order) . "\" /></p>";
}

function save_cpt_template_meta_data( $post_id ) {

    if ( isset( $_REQUEST['_wp_page_template'] ) ) {
        update_post_meta( $post_id, '_wp_page_template', $_REQUEST['_wp_page_template'] );
    }
}
add_action( 'save_post' , 'save_cpt_template_meta_data' );

function custom_single_template($template) {
    global $post;

    $post_meta = ( $post ) ? get_post_meta( $post->ID ) : null;
    if ( isset($post_meta['_wp_page_template'][0]) && ( $post_meta['_wp_page_template'][0] != 'default' ) ) {
        $template = get_template_directory() . "https://wordpress.stackexchange.com/" . $post_meta['_wp_page_template'][0];
    }

    return $template;
}
add_filter( 'single_template', 'custom_single_template' );
/** END Custom Post Type Template Selector **/

The only assumption I’ve made here is that your templates follow a naming convention best practice of:

posttype-templatename.php

As an example you could define some custom templates for an “Event” custom post type using the following naming convention within your theme:

event-standard.php
event-allday.php
event-recurring.php

This code is smart enough to only allow “event” templates to be applied to the Event post type. In other words, a template called “section-video.php” template would never be visible to the Event post type. That template would instead appear as an option on the “Section” post type.

To remove this feature you simply need to remove the conditional logic from the code above:

if ( $post->post_type == strstr( $template_filename, '-', true) ) {  }

Leave a Comment