4

Updates 6 Feb 14 to make more specific as requested in comments.

I have a post type, we'll call it "Projects", with the slug "projects" for which I'd like to offer two views:

  1. list view (default)
  2. map view

I'd like those views to be at the following permalinks:

  1. example.org/projects/ (default post type archive page, archive-projects.php)
  2. example.org/projects/map/

How do I do handle instructing WordPress to use a specific template when accessing that one specific URL that would otherwise imply a specific Projects post? It would be nice if that redirect managed to keep the default post type archive query, but I can write a secondary loop in the template if necessary. I had initially thought endpoints were the answer, but I'm pretty sure that's not true.


Original Post

I have a post type—"Projects"—for which I'd like two views of its post type archive. The first is a pretty-straight forward list of Projects sorted by category and the second is a mapped view of the projects. Making templates for those two views is not a problem.

How can I make these two different views of the Post Type Archive, each with a unique URL?

Ideally I'd like the first template to be the default at /projects/ and the second view at /projects/map/.

It seemed like a custom endpoint was the way to go, but this comment on an old question with a title that perfectly describes what I'm looking for suggests that there is no way to have an end point for only a custom post type archive.

Should I use a custom rewrite, then, or should I even consider going with a more jQuery driven solution?

  • 2
    your question doesn't have a correct answer, it's an opinion. – Milo Jan 21 '14 at 16:44
  • 1
    @Milo I suppose I'm trying to understand if there's a canonical technique for this situation. I thought endpoints were the answer, but that seems to not be the case (maybe?). How could I make the question more specific and less open to opinion-driven answers? – mrwweb Jan 21 '14 at 17:53
5
+150

Updated approach

The first thought/suggestion I made actually - like you said - doesn't work as I understood it. At least I tried it and couldn't figure it out. That said, what you want is still achievable, but not by using a endpoint or at least not by making use of add_rewrite_endpoint().

However I figured it should be possible via a "ordinary" rewrite setup. Below code shows how to setup a post type, a query variable and a rewrite rule, to make a custom view for the post type archive possible. Last but not least we need to make sure to load the correct template. This it is possible to kind of emulate an endpoint. The exemplary code should get you in the right direction, so you can adapt it to your needs.

Code:

// minimal setup to register the new post type
add_action( 'init', 'wpse130664_pt_rw_ep_cpt');
function wpse130664_pt_rw_ep_cpt() { 
    register_post_type( 'rw_ep_cpt', 
        array(
            'label' => __( 'rw_ep_cpt', 'rw_ep_cpt_textdomain' ),
            'public' => true,
            // the archive slug defaults to the post type name »rw_ep_cpt«
            'has_archive' => true
        )
    );  
} 

// add a new query variable
add_filter('init', 'wpse130664_pt_archive_qv');
function wpse130664_pt_archive_qv() {
    global $wp;
    $wp->add_query_var('aview');
}

// add a new rewrite rule
add_filter('init', 'wpse130664_pt_archive_rw_rule');
function wpse130664_pt_archive_rw_rule() {
    // we're adding »aview« as a possibility to the slug 
    // of the custom post type archive we registered above
    // no need to match anything, e.g. with »$matches[1]«
    // so we're defaulting the aview query variable to 1
    // this kind of emulates a endpoint
    add_rewrite_rule( "rw_ep_cpt/aview$", "index.php?post_type=rw_ep_cpt" . '&aview=1', 'top' );
}

// load the »aview« template
add_filter( 'template_include', 'wpse130664_aview_tmpl_red' );
function wpse130664_aview_tmpl_red( $original_template ) {
    // we're loading the template conditionally, 
    // but only if we're actually at the »aview« "endpoint"
    if ( 1 == intval(get_query_var('aview')) ) {
        // you've to create the template you want to use here
        return get_template_directory().'/archive-aview.php';
    } else {
        return $original_template;
    }
}


Answer to comment

In the mean time, I'm curious why you chose to use the methods on the $wp global rather than the functions get_query_var() and add_query_var()?

To be honest I reused some code I've written some time ago, I only adapted it a bit to make it fit to your needs. There I used the above approach, with the $wp global. But that of course is no explanation for the why, so I try to address that too.

If I'm not totally absolutely mistaken add_query_var() is only available as function/method of the WP class. So the above way is just the way to do it.

Regarding get_query_var(), this one you could use instead, but it globalizes $wp_query and uses the get() function/method of the WP_Query class. So in a way the difference isn't or shouldn't be a big one.

Besides that, one more thought, the WP_Query object is likely to be bigger than the WP object, if not always. So doing it via global $wp might actually be a good thing.
But actually I didn't thought and tested this trough, I've used it like this and it is working in my setup, so I was satisfied when I did that code. So there might be a drawback in another setup/use case. Or doing it another way might be more valuable/beneficial in other cases. But like I said above, this is to get you started, you have to adapt it to your needs yourself.

Update after comment

A well-timed blog post from @MarkJaquith turned me on to the template_include filter which is better-suited for including a new template file. I've updated my code to use that filter and I'd recommend updating the example code too.

As @mrwweb pointed out and above linked article explains it, the template_redirect filter isn't optimal for choosing a different template. Or as @MarkJaquith simply concludes:

→ template_redirect is for redirects.
→ template_include is for includes.

He convinced me, so I changed the way the »aview« template loads to the template_include filter in above code.




First thought/approach


Disclaimer:
I'm keeping this, because of it's informational value, but this won't work for the custom post type archive. However using ep_mask comes in handy for creating endpoints for the single custom post type posts.


From what I understand, endpoints can be done in such cases. I recently read into that, but never actually had to approach doing it. So this is to the best of my knowledge, maybe someone with a lot more of it will come by and clear things up.

You're right with what you said:

My understanding is that endpoints do not work for post type archives. They're intended for posts within that post type but not the archive itself.


So making use of the ep_mask parameter like described below can be used for the posts of a custom post type, but not for the archive. Beside that, playing with this made me realize how handy a custom ep_mask can be for the single custom post type endpoint creation.

Basically what I read pinnacled into making use of the ep_mask argument you've available when using

and

rewrite argument.

Take a look at the related trac ticket 19275. You can find additional possibly helpful information at the register_post_type() codex page at the permalink_epmask description, especially trac ticket 12605.

As for an overview about endpoints:

Here is an answer on:

for categories there already is EP_CATEGORIES of course, but it shows that it really should be possible, if you make use of the ep_mask argument.

  • I'm hoping the update dealt with the opinion issue. I'll be happy with any answer. My understanding is that endpoints do not work for post type archives. They're intended for posts within that post type but not the archive itself. – mrwweb Feb 6 '14 at 21:41
  • This looks awesome! Thanks. I'll come back and report on success/failure ASAP. In the mean time, I'm curious why you chose to use the methods on the $wp global rather than the functions get_query_var() and add_query_var()? – mrwweb Feb 10 '14 at 17:01
  • Suuuuuuuuuper close. Tweaked everything and got it working. However, when using the pretty permalink (projects/map) I get 404 headers, no posts, no queried_object, etc. and NOTICE: C:\xampp\htdocs\eworks\wp-includes\post-template.php:29 - Trying to get property of non-object (there's no ID set). When going to ?post_type=project&map=1, I don't get that problem. It seems that WordPress doesn't realize this should be a post type archive still. Not sure how to handle that. I could easily write a custom query in the template but that would leave the underlying issue in place. – mrwweb Feb 12 '14 at 18:14
  • Hmmmmmm. The crux of the issue is that $wp_query doesn't get correctly set with permalinks (though it's partly right) and I have no idea why (var_dumps of $wp_query in both situations, and my adapted code). – mrwweb Feb 12 '14 at 22:33
  • 1
    A well-timed blog post from @MarkJaquith turned me on to the template_include filter which is better-suited for including a new template file. I've updated my code to use that filter and I'd recommend updating the example code too. – mrwweb Feb 19 '14 at 18:53
1

Update 03/12/17

The plugin mentioned below is abandoned, what I currently maintain is another package, Cortex


Consider my answer just as an alternative.

I developed a plugin, Clever Rules, that allows to specify a route, a query and (among other things) a specific template.

What you need using Clever Rules is register a rule, but please note that with current version you can register rules only in plugins, not in themes.

Download Clever Rules, activate it. Now you need a plugin to register your rules: you can create a new plugin, or insert the following code in a plugin you are developing...

add_action('plugins_loaded', 'register_my_rules');

function register_my_rules() {

    // next line ensure function runs only if Clever Rules is installed
    if ( ! function_exists('register_clever_rule') ) return;

    $args = array( 'route' => '/projects/map/', 'query' => 'post_type=projects' );

    register_clever_rule( $args )->template('your-specific-template.php');

}

This 3 lines of code is the only thing you need. If you created a new plugin for that, activate it, of course.

No one of the default urls will be affected.

'your-specific-template.php' can be a template file in parent or even child theme. Of course you can customize the query just like you want and use other plugin features.

And if you like the system, you can use the same plugi to register all the rules you need.

Notes

I am developing a new version of that plugin, that will be a bit more performant (when there are a lot of rules, only one is not a problem), allow rules registration in themes, has a little different API and some additional features. It will be public released soon, however, the current version works too.

  • Oh no @gmazzap - this Clever Rules URL is dead :( – Howdy_McGee Dec 1 '17 at 20:29
  • @Howdy_McGee thanks for mentioning. Clever Rules is dead as well, update the URL to point to Git URL that is still there. Also update the answer. – gmazzap Dec 3 '17 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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