Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I have registered a custom post type and created a file called archive-myCPT.php and another one for single posts called single-myCPT.php.

What I want to do is to create a page where I will show just few posts from my custom post type. Inside my archive-myCPT.php I will have a link called let's say "Special Posts" and this link will the the custom page which will be a little bit different from archive-myCPT.php, the only difference is that here I will use a custom query for the posts.

The only way to do this seems to create a page template, but the problem here is that I can not assign a page template to pages within my CPT. I saw a plugin which may help me with the page template support for CPT, but is pretty old and I don't know if it's compatible with the latest version of WP.

Is this the only way to do this ? Is this a good practice? Do you recommend something else ?

share|improve this question
add comment

2 Answers

up vote 1 down vote accepted

If you're adding a custom query within the template to load posts, the post type of that page doesn't have to be your custom post type. You can create a plain vanilla page, create a template for that via page-your_page_slug.php, or via a custom template assigned in the templates dropdown, then query for whatever post type you want via WP_Query.

EDIT - here's an example using the single_template filter. you'll have to change the post type and slug to match yours.

function wpa_single_cpt_template( $templates = '' ){
    $single = get_queried_object();

    if( 'myCPT' == $single->post_type
        && 'my-special-slug' == $single->post_name )
        $templates = locate_template( 'my-special-template.php', false );

    return $templates;
}
add_filter( 'single_template', 'wpa_single_cpt_template' );

adapted from the example on Template Hierarchy: Filter Hierarchy.

share|improve this answer
    
I was thinking about this too, but what about the PERMALINK structure ? It will look like this : mywebsite.com/mycustompage and I want to see it like this mywebsite.com/myCPT/mycustompage –  Alecs Dec 23 '13 at 15:04
1  
You could add an extra rewrite rule to catch that specific path if you want it to be a child of your cpt slug, or you can add a filter to single template to load it without having to manually choose the template. I can give you code for either of those options as soon as i'm not on my mobile. –  Milo Dec 23 '13 at 15:14
    
I like the idea to add a filter to a single template to load it without having to manually choose the template. I would love to see an example. Thank you ! –  Alecs Dec 23 '13 at 15:28
    
It is possible to call wp_get_archives and retrieve the posts within the custom post type ? –  Alecs Dec 23 '13 at 16:01
1  
see edit above. wp_get_archives doesn't work with custom types without filtering the query and adding rewrite rules to handle the incoming requests. you can find questions/answers here about how to do that if you search around a bit. custom post types weren't intended for use as date-based posts the way the built in post post type is. –  Milo Dec 23 '13 at 19:11
show 2 more comments

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.

share|improve this answer
    
Thank you for your detailed answer, seems a lot of work to add a custom page within a CPT. –  Alecs Dec 23 '13 at 15:31
    
... a custom template, yes. That it is this hard is the result of one of the more puzzling decisions in WordPress development. –  s_ha_dum Dec 23 '13 at 15:33
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.