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:
- the url contain 2 pieces and both are numeric, first of 4 digits, second is between 1 and 12
- 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
- Get the url
- Explode the url pieces and count them: if count isn’t 1 or 2 do nothing
- 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;
}