Redirect blog archive into certain format

I’m working on a plugin that handle things like that. While it’s in development I want to share some code derived form my plugin that can help you, I hope.

If you look at your sources urls, they are easily to be recognize: you have 2 cases in which you need to redirect:

  1. the url contain 2 pieces and both are numeric, first of 4 digits, second is between 1 and 12
  2. the url contain one piece and it is a valid post slug

Looking at pieces count is very easy, is a matter of a count() and a explode(), looking at valid post slug needs to be queried in database.

Workflow

  1. Get the url
  2. Explode the url pieces and count them: if count isn’t 1 or 2 do nothing
  3. Checking url pieces and apply redirect if needed:
    • if count is 1 check if it a valid post slug and if so redirect
    • if count is 2, check if pieces are like /%year%/%monthnum% and if so redirect

The number #3 can be improved in performance using transient-based cache.

1. Get the url

Let’s write a function that get the url pieces. It must strips the home_url() part from urls.

function get_url_pieces() {
  $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), "https://wordpress.stackexchange.com/" );
  $full = trim( str_replace( $home_path, '', add_query_arg( array() ) ), "https://wordpress.stackexchange.com/" );
  $sane_array = explode( '?', $full );
  // removing any query string
  $qs = array();
  if ( isset( $sane_array[ 1 ] ) ) parse_str( $sane_array[ 1 ], $qs );
  $stripped = trim( $sane_array[ 0 ], '/\\' );
  $pieces = ! empty( $stripped ) ? array_filter( explode( "https://wordpress.stackexchange.com/", $stripped ) ) : array();
  return $pieces;
}

2. Explode the url pieces and count them: if count isn’t 1 or 2 do nothing

This part of workflow should be run as easy as possible, 'after_setup_theme' is a good place, because is available for both plugins and url and is pretty early.

add_action( 'after_setup_theme', function() {
  if ( is_admin() ) return;
  $url_pieces = get_url_pieces();
  if ( is_array($url_pieces) ) {
    if ( count( $url_pieces ) === 1 ) {
      my_post_redirect( $url_pieces );
    } elseif ( in_array( count($url_pieces), array(2, 3), TRUE ) ) {
      my_date_redirect( $url_pieces );
    }
  }
} );

3. Checking url pieces and apply redirect if needed

First function for 2 pieces urls

function my_date_redirect( Array $pieces ) {
  $cached = redirect_if_cached( $pieces );
  // if we are here url is not cached, see redirect_if_cached() below
  // let's check date format
  if ( ! in_array( count($pieces), array( 2, 3), TRUE ) ) return;
  if ( ! strlen("{$pieces[0]}") === 4 ) return;
  $day = ! isset( $pieces[2] ) ? '01' : $pieces[2];
  if ( ! checkdate ( (int)$pieces[1], (int) $day, (int) $pieces[0] ) ) return;
  // that's a valid date
  // cache and redirect
  $redirect = "date/{$pieces[0]}/{$pieces[1]}";
  if ( isset($pieces[2]) ) $redirect .= "/{$pieces[2]}";
  $cached[ serialize($pieces) ] = $redirect;
  set_transient('my_redirects', $cached);
  wp_safe_redirect( home_url( $redirect ), 301 );
  exit();
}

Second function for 1 pieces urls

function my_post_redirect( Array $pieces ) {
  $cached = redirect_if_cached( $pieces );
  // if we are here url is not cached, see redirect_if_cached() below
  if ( ! count( $pieces ) === 1 ) return;
  global $wpdb;
  $query =  "SELECT ID, post_name FROM {$wpdb->posts} WHERE (post_status="publish"";
  $query .= ( is_user_logged_in() ) ? " OR post_status="private")" : ')';
  $query .= " AND post_type="post" AND post_name = %s";
  $post = $wpdb->get_row( $wpdb->prepare( $query, $pieces[0] ) );
  if ( empty($post) ) return;
  // that's a valid slug
  // cache and redirect
  $redirect = "{$post->ID}/{$pieces[0]}";
  $cached[ serialize( $pieces ) ] = $redirect;
  set_transient( 'my_redirects', $cached );
  wp_safe_redirect( home_url( $redirect ), 301 );
  exit();
}

Implementing cache

function redirect_if_cached ( Array $pieces ) {
  $cached = array_filter( (array) get_transient('my_redirects') );
  $key = serialize( $pieces );
  if ( array_key_exists( $key, $cached ) ) {
    wp_safe_redirect( home_url( $cached[$key] ), 301 );
    exit();
  }
  // if url is not cached return what's currently cached
  return $cached;
}