This is easy to do with built-in post types, but it requires extra work when a custom post type is in use.
If you use wp_get_archives() to get a date archive, it only gets them for the
"Posts" post type, and not for custom post types (CPTs). This article deals with
three things:
- How to apply rewrite rules so that CPTs have date archives.
- How to include a custom post type in the output of
wp_get_archives(). - How to make
wp_get_archives()output the right kind of URLs, such assite.com/cpt/2015/01.
Requirements
The custom post type needs to have 'has_archive' => true. More
here.
Making date archive for custom post types
Here are two functions that handle the rewrite rules for you. They go into
functions.php and are adapted from
here,
and I've improved it a bit, see the comments for explanation. Remember to change
your custom post type in $rules in the first function.
/**
* Custom post type specific rewrite rules
* @return wp_rewrite Rewrite rules handled by Wordpress
*/
function cpt_rewrite_rules($wp_rewrite)
{
// Here we're hardcoding the CPT in, article in this case
$rules = cpt_generate_date_archives('article', $wp_rewrite);
$wp_rewrite->rules = $rules + $wp_rewrite->rules;
return $wp_rewrite;
}
add_action('generate_rewrite_rules', 'cpt_rewrite_rules');
/**
* Generate date archive rewrite rules for a given custom post type
* @param string $cpt slug of the custom post type
* @return rules returns a set of rewrite rules for Wordpress to handle
*/
function cpt_generate_date_archives($cpt, $wp_rewrite)
{
$rules = array();
$post_type = get_post_type_object($cpt);
$slug_archive = $post_type->has_archive;
if ($slug_archive === false) {
return $rules;
}
if ($slug_archive === true) {
// Here's my edit to the original function, let's pick up
// custom slug from the post type object if user has
// specified one.
$slug_archive = $post_type->rewrite['slug'];
}
$dates = array(
array(
'rule' => "([0-9]{4})/([0-9]{1,2})/([0-9]{1,2})",
'vars' => array('year', 'monthnum', 'day')
),
array(
'rule' => "([0-9]{4})/([0-9]{1,2})",
'vars' => array('year', 'monthnum')
),
array(
'rule' => "([0-9]{4})",
'vars' => array('year')
)
);
foreach ($dates as $data) {
$query = 'index.php?post_type='.$cpt;
$rule = $slug_archive.'/'.$data['rule'];
$i = 1;
foreach ($data['vars'] as $var) {
$query.= '&'.$var.'='.$wp_rewrite->preg_index($i);
$i++;
}
$rules[$rule."/?$"] = $query;
$rules[$rule."/feed/(feed|rdf|rss|rss2|atom)/?$"] = $query."&feed=".$wp_rewrite->preg_index($i);
$rules[$rule."/(feed|rdf|rss|rss2|atom)/?$"] = $query."&feed=".$wp_rewrite->preg_index($i);
$rules[$rule."/page/([0-9]{1,})/?$"] = $query."&paged=".$wp_rewrite->preg_index($i);
}
return $rules;
}NOTE: You need to flush the permalinks after this. Go to Settings > Permalinks and click the "Save Changes" button.
Now you should be able to access monthly archives from
example.com/post-type-slug/2015/01 and so on. You'd use the
archive-{cpt-name}.php template to display the entries.
Make wp_get_archives() work with CPTs
Next, we need to get links to the archive pages, we can modify
wp_get_archives() to do that. Let's use the getarchives_where filter:
function example_getarchives_where($where)
{
return str_replace("WHERE post_type = 'post'", "WHERE post_type IN ('article')", $where);
}
add_filter('getarchives_where', 'example_getarchives_where');Note the second argument to str_replace. There we can specify which post types
will be included. To add more post types, the following gets Posts and Articles:
WHERE post_type IN ('post', 'article')See the getarchives_where source code
here,
that might open things up a bit.
Now wp_get_archives() outputs posts from the right post type, but the URLs it
emits are still wrong. It returns site.com/2015/01, but we want
site.com/cpt_slug/2015/01. One more function is needed.
Make wp_get_archives() give right URLs
This could be done right in a template also, but I prefer keeping templates
clean and abstracting all the functions in functions.php. The following is
just a wrapper around wp_get_archives(). It replaces site.com with
site.com/{cpturl}. See comments for more info:
/**
* Get archives to custom post type
* @param string $cpt The wanted custom post type
* @return array A list of links to date archives
*/
function cpt_wp_get_archives($cpt)
{
// Configure the output
$args = array(
'format' => 'custom',
'before' => '<li class="post-list__item post-list__item--archive">',
'after' => '</li>',
'echo' => 0,
'show_post_count' => true
);
// Get the post type objest
$post_type_obj = get_post_type_object($cpt);
// Slug might not be the cpt name, it might have custom slug, so get it
$post_type_slug = $post_type_obj->rewrite['slug'];
// Domain of the current site
$host = $_SERVER['HTTP_HOST'];
// Replace `domain.tld` with `domain.tdl/{cpt-slug}`
$output = str_replace($host, "$host/$post_type_slug", wp_get_archives($args));
return $output;
}Then use it:
echo cpt_wp_get_archives('custom-post-type-name');Conclusions
This was meant to be one of those quick things, but ended up being a wild journey deep into the gut of WP. And don't forget to flash your permalinks.