You would think that it would be as simple as adding add_post_type_support( 'cptslug', 'page-attributes' );
to your code, but it isn't. If you check the source you will see that the template dropdown is restricted to the page
post type only. There is also a note in the Codex and a reference to a closed Trac ticket on the topic.
You can add that box to your CPT by copying the Core function, making a small edit and adding a new box.
function my_cpt_attributes_meta_box($post) {
$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 ( ! empty($pages) ) {
?>
<p><strong><?php _e('Parent') ?></strong></p>
<label class="screen-reader-text" for="parent_id"><?php _e('Parent') ?></label>
<?php echo $pages; ?>
<?php
} // end empty pages check
} // end hierarchical check.
$template = get_post_meta($post->ID,'_wp_page_template',true);
?>
<p><strong><?php _e('Template') ?></strong></p>
<label class="screen-reader-text" for="page_template"><?php _e('Page Template') ?></label><select name="page_template" id="page_template">
<option value='default'><?php _e('Default Template'); ?></option>
<?php page_template_dropdown($template); ?>
</select>
<p><strong><?php _e('Order') ?></strong></p>
<p><label class="screen-reader-text" for="menu_order"><?php _e('Order') ?></label><input name="menu_order" type="text" size="4" id="menu_order" value="<?php echo esc_attr($post->menu_order) ?>" /></p>
<p><?php if ( 'page' == $post->post_type ) _e( 'Need help? Use the Help tab in the upper right of your screen.' ); ?></p>
<?php
}
function add_my_cpt_attributes_meta_box() {
add_meta_box(
'my_cpt_attributes_meta_box',
'My Box Name',
'my_cpt_attributes_meta_box',
'book',
'side',
'core'
);
}
add_action( 'add_meta_boxes', 'add_my_cpt_attributes_meta_box' );
If you test that code, you will notice that the box appears but the data doesn't save. Here is why.
We are going to have to save the data ourselves.
function my_save_cpt_template($post_ID,$post) {
$page_template = (isset($_POST['page_template'])) ? $_POST['page_template'] : false;
$page_templates = wp_get_theme()->get_page_templates();
// var_dump($page_template,$page_templates,'default' != $page_template && ! isset( $page_templates[ $page_template ] )); die;
if ( 'default' != $page_template && ! isset( $page_templates[ $page_template ] ) ) {
if ( $wp_error )
return new WP_Error('invalid_page_template', __('The page template is invalid.'));
else
return 0;
}
update_post_meta($post_ID, '_wp_page_template', $page_template);
}
add_action('save_post_cptslug','my_save_cpt_template',1,2);
Now it should save but the template doesn't load. Here is why. get_single_template
runs before get_page_template
, and get_page_template
is what looks for the specialized templates. So we need to load that template ourselves.
add_action(
'template_include',
function ($template) {
global $post;
if (is_singular() && isset($post->post_type) && 'book' === $post->post_type) {
$new = get_post_meta($post->ID,'_wp_page_template',true);
$new = locate_template($new);
if (!empty($new)) {
$template = $new;
}
}
return $template;
}
);
Barely tested. Possibly buggy. Caveat emptor. No refunds.