Occasionally you run across a situation where a website permalink structure needs to be changed from what it has been to a new structure. Most sites that have been public will have links to them from other websites that need to properly redirect to the new permalink structure. Removing an element from the permalink structure and redirecting old links is trivial, however adding an additional element to the url structure can be difficult. For example:
A fairly typical structure is: /%year%/%month%/%day%/%postname%/
Lets use a simple example of adding the category to the permalink structure: /%category%/%year%/%month%/%day%/%postname%/
To provide a more useful example:
Original: https://ben.lobaugh.net/2016/05/15/cruising-the-san-juans
With category: https://ben.lobaugh.net/sailing/2016/05/15/cruising-the-san-juans
The original url is going to display the 404 page. I am going to provide an example that you can use in your own site to capture the url causing the 404 and attempt to locate the new post permalink. If none can be found it will fallback to the 404 page. As long as there is something in the original url you can use to locate a post (like the %postname%) this method will work with minor tweaks.
add_action( 'template_redirect', 'maybe_redirect_404_old_permalink' );
/**
* Attempts to forward old permalinks to the new permalink structure
*
* @author Ben Lobaugh
*/
function maybe_redirect_404_old_permalink() {
// Only run this function if we are on a 404
if( ! is_404() ) {
return;
}
// "trick" to get the full URL
$url = add_query_arg( '', '' );
/*
* Pull the URL path apart to find a slug (post_name)
* The final segment should be the slug
*/
$parts = explode( '/', $url );
$parts = array_filter( $parts );
$size = count( $parts );
$maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based
// Attempt to locate corresponding post in the database
$args = array(
'name' => $maybe_slug,
'post_type' => 'post',
'post_status' => 'publish',
'numberposts' => 1,
);
$posts = get_posts( $args );
// Identify a found post
if( $posts && ! empty( $posts[0]->ID ) ) {
$post_id = $posts[0]->ID;
$post_url = get_permalink( $post_id );
// Attempt to forward to the new post permalink
if( $post_url ) {
wp_safe_redirect( $post_url, 301 ); // Permanent redirect
}
}
/*
* If we made it down here then we could not find a matching post in
* the database. No biggie, simply do nothing and display the 404 page
* as normal :)
*/
}
This is exactly the use case I was searchin for. However after thinking this through I came to the conclussion to not make this redirects. I simply keep my old permalink structure, and won’t add the category. Since this approach will put performance at risk.
EXACTLY WHAT I NEEDED! Thanks a lot for this amazing piece of code. I think this should be an essential to have in wordpress. I was worried that changing urls might hurt my sites SEO but this saved me!
Spot on thank you 🙂
This is awesome – works like a charm. Thank you!
Sorry but not following completely, where do I add this code?
Excellent. Thank you!
This system for URL’s is also helpful for setting up conditional calls to action within the blog. For example, “Category1”-related offer appears only on url’s containing “Category1”
🙂
This has been working fine for us until we enabled w3 total cache plugin. Any idea what I can look for in the w3 total cache plugin to see what this stopped working?
Doesn’t work with W3 Total Cache.
Hi,
Thanks for the code.. I think it does not work really with get_posts so I changed it to use WP_Query
add_action( ‘template_redirect’, ‘maybe_redirect_404_old_permalink’ );
/**
* Attempts to forward old permalinks to the new permalink structure
*
* @author Ben Lobaugh
*/
function maybe_redirect_404_old_permalink() {
// Only run this function if we are on a 404
if( ! is_404() ) {
return;
}
// “trick” to get the full URL
$url = add_query_arg( ”, ” );
/*
* Pull the URL path apart to find a slug (post_name)
* The final segment should be the slug
*/
$parts = explode( ‘/’, $url );
$parts = array_filter( $parts );
$size = count( $parts );
$maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based
// Attempt to locate corresponding post in the database
$args = array(
‘name’ => $maybe_slug,
‘post_type’ => ‘post’,
‘post_status’ => ‘publish’,
‘numberposts’ => 1,
);
$loop = new WP_Query( $args );
while ( $loop->have_posts() ) : $loop->the_post();
$post_url = get_permalink();
if( $post_url ) {
wp_safe_redirect( $post_url, 301 ); // Permanent redirect
exit;
}
endwhile;
wp_reset_postdata();
}
Regards
Oh my goodness! Impressive article dude! Thank you so much, However I am experiencing troubles with your RSS. I don’t understand the reason why I cannot join it. Is there anybody getting the same RSS issues? Anyone who knows the answer will you kindly respond? Thanx!!
Hi! Great function, many thanks!
I also upgraded it to work with GET params like ?ab=cd (otherwise it doesn’t work)
function maybe_redirect_404_old_permalink() {
// Only run this function if we are on a 404
if( ! is_404() ) {
return;
}
// “trick” to get the full URL
$url = add_query_arg( ”, ” );
$regex = “#\?(.*?)$#smi”;
$params = “”;
if(preg_match($regex, $url, $match)) {
$params = $match[1];
$url = preg_replace($regex, ”, $url);
}
/*
* Pull the URL path apart to find a slug (post_name)
* The final segment should be the slug
*/
$parts = explode( ‘/’, $url );
$parts = array_filter( $parts );
$size = count( $parts );
$maybe_slug = $parts[ $size ]; // We use size here because the filter turned 1 based
// Attempt to locate corresponding post in the database
$args = array(
‘name’ => $maybe_slug,
‘post_type’ => ‘post’,
‘post_status’ => ‘publish’,
‘numberposts’ => 1,
);
$posts = get_posts( $args );
// Identify a found post
if( $posts && ! empty( $posts[0]->ID ) ) {
$post_id = $posts[0]->ID;
$post_url = get_permalink( $post_id );
// Attempt to forward to the new post permalink
if( $post_url ) {
if(!empty($params)) {
$post_url .= “?” . $params;
}
wp_safe_redirect( $post_url, 301 ); // Permanent redirect
}
}
/*
* If we made it down here then we could not find a matching post in
* the database. No biggie, simply do nothing and display the 404 page
* as normal
*/
}
Hello,
Where do I add this code please?