2

I have seen many questions/posts regarding this, but have yet to find a decent solution. Basically I am trying to do what wp_get_archives does, but for a custom post type (personally I am unsure why wp_get_archives doesn't support custom post types!).

The code I am currently using is as follows

functions.php

function Cpt_getarchives_where_filter( $where , $r ) {
  $post_type = 'events';
  return str_replace( "post_type = 'post'" , "post_type = '$post_type'" , $where );
}

sidebar-events.php

add_filter( 'getarchives_where' , 'Cpt_getarchives_where_filter' , 10 , 2 );
wp_get_archives();
remove_filter('getarchives_where' , 'Cpt_getarchives_where_filter' , 10 );

This code displays the dates (e.g. April 2014, March 2014) etc, which is great, but clicking the links just goes to a 404. The URL that is created on each date link is /2014/04/, however it should be something like /events/2014/04/.

Is there any way to include 'events' in the URL so that the archive-events.php template can be used, and is there any reason why the links currently generate a 404?

Many thanks for any help

  • The problem (and probably the reason why wp_get_archives does't work with CPTs) is that even if the url is something like /events/2014/04/ this url will bring to a 404, because doesn't exist any rewrite rule that handle that sort of url, in fact, it will be considered as the 4th page of an event with slug '2004'. So first you need to register the rewrite rule that handle the url, then you can filter 'month_link' to make return the url you want. – gmazzap Apr 30 '14 at 13:45
4

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})/?$', '/' );
  $month = ltrim( $base . $slug . '/([0-9]{4})/([0-9]{2})/?$', '/' );
  $day = ltrim( $base . $slug . '/([0-9]{4})/([0-9]{2})/([0-9]{2})/?$', '/' );
  $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+.

  • Thanks very much for this - just tried it out though and getting an error on the following line if ( empty( get_option( 'permalink_structure' ) ) || ! $pto->rewrite ) {. The error is "Can't use function return value in write context". Any ideas? Much appreciated – wickywills Apr 30 '14 at 15:17
  • Just sorted that particular error by putting get_option( 'permalink_structure' ) into a variable. The archive list now works, and the link structure appears correct, however each of the links go to 404 pages – wickywills Apr 30 '14 at 15:24
  • Ok, think I'm pretty close now - all the URLs look good, and I no longer receive 404 errors. The problem now is that each of the archive links I click on just links to the archive page and lists ALL the events (i.e. not the events specific to the date I selected) – wickywills Apr 30 '14 at 15:42
  • @wickywills yes, I updated the answer to remove first issue (it worked only on PHP 5.5+). For the other problem, I just tested code and it worked for me... What you get if var_dump($GLOBALS['wp_query']); in the page that displays archives? Is not the m query var set properly? – gmazzap Apr 30 '14 at 15:57
  • 1
    @wickywills Note that I've edited a little the code to fix an issue when a static front url is used and CPT is registered using 'with_front' => true that is the default. – gmazzap Apr 30 '14 at 16:34
0

I think G.M has given everything you need. I wrote a blog post on the permalink generation for CPT date archives, but haven't had the time to get the other pre_get_posts portion of it in a blog post form yet.

There's basically 3 pieces to the puzzle:

  1. Generating your rewrite rules to read the /2014/04/30, /2014/, etc. date strings.
  2. Getting your WP_Query to recognize the query vars.
  3. and creating the pagination/archive links for sidebar widgets (which my post covers).

protected by Community Nov 13 '17 at 11:38

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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