WordPress has a built-in function called wp_safe_redirect().  This allows you to create redirects in code, but only to whitelisted domains (via the allowed_redirect_hosts filter).

The downside to this is that you have to remember to whitelist the domains.  It’s easy to forget if you’re doing a lot of redirects, for instance with the WPCOM Legacy Redirector plugin.

When this happens, all un-whitelisted redirects will be redirected by default to /wp-admin/ instead, and can cause a headache trying to figure out what’s going wrong.

I had an idea to solve this problem.  A simple logging plugin that logs failed redirects and adds a dashboard widget to show the domains and number of times the redirect has failed:

The code behind this:

<?php
class Emrikol_WSRD_Dashboard {
	public static function instance() {
		static $instance = false;
		if ( ! $instance ) {
			$instance = new Emrikol_WSRD_Dashboard();
		}
		return $instance;
	}

	public function __construct() {
		add_action( 'init', array( $this, 'init' ) );
		add_filter( 'allowed_redirect_hosts', array( $this, 'check_redirect' ), PHP_INT_MAX, 2 );
	}

	public function init() {
		if ( $this->is_admin() && isset( $_GET['wsrd_delete'] ) && check_admin_referer( 'wsrd_delete' ) && isset( $_GET['ID'] ) ) {
			$post_id = (int) $_GET['ID'];

			if ( 'wsrd' !== get_post_type( $post_id ) ) {
				// This isn't the right post type, abort!
				add_action( 'admin_notices', array( $this, 'message_log_not_deleted' ) );
				return;
			}

			$delete = wp_delete_post( $post_id, true );
			wp_cache_delete( 'wsrd_report' );

			if ( $delete ) {
				add_action( 'admin_notices', array( $this, 'message_log_deleted' ) );
			} else {
				add_action( 'admin_notices', array( $this, 'message_log_not_deleted' ) );
			}
		}

		$args = array(
			'supports' => array( 'title' ),
			'public'   => false,
		);
		register_post_type( 'wsrd', $args );

		add_action( 'wp_dashboard_setup', array( $this, 'add_dashboard_widgets' ) );
	}

	public function add_dashboard_widgets() {
		if ( $this->is_admin() ) {
			wp_add_dashboard_widget( 'emrikol_wsrd_dashboard', 'Failed Safe Redirects', array( $this, 'show_admin_dashboard' ) );
		}
	}

	public function check_redirect( $allowed_hosts, $redirect_host ) {
		if ( ! in_array( $redirect_host, $allowed_hosts, true ) ) {
			// No redirect, please record.
			$found_host = new WP_Query( array(
				'fields'                 => 'ids',
				'name'                   => md5( $redirect_host ),
				'post_type'              => 'wsrd',
				'post_status'            => 'any',
				'no_found_rows'          => true,
				'posts_per_page'         => 1,
				'update_post_term_cache' => false,
				'update_post_meta_cache' => false,
			) );

			if ( empty( $found_host->posts ) ) {
				// No past redirect log found, create one.
				$args   = array(
					'post_name'  => md5( $redirect_host ),
					'post_title' => $redirect_host,
					'post_type'  => 'wsrd',
					'meta_input' => array(
						'count' => 1,
					),
				);
				$insert = wp_insert_post( $args );
			} else {
				// Found!  Update count.
				$count = absint( get_post_meta( $found_host->posts[0], 'count', true ) );
				$count++;
				update_post_meta( $found_host->posts[0], 'count', $count );
			}
		}
		// We don't want to modify, always return allowed hosts unharmed.
		return $allowed_hosts;
	}

	public function show_admin_dashboard() {
		global $wpdb;

		$report = wp_cache_get( 'wsrd_report' );
		if ( false === $report ) {
			$report = $wpdb->get_results( "SELECT ID, post_title AS host, meta_value AS count FROM $wpdb->posts LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id ) WHERE post_type='wsrd'  ORDER BY ABS( count ) DESC LIMIT 20;" );
			wp_cache_set( 'wsrd_report', $report, 'default', MINUTE_IN_SECONDS * 5 );
		}

		?>
		<style>
			table#wsrd {
				border-collapse: collapse;
				width: 100%;
			}
			table#wsrd th {
				background: #f5f5f5;
			}

			table#wsrd th, table#wsrd td {
				border: 1px solid #f5f5f5;
				padding: 8px;
			}

			table#wsrd tr:nth-child(even) {
				background: #fafafa;
			}
		</style>
		<div class="activity-block">
			<?php if ( empty( $report ) ) : ?>
			<p><strong>None Found!</strong></p>
			<?php else : ?>
			<table id="wsrd">
				<thead>
					<tr>
						<th>Domain</th>
						<th>Count</th>
						<th>Control</th>
					</tr>
				</thead>
				<tbody>
					<?php foreach ( $report as $line ) : ?>
						<tr>
							<td><?php echo esc_html( $line->host ); ?></td>
							<td><?php echo esc_html( $line->count ); ?></td>
							<td><a href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'wsrd_delete' => true, 'ID' => rawurlencode( $line->ID ) ), admin_url() ), 'wsrd_delete' ) ); ?>">Delete</a></td>
						</tr>
					<?php endforeach; ?>
				</tbody>
			</table>
			<?php endif; ?>
		</div>
		<?php
	}

	public function message_log_deleted() {
		echo '<div id="message" class="notice notice-success is-dismissible"><p>Redirect log deleted!</p></div>';
	}

	public function message_log_not_deleted() {
		echo '<div id="message" class="notice notice-error is-dismissible"><p>Redirect log delete failed!</p></div>';
	}


	private function is_admin() {
		if ( current_user_can( 'manage_options' ) ) {
			return true;
		}
		return false;
	}
}
Emrikol_WSRD_Dashboard::instance();

Leave a comment

Leave a Reply

%d bloggers like this: