From 57d02a99a497c27bab99a6989a26df90542e4af2 Mon Sep 17 00:00:00 2001 From: Mark Kennedy Date: Wed, 13 Dec 2017 16:57:40 +0000 Subject: [PATCH 1/6] modified redirect to use new storage format; modified redirect to recursive check URLs against the list so we can reduce chains of 301s to a single redirect --- wp-simple-301-redirects.php | 125 +++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 38 deletions(-) diff --git a/wp-simple-301-redirects.php b/wp-simple-301-redirects.php index df328b3..1ef22dc 100644 --- a/wp-simple-301-redirects.php +++ b/wp-simple-301-redirects.php @@ -420,57 +420,105 @@ function get_redirect_by_index( $index ) { return false; } + function match($redirect, $url) { + + $do_redirect = false; + $destination = $redirect['destination']; + $request = $redirect['request']; + // check if we should use regex search + $wildcard = isset( $redirect['wildcard'] ) ? $redirect['wildcard'] : false; + + if ($wildcard === 'true' && strpos($request,'*') !== false) { + // wildcard redirect + + // don't allow people to accidentally lock themselves out of admin + if ( strpos($url, '/wp-login') !== 0 && strpos($url, '/wp-admin') !== 0 ) { + + // Turn a glob (well, *'s only), into a valid regular expression + $request = preg_quote($request, '/'); + + // preg_quote will turn * into \* + $request = str_replace('\*','(.*)',$request); + $pattern = '/^' . str_replace( '/', '\/', rtrim( $request, '/' ) ) . '/'; + + // destination uses * as a replacement token for the first * match + // TODO possily remove this, it's confusing, right? + $destination = str_replace('*','$1',$destination); + + // preg_replace will return NULL if the pattern is an error + // so best to preg_match first and make sure this is valid + // also slightly cheaper on misses to do matches than replacements + if( preg_match($pattern, $url) ) { + $do_redirect = preg_replace($pattern, $destination, $url); + } + } + } + elseif( rtrim( urldecode( $url ), '/' ) == rtrim( $request, '/' ) ) { + // simple comparison redirect + $do_redirect = $destination; + } + return $do_redirect; + } + + /** + * call recursively to flatten chained 301s + */ + function resolve($redirects, $request, $safety) { + if( $safety > 20 ) { + // we're in too deep! + return $request; + } + foreach($redirects as $key => $redirect) { + //pass + //echo $request['url'] . "\n"; + //echo $redirect['request'] . "\n"; + $dest_url = $this->match( $redirect, $request['url'] ); + if( false !== $dest_url ) { + $request['url'] = $dest_url; + $request['do_redirect'] = true; + // don't evaluate this one twice + unset($redirects[$key]); + // look up the new dest url against the list again + return $this->resolve( + $redirects, + $request, + $safety+1 + ); + } + } + // otherwise + return $request; + } + /** * Read the list of redirects and if the current page * is found in the list, send the visitor on her way. - * @todo Update this to work with the new storage format. */ function redirect() { // this is what the user asked for (strip out home portion, case insensitive) $userrequest = str_ireplace(get_option('home'),'',$this->get_address()); $userrequest = rtrim($userrequest,'/'); - $redirects = get_option('301_redirects'); + $redirects = get_option($this->redirects_option); + if (!empty($redirects)) { - $wildcard = get_option('301_redirects_wildcard'); - $do_redirect = ''; - - // compare user request to each 301 stored in the db - foreach ($redirects as $storedrequest => $destination) { - // check if we should use regex search - if ($wildcard === 'true' && strpos($storedrequest,'*') !== false) { - // wildcard redirect - - // don't allow people to accidentally lock themselves out of admin - if ( strpos($userrequest, '/wp-login') !== 0 && strpos($userrequest, '/wp-admin') !== 0 ) { - // Make sure it gets all the proper decoding and rtrim action - $storedrequest = str_replace('*','(.*)',$storedrequest); - $pattern = '/^' . str_replace( '/', '\/', rtrim( $storedrequest, '/' ) ) . '/'; - $destination = str_replace('*','$1',$destination); - $output = preg_replace($pattern, $destination, $userrequest); - if ($output !== $userrequest) { - // pattern matched, perform redirect - $do_redirect = $output; - } - } - } - elseif(urldecode($userrequest) == rtrim($storedrequest,'/')) { - // simple comparison redirect - $do_redirect = $destination; - } + $request = [ 'url' => $userrequest, 'do_redirect' => false ]; + + $request = $this->resolve( $redirects, $request, 0 ); - // redirect. the second condition here prevents redirect loops as a result of wildcards. - if ($do_redirect !== '' && trim($do_redirect,'/') !== trim($userrequest,'/')) { - // check if destination needs the domain prepended - if (strpos($do_redirect,'/') === 0){ - $do_redirect = home_url().$do_redirect; - } - header ('HTTP/1.1 301 Moved Permanently'); - header ('Location: ' . $do_redirect); - exit(); + // redirect. the second condition here prevents redirect loops as a result of wildcards. + if( + $request['do_redirect'] == true + && trim( $request['url'], '/') !== trim( $userrequest, '/' ) + ) { + // check if destination needs the domain prepended + if( strpos( $do_redirect, '/' ) === 0 ) { + $request['url'] = home_url() . $request['url']; } - else { unset($redirects); } + header( 'HTTP/1.1 301 Moved Permanently' ); + header( 'Location: ' . $request['url'] ); + exit(); } } } @@ -488,6 +536,7 @@ function get_address() { function get_protocol() { // Set the base protocol to http $protocol = 'http'; + // check for https if ( isset( $_SERVER["HTTPS"] ) && strtolower( $_SERVER["HTTPS"] ) == "on" ) { $protocol .= "s"; From 042370d6f6522d8763ab77d489587616fe5f9a25 Mon Sep 17 00:00:00 2001 From: Mark Kennedy Date: Wed, 13 Dec 2017 17:01:56 +0000 Subject: [PATCH 2/6] A basic README --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8752409..6a4aec0 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ -simple-301-redirects -==================== +# Simple 301 Redirects + **Notice: This entire repository should be considered experimental. The stable version of this plugin should always be [downloaded from the WordPress Plugin Directory](https://wordpress.org/plugins/simple-301-redirects/ "Download the release version of Simple 301 Redirects").** -Simple 301 Redirects is a popular URL Redirection plugin for WordPress +Simple 301 Redirects is a popular URL Redirection plugin for WordPress. + +## Features + + - Allows your users to 301 redirect any URL to any other from the WP Admin site. + - Supports simple globby wildcards (`/some/path/*/` -> `/somewhere/else/`) + - Resolves 301 chains into single redirects From 3a91d5f3dc5e9b3a0b4d9c90b55888a78e6f1aee Mon Sep 17 00:00:00 2001 From: Mark Kennedy Date: Wed, 13 Dec 2017 17:04:13 +0000 Subject: [PATCH 3/6] tidying up --- wp-simple-301-redirects.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/wp-simple-301-redirects.php b/wp-simple-301-redirects.php index 1ef22dc..9754426 100644 --- a/wp-simple-301-redirects.php +++ b/wp-simple-301-redirects.php @@ -469,9 +469,6 @@ function resolve($redirects, $request, $safety) { return $request; } foreach($redirects as $key => $redirect) { - //pass - //echo $request['url'] . "\n"; - //echo $redirect['request'] . "\n"; $dest_url = $this->match( $redirect, $request['url'] ); if( false !== $dest_url ) { $request['url'] = $dest_url; From 880de14fdb68759d43b61f46c3df9f3bd9d7ed29 Mon Sep 17 00:00:00 2001 From: Mark Kennedy Date: Wed, 13 Dec 2017 21:37:46 +0000 Subject: [PATCH 4/6] fix double escaped slashes; add author credit --- wp-simple-301-redirects.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wp-simple-301-redirects.php b/wp-simple-301-redirects.php index 9754426..0e91fc1 100644 --- a/wp-simple-301-redirects.php +++ b/wp-simple-301-redirects.php @@ -6,6 +6,8 @@ Version: 1.08a Author: Scott Nellé Author URI: http://www.scottnelle.com/ +Author: Mark Kennedy +Author URI: https://github.com/mrmonkington */ /* Copyright 2009-2016 Scott Nellé (email : contact@scottnelle.com) @@ -439,7 +441,7 @@ function match($redirect, $url) { // preg_quote will turn * into \* $request = str_replace('\*','(.*)',$request); - $pattern = '/^' . str_replace( '/', '\/', rtrim( $request, '/' ) ) . '/'; + $pattern = '/^' . rtrim( $request, '/' ) . '/'; // destination uses * as a replacement token for the first * match // TODO possily remove this, it's confusing, right? From 8c126dfc38f5c7897887bdfff61f27147af3015f Mon Sep 17 00:00:00 2001 From: Mark Kennedy Date: Thu, 21 Dec 2017 13:49:47 +0000 Subject: [PATCH 5/6] handle amp --- wp-simple-301-redirects.php | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/wp-simple-301-redirects.php b/wp-simple-301-redirects.php index 0e91fc1..fecbbed 100644 --- a/wp-simple-301-redirects.php +++ b/wp-simple-301-redirects.php @@ -10,6 +10,8 @@ Author URI: https://github.com/mrmonkington */ +include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); + /* Copyright 2009-2016 Scott Nellé (email : contact@scottnelle.com) This program is free software; you can redistribute it and/or modify @@ -72,7 +74,7 @@ public function __construct() { $this->capability = apply_filters( 's301r_capability', 'manage_options' ); // Add the redirect action, high priority. - add_action( 'init', array( $this, 'redirect' ), apply_filters( 's301r_priority', 1 ) ); + add_action( 'wp', array( $this, 'redirect' ), apply_filters( 's301r_priority', 1 ) ); // Create the menu item. add_action( 'admin_menu', array( $this, 'create_menu' ) ); @@ -499,9 +501,17 @@ function redirect() { $userrequest = rtrim($userrequest,'/'); $redirects = get_option($this->redirects_option); - + if (!empty($redirects)) { + $is_amp = false; + if( is_plugin_active('amp/amp.php') ) { + if( is_amp_endpoint() ) { + $is_amp = true; + $userrequest = preg_replace( '|/' . AMP_QUERY_VAR . '$|', '', $userrequest ); + } + } + $request = [ 'url' => $userrequest, 'do_redirect' => false ]; $request = $this->resolve( $redirects, $request, 0 ); @@ -515,6 +525,14 @@ function redirect() { if( strpos( $do_redirect, '/' ) === 0 ) { $request['url'] = home_url() . $request['url']; } + + if( $is_amp ) { + $request['url'] = rtrim( $request['url'], '/' ); + $request['url'] = $request['url'] . '/' . AMP_QUERY_VAR; + } + + $request['url'] = $request['url'] . $this->get_query_str(); + header( 'HTTP/1.1 301 Moved Permanently' ); header( 'Location: ' . $request['url'] ); exit(); @@ -529,7 +547,18 @@ function redirect() { */ function get_address() { // return the full address - return $this->get_protocol().'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; + $url = $this->get_protocol().'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; + $url = explode('?', $url); + return $url[0]; + } // end function get_address + function get_query_str() { + // return the full address + $url = $this->get_protocol().'://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; + $url = explode('?', $url); + if( '' != $url[1] ) { + return '?' . $url[1]; + } + return ''; } // end function get_address function get_protocol() { From 0d2a3d3fa9edf5f5afb545d26166a9b9a63dac75 Mon Sep 17 00:00:00 2001 From: Brendan Smith Date: Fri, 5 Oct 2018 09:22:34 +0100 Subject: [PATCH 6/6] Factor out repeated calls to generate_hash function in redirect listing --- wp-simple-301-redirects.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wp-simple-301-redirects.php b/wp-simple-301-redirects.php index fecbbed..7e29145 100644 --- a/wp-simple-301-redirects.php +++ b/wp-simple-301-redirects.php @@ -189,13 +189,14 @@ function list_redirects() { if ( ! empty( $redirects ) && is_array( $redirects ) ) { foreach ($redirects as $index => $data) { if ( ! empty( $data['request'] ) && ! empty( $data['destination'] ) ) { + $hash = $this->generate_hash($index); ?> - +
- | - + | +