Redirecting deleted posts url to another WordPress install

This is a problematic tasks.

What I suggest you is run a one-time task that:

  1. get all the posts tagged ‘business-consultants’
  2. run a one-time task* to save in a php file an array of that posts slugs
  3. when a request is done on one of that url, redirect to new site url
  4. delete posts

I’ll write all the code as plugin.

I very strongly suggest you to take a full backup of your database before installing this plugin

Save following code in a file called RedirectDeleted.php, put in plugin folder, and activate it.

It will take same seconds before the page is fully loaded, but after that, if you go on your post list page, you should see all the post tagged ‘business-consultants’ deleted from WordPress, in addition in your uploads folder you should see a php file named redirect_deleted_business-consultants.php.

If ever, just visit an url like http://www.example.com/a-post-slug (where ‘a-post-slug’ is a real slug of a ost tagged ‘business-consultants’) and you should be redirected to http://www.example.com/business-consultants/a-post-slug.

Please note that if you ever delete redirect_deleted_business-consultants.php file or you unistall the plugin, the redirect will not work anymore.
Install the plugin as a must-use plugin can be an idea to prevent unistall.

The great thing is that you can move to its own WP install any other tag!

E.g. if you have a tag with slug ‘business-stuff’ and you want to move it to another WP install, just create this install at the url http://www.example.com/business-stuff and add ‘business-stuff’ to the plugin file in the $target_tags array (see below).

The code:

<?php
/**
 * Plugin Name: RedirectDeletedTag
 * Plugin URI: https://wordpress.stackexchange.com/questions/136018/
 */

class RedirectDeletedTag {

  /**
  * Adjust this variable according to real tag slug.
  * The key is the tag slug, and the value is the base url for the redirect.
  * If you want, you can add other tag slugs you want ot move to separate sites.
  */
  protected $target_tags = array(
    'business-consultants' => 'http://www.example.com/business-consultants/',
  );

  function cacheUrls() {
    if ( ! is_admin() || ! current_user_can( 'install_plugins' ) ) return;
    $t = new ArrayIterator( $this->target_tags );
    while( $t->valid() ) {
      $tag = $t->key();
      if ( $this->shouldCache( $tag ) ) $this->cacheTagUrls( $tag );
      $t->next();
    }
  }

  function redirect() {
    if ( is_admin() ) return;
    $url = $this->getUrl();
    if ( ! empty( $url ) ) {
      $t = new ArrayIterator( $this->target_tags );
      while( $t->valid() ) {
       $this->redirectTag( $t->key(), $url, $t->current() );
       $t->next();
     }
    }
  }

  protected function cacheTagUrls ( $tag ) {
    $args = array( 'post_type' => 'post', 'nopaging' => true, 'tag' => $tag );
    $query = new WP_Query( $args );
    if ( $query->have_posts() ) {
      $slugs = wp_list_pluck( $query->posts, 'post_name' );
      $id_list =  implode( ',',  wp_list_pluck( $query->posts, 'ID') );
      $cache="<?php return array("" . implode('","', $slugs ) . '");';
      if ( @file_put_contents( $this->getCachePath( $tag ), $cache ) !== FALSE ) {
        set_transient( __CLASS__ . $tag, '1' );
        // cache saved, delete all posts and term
        global $wpdb;
        $wpdb->query( "DELETE FROM $wpdb->posts WHERE ID IN ($id_list)" );
        $term = get_term_by('slug', $tag, 'post_tag');
        if ( ! empty( $term ) && isset( $term->term_id ) ) {
          wp_delete_term( $term->term_id, 'post_tag' );
        }
      }
    }
  }

  protected function redirectTag( $tag, $url, $base ) {
    if ( file_exists( $this->getCachePath( $tag ) ) ) {
      $cached = (array) include $this->getCachePath( $tag );
      if ( in_array( $url, $cached ) ) {
        wp_redirect( trailingslashit( $base ) . $url );
        exit();
      }
    }
  }

  protected function shouldCache( $tag = '' ) { 
    $should = FALSE;
    if ( is_admin() && current_user_can( 'install_plugins' ) ) {
      $transient = get_transient( __CLASS__ . $tag );
      $file = file_exists( $this->getCachePath( $tag ) );
      $should = empty($transient) && ! $file;
    }
    return $should;
  }

  protected function getCachePath( $tag = '' ) {
    $upload_dir = wp_upload_dir();
    $base = trailingslashit( $upload_dir['basedir'] );
    return  $base . 'redirect_deleted_' . $tag . '.php';
  }

  protected function getUrl() {
    $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/" );
    $stripped = explode( '?', $full );
    return trim( $stripped[0], "https://wordpress.stackexchange.com/" );
  }

}

$rd = new RedirectDeletedTag();
add_action( 'shutdown', array( $rd, 'cacheUrls' ) );
add_action( 'after_setup_theme', array( $rd, 'redirect' ) );

The plugin is available as Gist here.


* To run a one-time task I’ll use a transient plus a check if current user is an administrator, plus the existence of the cache file. If you want, take a look at this question/answers to know different approach to one-time tasks in WordPress.