Date archives for custom post type

What you want can be done also filtering 'month_link' for monthly archives, 'year_link' for yearly archives and 'day_link' for daily archives.

You can also write a function that extends wp_get_archives to work with CPTs, adding filters to 'getarchives_where' and to the archive link.

function wp_get_cpt_archives( $cpt="post", $args = array() ) {
  // if cpt is post run the core get archives
  if ( $cpt === 'post' ) return wp_get_archives($args); 
  // if cpt doesn't exists return error
  if ( ! post_type_exists($cpt) ) return new WP_Error('invalid-post-type');
  $pto = get_post_type_object($cpt);
  // if cpt doesn't have archive return error
  if ( ! $pto = get_post_type_object( $cpt ) ) return false;
  $types = array('monthly' => 'month', 'daily' => 'day', 'yearly' => 'year');
  $type = isset( $args['type'] ) ?  $args['type'] : 'monthly';
  if ( ! array_key_exists($type, $types) ) {
    // supporting only 'monthly', 'daily' and 'yearly' archives
     return FALSE;
  }
  $done_where = $done_link = FALSE;
  // filter where
  add_filter( 'getarchives_where' , function( $where ) use (&$done_where, $cpt) {
    if ( $done_where ) return $where;
    return str_replace( "post_type="post"" , "post_type="{$cpt}"" , $where );
  });
  // filter link
  add_filter( "{$types[$type]}_link", function( $url ) use (&$done_link, $pto, $cpt) {
    if ( $done_link ) return $url;
     // if no pretty permalink add post_type url var
     // if ( get_option( 'permalink_structure' ) || ! $pto->rewrite ) {
     if ( ! get_option( 'permalink_structure' ) || ! $pto->rewrite ) {
       return add_query_arg( array( 'post_type' => $cpt ), $url );
     } else { // pretty permalink
       global $wp_rewrite;
       $slug = is_array( $pto->rewrite ) ? $pto->rewrite['slug'] : $cpt;
       $base = $pto->rewrite['with_front'] ? $wp_rewrite->front : $wp_rewrite->root;
       $home = untrailingslashit( home_url( $base ) );
       return str_replace( $home,  home_url( $base . $slug ), $url );
     }
  });
  // get original echo arg and then set echo to false  
  $notecho = isset($args['echo']) && empty($args['echo']);
  $args['echo'] = FALSE; 
  // get archives
  $archives = wp_get_archives($args);
  // prevent filter running again
  $done_where = $done_link = TRUE;
  // echo or return archives
  if ( $notecho ) {
    return $archives;
  } else {
    echo $archives;
  }
}

Please read inline comments for more info on how function works.

Now there is a problem. The url like /events/2014/04/ are not recognized by WordPress, so you need to add the reqrite rules that handle that sort of urls.

Again you can write a function that add that rules for you:

function generate_cpt_archive_rules( $cpt ) {
  if ( empty($cpt) || ! post_type_exists($cpt) ) return;
  global $wp_rewrite;
  $pto = get_post_type_object($cpt);
  if ( ! $pto->has_archive ) return;
  $base = $pto->rewrite['with_front'] ? $wp_rewrite->front : $wp_rewrite->root;
  $base = trailingslashit( $base );
  $slug = is_array( $pto->rewrite ) ? $pto->rewrite['slug'] : $cpt;
  $year = ltrim( $base . $slug . '/([0-9]{4})/?$', "https://wordpress.stackexchange.com/" );
  $month = ltrim( $base . $slug . '/([0-9]{4})/([0-9]{2})/?$', "https://wordpress.stackexchange.com/" );
  $day = ltrim( $base . $slug . '/([0-9]{4})/([0-9]{2})/([0-9]{2})/?$', "https://wordpress.stackexchange.com/" );
  $index = 'index.php?post_type=" . $cpt;
  $rules[$year] =  $index . "&m=$matches[1]';
  $rules[$month] = $index . '&m=$matches[1]$matches[2]';
  $rules[$day] = $index . '&m=$matches[1]$matches[2]$matches[3]';
  $page = 2;
  foreach ( array( $year, $month, $day ) as $rule ) {
    $paged = str_replace( '/?$', '/page/([0-9]{1,})/?$', $rule);
    $rules[$paged] = $rules[$rule] . '&paged=$matches[' . $page . ']';
    $page++;
  }
  return $rules;
}

This function generate the rules, however, you also need to add and flush them.

Now is recommend to flush rules on theme (or plugin) activation and register them on init, so you should do something like:

function register_cpt_dates_rules() {
  $cpts = array( 'events' );
  foreach ( $cpts as $cpt ) {
    foreach ( generate_cpt_archive_rules( $cpt ) as $rule => $rewrite ) {
      add_rewrite_rule( $rule, $rewrite, 'top' );
    }
  }
}
// flushing on theme switch
add_action('after_switch_theme', function() {
  register_cpt_dates_rules();
  $GLOBALS['wp_rewrite']->flush_rules();
});
// registering on init
add_action( 'init', 'register_cpt_dates_rules' );

Once you have added these functions to your functions.php (and you have deactivated and activated again your theme to make 'after_switch_theme' run) you can display the archives using:

wp_get_cpt_archives( 'events' );

And it will display the archives for event CPT, and the link will be something /events/2014/04/.

It works also for yearly or daily archives:

wp_get_cpt_archives( 'events', array( 'type'=>'yearly' ) );
wp_get_cpt_archives( 'events', array( 'type'=>'daily' ) );

Using my two functions you have a super-easy way to make wp_get_archives works with CPTs.

Note that all the code here require PHP 5.3+.

Leave a Comment