Redirect blog archive into certain format

My blog archive URLs were like:

http://example.com/2014/03

I changed my site’s permalink from /%postname%/ to /%postid%/%postname%/, and using Redirection plugin, I set a redirection like:
redirection 1

Read More

and it’s working. But the problem occurs in the archive URLs. The new archive URL generated with a /date/ basename before the dates, like:

http://example.com/date/2014/03

I’m trying to use the same Redirection plugin to redirect the URL, but failed:
redirection 2

I then tried writing .htaccess on my own (with no .htaccess writing experience) with:

Redirect permanent http://example.com/([0-9]+)/([0-9]+) http://example.com/date/([0-9]+)/([0-9]+)

with the help of htaccess editor and this blog. But failed too.

How can I let my blog not to get 404 on such archive URL? I’m afraid, I’m dumb with rewrite rule till now. 🙁

Related posts

Leave a Reply

1 comment

  1. 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 ), '/' );
      $full = trim( str_replace( $home_path, '', add_query_arg( array() ) ), '/' );
      $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( '/', $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;
    }