Search code examples
phpwordpresshttp-redirectpantheon

Redirecting a few thousand URLs using PHP


I am migrating a site which will require about 5000 redirects, in a format such as

http://www.acme.org/content/item.aspx?id=123 redirects to http://www.acme.org/resources/articles/why-acme-is-great

Normally I would accomplish this through .htaccess or an nginx module. However I'm on a WordPress specific host, Pantheon, which does not allow access to that.

Therefore the only solution I could think of is to use PHP. The following is working. There's some WordPress specific code in there to prevent WordPress from just throwing a 404.

add_filter('template_redirect', 'my_404_override');
function my_404_override()
{
  global $wp_query;
  if (strpos($_SERVER['REQUEST_URI'], 'issues') !== false) {
    $redirectURL = "/resources";
    if (strpos($_SERVER['REQUEST_URI'], '123') !== false) {
      $redirectURL .= "/articles/why-acme-is-great/";
    }
  }

  if (!empty($redirectURL)) {
    status_header(200);
    $wp_query->is_404 = false;
    header('HTTP/1.0 301 Moved Permanently');
    header('Location: http://www.acme.org' . $redirectURL);
  }
}

This works fine. However I have two concerns:

  1. With a list of 5000, what kind of impact will this have on performance? I'm planning on using some larger conditionals and then narrowing down (in the example above, I first check for /resources before looking at specific IDs.
  2. While this list will in theory never need to be modified, it feels like an ugly solution both in terms of syntax and logic. Is there a better method I'm not thinking of?

Solution

  • Your problem is that you will need to make a map of old content to new. Adding a custom field to each post content to the effect of "old_url=123", then doing a wp_query for the post-slug. I would assume your old ID's (ie 123) wouldn't necessarily match up with new ones. The approach of adding conditionals for every possible URL is unfeasible and difficult to maintain.

    When you add a field for each new post / page that has "old content" then your code can be something like this:

    add_filter('template_redirect', 'my_404_override');
    function my_404_override()
    {
       global $wp_query;
       $old_url = $_SERVER['REQUEST_URI'];
       $results = $wpdb->get_results(
            $wpdb->prepare( "SELECT wp_posts.guid, redirect_url FROM wp_posts LEFT JOIN wp_post_meta ON wp_posts.ID = wp_post_meta.post_id WHERE wp_post_meta.old_url = %s LIMIT 1", $old_url ));
         $redirectURL = $results[0]['guid'];
      }
    
    if (!empty($redirectURL)) {
        status_header(200);
        $wp_query->is_404 = false;
        header('HTTP/1.0 301 Moved Permanently');
        header('Location: ' . $redirectURL);
        }
    }
    

    This is pseudo code; but the general idea is that you query one row no matter what; and the impact on performance is negligible, as only in the case of 404, you check to find if a redirect exists and that query returns only one row.

    There are problems to this approach, namely if someone enters for example the same number on two posts, there is no method for prioritizing which is the most important redirect. You might also consider to use a plugin to solve this problem.

    https://wordpress.org/plugins/redirection/