Custom Post Type Archives Part 5

I certainly didn’t expect a part 5, but my monthly archive links suddenly stopped working! I hadn’t made any theme changes related to them so this was a most surprising problem.

First of all, I tried to find out which template file was getting called, but my debug statements in various files weren’t being called at all. Finally, a close look at the template hierarchy revealed that a 404 error goes straight to the 404 page, and there it was. This suggests that the query couldn’t find anything. Printing out the array of query variables made me realize that the URL segments were being mapped to the wrong variables.

This was most unexpected, and for some unknown reason, even reactivating the Custom Post Type Archives plugin didn’t work.

A lot of Googling revealed this discussion which suggests <domain>/2010/?post_type=cpt works. It was time to find out more about how URL rewriting works.

The Codex didn’t have much information about them, just some shallow user level instructions on what they look like and how to activate them. I did remember reading before that the rewriting was done by parsing the URL and giving everything to index.php to handle. Since I could get my custom post types to display with the right template file when using ugly URLs, getting the rewrite rules to work would be the key to the solution.

The code for rewrites is in wp-includes/rewrite.php, which is where the WP_Rewrite class is defined. Looking through the whole file gave me a better understanding of what’s happening. It seems that lots of rules are actually stored internally. These rules convert the pretty links into query variables which the rest of the WordPress framework can understand.

The function reference for WP_Rewrite explained the variables and the filters, which could be used to modify the rewrite rules. With this clue, I went through the source code of the plugin to find where it adds the rewrite rules. I then modified it to suit my own purposes.

The code is as follows.

function register_post_type_rewrite_rules($wp_rewrite) {

	$args = array('public' => true, '_builtin' => false);	//get all public custom post types
	$output = 'names';
	$operator = 'and';
	$post_types = get_post_types($args,$output,$operator);

	$url_base = ($url_base == '') ? $url_base : $url_base . '/';
	$custom_rules = array();

	$post_types = implode('|', $post_types);
		$custom_rules = array( "$url_base($post_types)/([0-9]+)/([0-9]{1,2})/([0-9]{1,2})/?$" =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3) . '&day=' . $wp_rewrite->preg_index(4),		//year month day

								"$url_base($post_types)/([0-9]+)/([0-9]{1,2})/?$"  =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2) . '&monthnum=' . $wp_rewrite->preg_index(3),			//year month
								"$url_base($post_types)/([0-9]+)/?$" =>
				'index.php?post_type_index=1&post_type=' . $wp_rewrite->preg_index(1) . '&year=' . $wp_rewrite->preg_index(2)	//year
							);

	$wp_rewrite->rules = array_merge($custom_rules, $wp_rewrite->rules); // merge existing rules with custom ones
	
	return $wp_rewrite;
}

add_filter('generate_rewrite_rules', 'register_post_type_rewrite_rules', 100);

First, get all public custom post types. This is explained in the function reference for get_post_types()

Next, add rewrite rules for year/month/day, year/month, and year URL permalinks to a custom array.

Finally, merge this array to the existing one and return the entire set of rules.

Once again, custom post type archives work like they’re supposed to. Phew!

Read about the next improvement to the custom post type archives widget at the Custom Post Type Archives jQuery Accordion post.

Advertisements

14 thoughts on “Custom Post Type Archives Part 5”

  1. Hi,

    I was really struggling with finding a solution for “post type” date archives. And then I stumbled on your series of posts dealing with custom post type archives. Very good information and helped me a lot. So thank you.

    I did however find a couple of errors in the above code I thought I would bring to your attention. First $url_base needs to be defined as ‘global’. Then in the first rewrite array entry, you have the ‘&’ code and it should be just the ‘&’ and the first instance of ‘$wp_rewrite’ also has the encoding for ‘>’ ($wp_rewrite-> s/b $wp_rewrite-> ).

    Once those corrections were make, the date archives for my post type work as expected.

    Thank you again.Michael

  2. Hi Michael,

    I’m glad my posts have helped you. Thanks for pointing out the mistakes left over from when I had to use HTML encoding. They’ve been fixed already. Defining $url_base as global doesn’t work for me though.

  3. Hi!

    First of all, thanks for sharing your code.
    I have some problems with it. I have a custom post type named “publications”, and I need a yearly archive with structure either:

    example.com/publications/2011

    or

    example.com/2011/publications

    I pasted your code in my functions.php file, but I think that it’s not been executed. May I be doing something wrong? I have to say that I’m not using the Custom Post Type Archives plugin, and I’ve coded the custom post type in my functions.php file.

    Thanks for your help!
    Jordi

    1. Hi Jordi,

      You should be able to get example.com/publications/2011 working without the plugin. Did you include ‘has_archive’ => true when registering the custom post type?

      You might have to flush rewrite rules by putting flush_rewrite_rules() in your theme’s functions.php. Take it out once it works though, it’s a very computationally intensive function.

  4. Hi, thanks for your reply 🙂

    I think I found my problem, but I don’t really know how to fix it. My Custom Post Type slug is “nanobio_publication” (as WordPress recommends, I use a namespace, that in my case is “nanobio”. What I do in my Custom Post Type register function is defining a rewrite slug “publications”. Here’s my code:

    $publications_args = array(
    ‘labels’ => $publications_labels,
    ‘public’ => true,
    ‘show_ui’ => true,
    ‘query_var’ => true,
    ‘show_in_menu’ => true,
    ‘menu_position’ => 5,
    ‘capability_type’ => ‘post’,
    ‘supports’ => array( ‘title’, ‘editor’, ‘excerpt’, ‘custom-fields’, ‘page-attributes’, ‘thumbnail’ ),
    ‘rewrite’ => array( ‘slug’ => ‘publications’, ‘with_front’ => false ),
    ‘has_archive’ => true
    );

    // Use namspace ‘nanobio’ to avoid conflicts with WP core vars
    register_post_type( ‘nanobio_publication’ , $publications_args );

    If I query it like example.com/nanobio_publication/2011, it works. But not with the “nice” url example.com/publications/2011

    Do you know how could I use the rewrite slug?

    Thanks for your help!

    1. I haven’t tried this, but from the codex, it looks like the has_archive argument can take a string too. Perhaps that might work?

      has_archive
      (boolean or string) (optional) Enables post type archives. Will use string as archive slug. Will generate the proper rewrite rules if rewrite is enabled.

  5. Yes, it’s strange… I had this line, that should generate the rewrite rule:

    ‘rewrite’ => array( ‘slug’ => ‘publications’, ‘with_front’ => false ),

    I specify a rewrite slug for the custom post type. I’ve tried to set has_archive to “publications” but without luck 😦

  6. Did you try flushing the rewrite rules after making those changes? If you’ve already done that, then I don’t have any more ideas. Try asking on the wp-hackers mailing list? Someone there might be able to provide more help.

  7. Hi,
    your explanation seems pretty promising to achieve a CPT archive list without using any plugin but i have been trying this for last 5 days and could not succeed. Here’s my case:
    – I have a CPT “News” with slug “newsitems” and has_archive => true
    – I have an archive template for this CPT called “archive-newsitems.php”
    – I have your archives filter code and rewrite rule code in my theme’s functions.php file
    – Using wp_get_archives(); function call to get archive list on the sidebar.php
    – I have two posts for newsitems CPT – one from MAY, 2010 and the other from OCTOBER, 2011

    OUTPUT:
    – I get archive list on my sidebar with only OCTOBER
    – And this OCTOBER links to a blank page, i guess it is linking to index.php because i don’t have anything in index.php

    So please any suggestion/help will be greatly appreciable.

    Thanks!

  8. Thanks a lot for the working solution!

    One question: so does works with this solution, if so how do I call out my monthly archives for my custom post type with . Or do you use something else?

    I assume you need to do something like this?

    ‘news’,
    ‘type’ => ‘monthly’,
    );
    wp_get_archives( $args ); ?>

    Thanks you.

    1. Thanks a lot for the working solution!

      One question: so does ‘wp_get_archives( $args );’ works with this solution, if so how do I call out my monthly archives for my custom post type with ‘wp_get_archives( $args );’. Or do you use something else?

      Thanks you.

  9. For all having a pagination issue, change the regex:

    $post_types = implode(‘|’, $post_types);
    $custom_rules = array( “$url_base($post_types)/([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})/?$” =>
    ‘index.php?post_type_index=1&post_type=’ . $wp_rewrite->preg_index(1) . ‘&year=’ . $wp_rewrite->preg_index(2) . ‘&monthnum=’ . $wp_rewrite->preg_index(3) . ‘&day=’ . $wp_rewrite->preg_index(4), //year month day

    “$url_base($post_types)/([0-9]{4})/([0-9]{1,2})/?$” =>
    ‘index.php?post_type_index=1&post_type=’ . $wp_rewrite->preg_index(1) . ‘&year=’ . $wp_rewrite->preg_index(2) . ‘&monthnum=’ . $wp_rewrite->preg_index(3), //year month
    “$url_base($post_types)/([0-9]{4})” =>
    ‘index.php?post_type_index=1&post_type=’ . $wp_rewrite->preg_index(1) . ‘&year=’ . $wp_rewrite->preg_index(2) //year
    );

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s