Safe Redirect Manager - Version 1.7.3

Version Description

(Aug. 26, 2014) = * Check if the global $wp_query is null before using get_query_var. Props cmmarslender * Unit tests * Making _x translatable text work. Props lucspe

Download this release

Release Info

Developer tlovett1
Plugin Icon 128x128 Safe Redirect Manager
Version 1.7.3
Comparing to
See all releases

Code changes from version 1.7.2 to 1.7.3

.gitignore ADDED
@@ -0,0 +1 @@
 
1
+ .svn
.travis.yml ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: php
2
+
3
+ php:
4
+ - 5.2
5
+ - 5.3
6
+ - 5.4
7
+
8
+ env:
9
+ - WP_VERSION=latest WP_MULTISITE=0
10
+ - WP_VERSION=latest WP_MULTISITE=1
11
+ - WP_VERSION=3.8 WP_MULTISITE=0
12
+ - WP_VERSION=3.8 WP_MULTISITE=1
13
+ - WP_VERSION=3.7 WP_MULTISITE=0
14
+ - WP_VERSION=3.7 WP_MULTISITE=1
15
+ - WP_VERSION=3.6 WP_MULTISITE=0
16
+ - WP_VERSION=3.6 WP_MULTISITE=1
17
+
18
+ before_script:
19
+ - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION
20
+
21
+ script: phpunit
bin/install-wp-tests.sh ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ if [ $# -lt 3 ]; then
4
+ echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version]"
5
+ exit 1
6
+ fi
7
+
8
+ DB_NAME=$1
9
+ DB_USER=$2
10
+ DB_PASS=$3
11
+ DB_HOST=${4-localhost}
12
+ WP_VERSION=${5-latest}
13
+
14
+ WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib}
15
+ WP_CORE_DIR=/tmp/wordpress/
16
+
17
+ set -ex
18
+
19
+ install_wp() {
20
+ mkdir -p $WP_CORE_DIR
21
+
22
+ if [ $WP_VERSION == 'latest' ]; then
23
+ local ARCHIVE_NAME='latest'
24
+ else
25
+ local ARCHIVE_NAME="wordpress-$WP_VERSION"
26
+ fi
27
+
28
+ wget -nv -O /tmp/wordpress.tar.gz http://wordpress.org/${ARCHIVE_NAME}.tar.gz
29
+ tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR
30
+
31
+ wget -nv -O $WP_CORE_DIR/wp-content/db.php https://raw.github.com/markoheijnen/wp-mysqli/master/db.php
32
+ }
33
+
34
+ install_test_suite() {
35
+ # portable in-place argument for both GNU sed and Mac OSX sed
36
+ if [[ $(uname -s) == 'Darwin' ]]; then
37
+ local ioption='-i .bak'
38
+ else
39
+ local ioption='-i'
40
+ fi
41
+
42
+ # set up testing suite
43
+ mkdir -p $WP_TESTS_DIR
44
+ cd $WP_TESTS_DIR
45
+ svn co --quiet http://develop.svn.wordpress.org/trunk/tests/phpunit/includes/
46
+
47
+ wget -nv -O wp-tests-config.php http://develop.svn.wordpress.org/trunk/wp-tests-config-sample.php
48
+ sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" wp-tests-config.php
49
+ sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" wp-tests-config.php
50
+ sed $ioption "s/yourusernamehere/$DB_USER/" wp-tests-config.php
51
+ sed $ioption "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php
52
+ sed $ioption "s|localhost|${DB_HOST}|" wp-tests-config.php
53
+ }
54
+
55
+ install_db() {
56
+ # parse DB_HOST for port or socket references
57
+ local PARTS=(${DB_HOST//\:/ })
58
+ local DB_HOSTNAME=${PARTS[0]};
59
+ local DB_SOCK_OR_PORT=${PARTS[1]};
60
+ local EXTRA=""
61
+
62
+ if ! [ -z $DB_HOSTNAME ] ; then
63
+ if [[ "$DB_SOCK_OR_PORT" =~ ^[0-9]+$ ]] ; then
64
+ EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
65
+ elif ! [ -z $DB_SOCK_OR_PORT ] ; then
66
+ EXTRA=" --socket=$DB_SOCK_OR_PORT"
67
+ elif ! [ -z $DB_HOSTNAME ] ; then
68
+ EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
69
+ fi
70
+ fi
71
+
72
+ # create database
73
+ mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
74
+ }
75
+
76
+ install_wp
77
+ install_test_suite
78
+ install_db
phpunit.xml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <phpunit
2
+ bootstrap="tests/bootstrap.php"
3
+ backupGlobals="false"
4
+ colors="true"
5
+ convertErrorsToExceptions="true"
6
+ convertNoticesToExceptions="true"
7
+ convertWarningsToExceptions="true"
8
+ >
9
+ <testsuites>
10
+ <testsuite>
11
+ <directory prefix="test-" suffix=".php">./tests/</directory>
12
+ </testsuite>
13
+ </testsuites>
14
+ </phpunit>
readme.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: tlovett1, tollmanz, taylorde, 10up, jakemgold, danielbachhuber, Ve
3
  Tags: http redirects, redirect manager, url redirection, safe http redirection, multisite redirects
4
  Requires at least: 3.1
5
  Tested up to: 3.8
6
- Stable tag: 1.7.2
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
@@ -24,6 +24,11 @@ Extract the zip file and just drop the contents in the wp-content/plugins/ direc
24
 
25
  == Changelog ==
26
 
 
 
 
 
 
27
  = 1.7.2 (Feb. 10, 2014) =
28
  * Added French translation. Props [jcbrebion](https://github.com/jcbrebion).
29
  * Bug fix: Don't perform redirects in the admin. Props [joshbetz](https://github.com/joshbetz).
3
  Tags: http redirects, redirect manager, url redirection, safe http redirection, multisite redirects
4
  Requires at least: 3.1
5
  Tested up to: 3.8
6
+ Stable tag: 1.7.3
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
24
 
25
  == Changelog ==
26
 
27
+ = 1.7.3 (Aug. 26, 2014) =
28
+ * Check if the global $wp_query is null before using get_query_var. Props [cmmarslender](https://github.com/cmmarslender)
29
+ * Unit tests
30
+ * Making _x translatable text work. Props [lucspe](https://github.com/lucspe)
31
+
32
  = 1.7.2 (Feb. 10, 2014) =
33
  * Added French translation. Props [jcbrebion](https://github.com/jcbrebion).
34
  * Bug fix: Don't perform redirects in the admin. Props [joshbetz](https://github.com/joshbetz).
safe-redirect-manager.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: Safe Redirect Manager
4
  Plugin URI: http://www.10up.com
5
  Description: Easily and safely manage HTTP redirects.
6
  Author: Taylor Lovett (10up LLC), VentureBeat
7
- Version: 1.7.2
8
  Author URI: http://www.10up.com
9
 
10
  GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
@@ -26,932 +26,952 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26
  */
27
 
28
  if ( defined( 'WP_CLI' ) && WP_CLI )
29
- require_once dirname( __FILE__ ) . '/inc/wp-cli.php';
30
 
31
  class SRM_Safe_Redirect_Manager {
32
 
33
- public $redirect_post_type = 'redirect_rule';
34
- private $redirect_nonce_name = 'srm_redirect_nonce';
35
- private $redirect_nonce_action = 'srm-save-redirect-meta';
36
-
37
- public $meta_key_redirect_from = '_redirect_rule_from';
38
- public $meta_key_redirect_to = '_redirect_rule_to';
39
- public $meta_key_redirect_status_code = '_redirect_rule_status_code';
40
- public $meta_key_enable_redirect_from_regex = '_redirect_rule_from_regex';
41
-
42
- public $cache_key_redirects = '_srm_redirects';
43
-
44
- public $valid_status_codes = array( 301, 302, 303, 307, 403, 404 );
45
-
46
- public $status_code_labels = array(
47
- 301 => 'Moved Permanently',
48
- 302 => 'Found',
49
- 303 => 'See Other',
50
- 307 => 'Temporary Redirect',
51
- 403 => 'Forbidden',
52
- 404 => 'Not Found',
53
- );
54
-
55
- private $whitelist_hosts = array();
56
-
57
- public $default_max_redirects = 150;
58
-
59
- /**
60
- * Sets up redirect manager
61
- *
62
- * @since 1.0
63
- * @uses add_action, add_filter
64
- * @return object
65
- */
66
- public function __construct() {
67
- add_action( 'init', array( $this, 'action_init_load_textdomain' ), 9 );
68
- add_action( 'init', array( $this, 'action_init' ) );
69
- add_action( 'init', array( $this, 'action_register_post_types' ) );
70
- add_action( 'parse_request', array( $this, 'action_parse_request' ), 0 );
71
- add_action( 'save_post', array( $this, 'action_save_post' ) );
72
- add_filter( 'manage_' . $this->redirect_post_type . '_posts_columns' , array( $this, 'filter_redirect_columns' ) );
73
- add_action( 'manage_' . $this->redirect_post_type . '_posts_custom_column' , array( $this, 'action_custom_redirect_columns' ), 10, 2 );
74
- add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
75
- add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
76
- add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
77
- add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
78
- add_filter( 'bulk_actions-edit-' . $this->redirect_post_type, array( $this, 'filter_bulk_actions' ) );
79
- add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
80
- add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
81
- add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
82
- add_filter( 'post_type_link', array( $this, 'filter_post_type_link' ), 10, 2 );
83
-
84
- // Search filters
85
- add_filter( 'posts_join', array( $this, 'filter_search_join' ) );
86
- add_filter( 'posts_where', array( $this, 'filter_search_where' ) );
87
- add_filter( 'posts_distinct', array( $this, 'filter_search_distinct' ) );
88
- }
89
-
90
- /**
91
- * Localize plugin
92
- *
93
- * @since 1.7
94
- * @uses load_plugin_textdomain
95
- * @return void
96
- */
97
- public function action_init_load_textdomain() {
98
- load_plugin_textdomain( 'safe-redirect-manager', false, basename( dirname( __FILE__ ) ) . '/languages' );
99
- }
100
-
101
- /**
102
- * Join posts table with postmeta table on search
103
- *
104
- * @since 1.2
105
- * @param string $join
106
- * @uses get_query_var
107
- * @return string
108
- */
109
- public function filter_search_join( $join ) {
110
- if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
111
- return $join;
112
-
113
- global $wpdb;
114
-
115
- $s = get_query_var( 's' );
116
- if ( ! empty( $s ) ) {
117
- $join .= " LEFT JOIN $wpdb->postmeta AS m ON ($wpdb->posts.ID = m.post_id) ";
118
- }
119
- return $join;
120
- }
121
-
122
- /**
123
- * Return distinct search results
124
- *
125
- * @since 1.2
126
- * @param string $distinct
127
- * @uses get_query_var
128
- * @return string
129
- */
130
- public function filter_search_distinct( $distinct ) {
131
- if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
132
- return $distinct;
133
-
134
- return 'DISTINCT';
135
- }
136
-
137
- /**
138
- * Join posts table with postmeta table on search
139
- *
140
- * @since 1.2
141
- * @param string $where
142
- * @uses is_search, get_query_var
143
- * @return string
144
- */
145
- public function filter_search_where( $where ) {
146
- if ( $this->redirect_post_type != get_query_var( 'post_type' ) || ! is_search() || empty( $where ) )
147
- return $where;
148
-
149
- $exact = get_query_var( 'exact' );
150
- $n = ( ! empty( $exact ) ) ? '' : '%';
151
-
152
- $search = '';
153
- $seperator = '';
154
- $terms = $this->get_search_terms();
155
- $search .= '(';
156
-
157
- // we check the meta values against each term in the search
158
- foreach ( $terms as $term ) {
159
- $search .= $seperator;
160
- $search .= sprintf( "( ( m.meta_value LIKE '%s%s%s' AND m.meta_key = '%s') OR ( m.meta_value LIKE '%s%s%s' AND m.meta_key = '%s') )", $n, $term, $n, $this->meta_key_redirect_from, $n, $term, $n, $this->meta_key_redirect_to );
161
- $seperator = ' OR ';
162
- }
163
-
164
- $search .= ')';
165
-
166
- $where = preg_replace( '/\(\(\(.*?\)\)\)/is', '((' . $search . '))', $where );
167
-
168
- return $where;
169
- }
170
-
171
- /**
172
- * Get an array of search terms
173
- *
174
- * @since 1.2
175
- * @uses get_query_var
176
- * @return array
177
- */
178
- private function get_search_terms() {
179
- $s = get_query_var( 's' );
180
-
181
- if ( ! empty( $s ) ) {
182
- preg_match_all( '/".*?("|$)|((?<=[\\s",+])|^)[^\\s",+]+/', stripslashes( $s ), $matches );
183
- $search_terms = array_map( create_function( '$a', 'return trim( $a, "\\"\'\\n\\r " );' ), $matches[0] );
184
- }
185
- return $search_terms;
186
- }
187
-
188
- /**
189
- * Swap tools logo for plugin logo
190
- *
191
- * @since 1.1
192
- * @uses plugins_url
193
- * @return void
194
- */
195
- public function action_print_logo_css() {
196
- if ( $this->is_plugin_page() ) {
197
- ?>
198
- <style type="text/css">
199
- #icon-tools {
200
- background: url("<?php echo plugins_url( 'images/icon32x32.png', __FILE__ ); ?>") no-repeat top left !important;
201
- margin-right: 0;
202
- }
203
- #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group {
204
- display: none;
205
- }
206
- #srm<?php echo $this->meta_key_redirect_from; ?> {
207
- width: 60%;
208
- }
209
- </style>
210
- <?php
211
- }
212
- }
213
-
214
- /**
215
- * Limit the bulk actions available in the Manage Redirects view
216
- *
217
- * @since 1.0
218
- * @return array
219
- */
220
- public function filter_bulk_actions( $actions ) {
221
-
222
- // No bulk editing at this time
223
- unset( $actions['edit'] );
224
-
225
- return $actions;
226
- }
227
-
228
- /**
229
- * Creates a redirect post, this function will be useful for import/exports scripts
230
- *
231
- * @param string $redirect_from
232
- * @param string $redirect_to
233
- * @param int $status_code
234
- * @param bool $enable_regex
235
- * @param string $post_status
236
- * @since 1.3
237
- * @uses wp_insert_post, update_post_meta
238
- * @return int|WP_Error
239
- */
240
- public function create_redirect( $redirect_from, $redirect_to, $status_code = 302, $enable_regex = false, $post_status = 'publish' ) {
241
- global $wpdb;
242
-
243
- $sanitized_redirect_from = $this->sanitize_redirect_from( $redirect_from );
244
- $sanitized_redirect_to = $this->sanitize_redirect_to( $redirect_to );
245
- $sanitized_status_code = absint( $status_code );
246
- $sanitized_enable_regex = (bool)$enable_regex;
247
- $sanitized_post_status = sanitize_key( $post_status );
248
-
249
- // check and make sure no parameters are empty or invalid after sanitation
250
- if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) )
251
- return new WP_Error( 'invalid-argument', __( 'Redirect from and/or redirect to arguments are invalid.', 'safe-redirect-manager' ) );
252
-
253
- if ( ! in_array( $sanitized_status_code, $this->valid_status_codes ) )
254
- return new WP_Error( 'invalid-argument', __( 'Invalid status code.', 'safe-redirect-manager' ) );
255
-
256
- // Check to ensure this redirect doesn't already exist
257
- if ( $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%s", $this->meta_key_redirect_from, $sanitized_redirect_from ) ) )
258
- return new WP_Error( 'duplicate-redirect', sprintf( __( 'Redirect already exists for %s', 'safe-redirect-manager' ), $sanitized_redirect_from ) );
259
-
260
- // create the post
261
- $post_args = array(
262
- 'post_type' => $this->redirect_post_type,
263
- 'post_status' => $sanitized_post_status,
264
- 'post_author' => 1
265
- );
266
-
267
- $post_id = wp_insert_post( $post_args );
268
-
269
- if ( 0 >= $post_id )
270
- return new WP_Error( 'error-creating', __( 'An error occurred creating the redirect.', 'safe-redirect-manager' ) );
271
-
272
- // update the posts meta info
273
- update_post_meta( $post_id, $this->meta_key_redirect_from, $sanitized_redirect_from );
274
- update_post_meta( $post_id, $this->meta_key_redirect_to, $sanitized_redirect_to );
275
- update_post_meta( $post_id, $this->meta_key_redirect_status_code, $sanitized_status_code );
276
- update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $sanitized_enable_regex );
277
-
278
- // We need to update the cache after creating this redirect
279
- $this->update_redirect_cache();
280
-
281
- return $post_id;
282
- }
283
-
284
- /**
285
- * Whether or not this is an admin page specific to the plugin
286
- *
287
- * @since 1.1
288
- * @uses get_post_type
289
- * @return bool
290
- */
291
- private function is_plugin_page() {
292
- return (bool) ( get_post_type() == $this->redirect_post_type || ( isset( $_GET['post_type'] ) && $this->redirect_post_type == $_GET['post_type'] ) );
293
- }
294
-
295
- /**
296
- * Echoes admin message if redirect chains exist
297
- *
298
- * @since 1.0
299
- * @uses apply_filters
300
- * @return void
301
- */
302
- public function action_redirect_chain_alert() {
303
- global $hook_suffix;
304
- if ( $this->is_plugin_page() ) {
305
-
306
- /**
307
- * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
308
- * will be running slow. Let's disable it by default.
309
- */
310
- if ( apply_filters( 'srm_check_for_possible_redirect_loops', false ) ) {
311
- if ( $this->check_for_possible_redirect_loops() ) {
312
- ?>
313
- <div class="updated">
314
- <p><?php _e( 'Safe Redirect Manager Warning: Possible redirect loops and/or chains have been created.', 'safe-redirect-manager' ); ?></p>
315
- </div>
316
- <?php
317
- }
318
- } if ( $this->max_redirects_reached() ) {
319
- ?>
320
- <?php if ( 'post-new.php' == $hook_suffix ) : ?><style type="text/css">#post { display: none; }</style><?php endif; ?>
321
- <div class="error">
322
- <p><?php _e( 'Safe Redirect Manager Error: You have reached the maximum allowable number of redirects', 'safe-redirect-manager' ); ?></p>
323
- </div>
324
- <?php
325
- }
326
- }
327
- }
328
-
329
- /**
330
- * Returns true if max redirects have been reached
331
- *
332
- * @since 1.0
333
- * @uses apply_filters, get_transient
334
- * @return bool
335
- */
336
- public function max_redirects_reached() {
337
- if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
338
- $redirects = $this->update_redirect_cache();
339
- }
340
-
341
- return ( count( $redirects ) >= $this->default_max_redirects );
342
- }
343
-
344
- /**
345
- * Check for potential redirect loops or chains
346
- *
347
- * @since 1.0
348
- * @uses home_url, get_transient
349
- * @return boolean
350
- */
351
- public function check_for_possible_redirect_loops() {
352
- if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
353
- $redirects = $this->update_redirect_cache();
354
- }
355
-
356
- $current_url = parse_url( home_url() );
357
- $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
358
-
359
- foreach ( $redirects as $redirect ) {
360
- $redirect_from = $redirect['redirect_from'];
361
-
362
- // check redirect from against all redirect to's
363
- foreach ( $redirects as $compare_redirect ) {
364
- $redirect_to = $compare_redirect['redirect_to'];
365
-
366
- $redirect_url = parse_url( $redirect_to );
367
- $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
368
-
369
- // check if we are redirecting locally
370
- if ( empty( $redirect_host ) || $redirect_host == $this_host ) {
371
- $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
372
- $redirect_to_url = $redirect_to;
373
- if ( ! preg_match( '/https?:\/\//i', $redirect_to_url ) )
374
- $redirect_to_url = $this_host . $redirect_to_url;
375
- else
376
- $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
377
-
378
- // possible loop/chain found
379
- if ( $redirect_to_url == $redirect_from_url )
380
- return true;
381
- }
382
- }
383
- }
384
-
385
- return false;
386
- }
387
-
388
- /**
389
- * Filters title out for redirect from in post manager
390
- *
391
- * @since 1.0
392
- * @param string $title
393
- * @param int $post_id
394
- * @uses is_admin, get_post_meta
395
- * @return string
396
- */
397
- public function filter_admin_title( $title, $post_id = 0 ) {
398
- if ( ! is_admin() )
399
- return $title;
400
-
401
- $redirect = get_post( $post_id );
402
- if ( empty( $redirect ) )
403
- return $title;
404
-
405
- if ( $redirect->post_type != $this->redirect_post_type )
406
- return $title;
407
-
408
- $redirect_from = get_post_meta( $post_id, $this->meta_key_redirect_from, true );
409
- if ( ! empty( $redirect_from ) )
410
- return $redirect_from;
411
-
412
- return $title;
413
- }
414
-
415
- /**
416
- * Customizes updated messages for redirects
417
- *
418
- * @since 1.0
419
- * @param array $messages
420
- * @uses esc_url, get_permalink, add_query_var, wp_post_revision_title
421
- * @return array
422
- */
423
- public function filter_redirect_updated_messages( $messages ) {
424
- global $post, $post_ID;
425
-
426
- $messages[$this->redirect_post_type] = array(
427
- 0 => '', // Unused. Messages start at index 1.
428
- 1 => sprintf( __( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
429
- 2 => __( 'Custom field updated.', 'safe-redirect-manager' ),
430
- 3 => __( 'Custom field deleted.', 'safe-redirect-manager' ),
431
- 4 => __( 'Redirect rule updated.', 'safe-redirect-manager' ),
432
- /* translators: %s: date and time of the revision */
433
- 5 => isset( $_GET['revision'] ) ? sprintf( __('Redirect rule restored to revision from %s', 'safe-redirect-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
434
- 6 => sprintf( __( 'Redirect rule published.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
435
- 7 => __( 'Redirect rule saved.', 'safe-redirect-manager' ),
436
- 8 => sprintf( __( 'Redirect rule submitted.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
437
- 9 => sprintf( __( 'Redirect rule scheduled for: <strong>%1$s</strong>.', 'safe-redirect-manager' ),
438
- // translators: Publish box date format, see http://php.net/date
439
- date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
440
- 10 => sprintf( __( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
441
- );
442
-
443
- return $messages;
444
- }
445
-
446
- /**
447
- * Clear redirect cache if appropriate post type is transitioned
448
- *
449
- * @since 1.0
450
- * @param string $new_status
451
- * @param string $old_status
452
- * @param object $post
453
- * @uses delete_transient
454
- * @return void
455
- */
456
- public function action_transition_post_status( $new_status, $old_status, $post ) {
457
- if ( ! is_object( $post ) )
458
- return;
459
-
460
- // recreate redirect cache
461
- if ( $this->redirect_post_type == $post->post_type ) {
462
- delete_transient( $this->cache_key_redirects );
463
- $this->update_redirect_cache();
464
- }
465
- }
466
-
467
- /**
468
- * Displays custom columns on redirect manager screen
469
- *
470
- * @since 1.0
471
- * @param string $column
472
- * @param int $post_id
473
- * @uses get_post_meta, esc_html, admin_url
474
- * @return void
475
- */
476
- public function action_custom_redirect_columns( $column, $post_id ) {
477
- if ( 'srm' . $this->meta_key_redirect_to == $column ) {
478
- echo esc_html( get_post_meta( $post_id, $this->meta_key_redirect_to, true ) );
479
- } elseif ( 'srm' . $this->meta_key_redirect_status_code == $column ) {
480
- echo absint( get_post_meta( $post_id, $this->meta_key_redirect_status_code, true ) );
481
- }
482
- }
483
-
484
- /**
485
- * Add new columns to manage redirect screen
486
- *
487
- * @since 1.0
488
- * @param array $columns
489
- * @return array
490
- */
491
- public function filter_redirect_columns( $columns ) {
492
- $columns['srm' . $this->meta_key_redirect_to] = __( 'Redirect To', 'safe-redirect-manager' );
493
- $columns['srm'. $this->meta_key_redirect_status_code] = __( 'HTTP Status Code', 'safe-redirect-manager' );
494
-
495
- // Change the title column
496
- $columns['title'] = __( 'Redirect From', 'safe-redirect-manager' );
497
-
498
- // Move date column to the back
499
- unset( $columns['date'] );
500
- $columns['date'] = __( 'Date', 'safe-redirect-manager' );
501
-
502
- return $columns;
503
- }
504
-
505
- /**
506
- * Saves meta info for redirect rules
507
- *
508
- * @since 1.0
509
- * @param int $post_id
510
- * @uses current_user_can, get_post_type, wp_verify_nonce, update_post_meta, delete_post_meta
511
- * @return void
512
- */
513
- public function action_save_post( $post_id ) {
514
- if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' == get_post_type( $post_id ) )
515
- return;
516
-
517
- // Update post meta for redirect rules
518
- if ( ! empty( $_POST[$this->redirect_nonce_name] ) && wp_verify_nonce( $_POST[$this->redirect_nonce_name], $this->redirect_nonce_action ) ) {
519
-
520
- if ( ! empty( $_POST['srm' . $this->meta_key_enable_redirect_from_regex] ) ) {
521
- $allow_regex = (bool) $_POST['srm' . $this->meta_key_enable_redirect_from_regex];
522
- update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $allow_regex );
523
- } else {
524
- $allow_regex = false;
525
- delete_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex );
526
- }
527
-
528
- if ( ! empty( $_POST['srm' . $this->meta_key_redirect_from] ) ) {
529
- update_post_meta( $post_id, $this->meta_key_redirect_from, $this->sanitize_redirect_from( $_POST['srm' . $this->meta_key_redirect_from], $allow_regex ) );
530
- } else {
531
- delete_post_meta( $post_id, $this->meta_key_redirect_from );
532
- }
533
-
534
- if ( ! empty( $_POST['srm' . $this->meta_key_redirect_to] ) ) {
535
- update_post_meta( $post_id, $this->meta_key_redirect_to, $this->sanitize_redirect_to( $_POST['srm' . $this->meta_key_redirect_to] ) );
536
- } else {
537
- delete_post_meta( $post_id, $this->meta_key_redirect_to );
538
- }
539
-
540
- if ( ! empty( $_POST['srm' . $this->meta_key_redirect_status_code] ) ) {
541
- update_post_meta( $post_id, $this->meta_key_redirect_status_code, absint( $_POST['srm' . $this->meta_key_redirect_status_code] ) );
542
- } else {
543
- delete_post_meta( $post_id, $this->meta_key_redirect_status_code );
544
- }
545
-
546
- /**
547
- * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
548
- * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
549
- * redirect info is saved, updating the cache before it is not sufficient.
550
- */
551
- $this->update_redirect_cache();
552
- }
553
- }
554
-
555
- /**
556
- * Registers post types for plugin
557
- *
558
- * @since 1.0
559
- * @uses register_post_type, _x, plugins_url, apply_filters
560
- * @return void
561
- */
562
- public function action_register_post_types() {
563
- $redirect_labels = array(
564
- 'name' => _x( 'Safe Redirect Manager', 'post type general name' ),
565
- 'singular_name' => _x( 'Redirect', 'post type singular name' ),
566
- 'add_new' => _x( 'Create Redirect Rule', $this->redirect_post_type ),
567
- 'add_new_item' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
568
- 'edit_item' => __( 'Edit Redirect Rule', 'safe-redirect-manager' ),
569
- 'new_item' => __( 'New Redirect Rule', 'safe-redirect-manager' ),
570
- 'all_items' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
571
- 'view_item' => __( 'View Redirect Rule', 'safe-redirect-manager' ),
572
- 'search_items' => __( 'Search Redirects', 'safe-redirect-manager' ),
573
- 'not_found' => __( 'No redirect rules found.', 'safe-redirect-manager' ),
574
- 'not_found_in_trash' => __( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
575
- 'parent_item_colon' => '',
576
- 'menu_name' => __( 'Safe Redirect Manager', 'safe-redirect-manager' )
577
- );
578
- $redirect_capability = 'manage_options';
579
- $redirect_capability = apply_filters( 'srm_restrict_to_capability', $redirect_capability );
580
- $capabilities = array(
581
- 'edit_post' => $redirect_capability,
582
- 'read_post' => $redirect_capability,
583
- 'delete_post' => $redirect_capability,
584
- 'edit_posts' => $redirect_capability,
585
- 'edit_others_posts' => $redirect_capability,
586
- 'publish_posts' => $redirect_capability,
587
- 'read_private_posts' => $redirect_capability
588
- );
589
-
590
- $redirect_args = array(
591
- 'labels' => $redirect_labels,
592
- 'public' => false,
593
- 'publicly_queryable' => true,
594
- 'show_ui' => true,
595
- 'show_in_menu' => 'tools.php',
596
- 'query_var' => false,
597
- 'rewrite' => false,
598
- 'capability_type' => 'post',
599
- 'capabilities' => $capabilities,
600
- 'has_archive' => false,
601
- 'hierarchical' => false,
602
- 'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
603
- 'menu_position' => 80,
604
- 'supports' => array( '' )
605
- );
606
- register_post_type( $this->redirect_post_type, $redirect_args );
607
- }
608
-
609
- /**
610
- * Registers meta boxes for redirect rule post type
611
- *
612
- * @since 1.0
613
- * @uses add_meta_box
614
- * @return void
615
- */
616
- public function action_redirect_rule_metabox() {
617
- add_meta_box( 'redirect_settings', __( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), $this->redirect_post_type, 'normal', 'core' );
618
- }
619
-
620
- /**
621
- * Echoes HTML for redirect rule meta box
622
- *
623
- * @since 1.0
624
- * @param object $post
625
- * @uses wp_nonce_field, get_post_meta, esc_attr, selected
626
- * @return void
627
- */
628
- public function redirect_rule_metabox( $post ) {
629
- wp_nonce_field( $this->redirect_nonce_action, $this->redirect_nonce_name );
630
-
631
- $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
632
- $redirect_to = get_post_meta( $post->ID, $this->meta_key_redirect_to, true );
633
- $status_code = get_post_meta( $post->ID, $this->meta_key_redirect_status_code, true );
634
- $enable_regex = get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true );
635
- if ( empty( $status_code ) )
636
- $status_code = 302;
637
- ?>
638
- <p>
639
- <label for="srm<?php echo $this->meta_key_redirect_from; ?>"><?php _e( 'Redirect From:', 'safe-redirect-manager' ); ?></label><br />
640
- <input type="text" name="srm<?php echo $this->meta_key_redirect_from; ?>" id="srm<?php echo $this->meta_key_redirect_from; ?>" value="<?php echo esc_attr( $redirect_from ); ?>" />
641
- <input type="checkbox" name="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>" id="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>" <?php checked( true, (bool) $enable_regex ); ?> value="1" />
642
- <label for="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>"><?php _e( 'Enable Regular Expressions (advanced)', 'safe-redirect-manager' ); ?></label><br />
643
- <p class="description"><?php _e( "This path should be relative to the root of this WordPress installation (or the sub-site, if you are running a multi-site). Appending a (*) wildcard character will match all requests with the base. Warning: Enabling regular expressions will disable wildcards and completely change the way the * symbol is interpretted.", 'safe-redirect-manager' ); ?></p>
644
- </p>
645
-
646
- <p>
647
- <label for="srm<?php echo $this->meta_key_redirect_to; ?>"><?php _e( 'Redirect To:', 'safe-redirect-manager' ); ?></label><br />
648
- <input class="widefat" type="text" name="srm<?php echo $this->meta_key_redirect_to; ?>" id="srm<?php echo $this->meta_key_redirect_to; ?>" value="<?php echo esc_attr( $redirect_to ); ?>" /><br />
649
- <p class="description"><?php _e( "This can be a URL or a path relative to the root of your website (not your WordPress installation). Ending with a (*) wildcard character will append the request match to the redirect.", 'safe-redirect-manager'); ?></p>
650
- </p>
651
-
652
- <p>
653
- <label for="srm<?php echo $this->meta_key_redirect_status_code; ?>"><?php _e( 'HTTP Status Code:', 'safe-redirect-manager' ); ?></label>
654
- <select name="srm<?php echo $this->meta_key_redirect_status_code; ?>" id="srm<?php echo $this->meta_key_redirect_status_code; ?>">
655
- <?php foreach ( $this->valid_status_codes as $code ) : ?>
656
- <option value="<?php echo esc_attr( $code ); ?>" <?php selected( $status_code, $code ); ?>><?php echo esc_html( $code . ' ' . $this->status_code_labels[$code] ); ?></option>
657
- <?php endforeach; ?>
658
- </select>
659
- <em><?php _e( "If you don't know what this is, leave it as is.", 'safe-redirect-manager' ); ?></em>
660
- </p>
661
- <?php
662
- }
663
-
664
- /**
665
- * Localize plugin
666
- *
667
- * @since 1.0
668
- * @uses load_plugin_textdomain, plugin_basename
669
- * @return void
670
- */
671
- public function action_init() {
672
- load_plugin_textdomain( 'safe-redirect-manager', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
673
-
674
- $this->default_max_redirects = apply_filters( 'srm_max_redirects', $this->default_max_redirects );
675
- }
676
-
677
- /**
678
- * Apply whitelisted hosts to allowed_redirect_hosts filter
679
- *
680
- * @since 1.0
681
- * @param array $content
682
- * @return array
683
- */
684
- public function filter_allowed_redirect_hosts( $content ) {
685
-
686
- foreach ( $this->whitelist_hosts as $host ) {
687
- $without_www = preg_replace( '/^www\./i', '', $host );
688
- $with_www = 'www.' . $without_www;
689
-
690
- if ( ! in_array( $without_www, $content ) ) $content[] = $without_www;
691
- if ( ! in_array( $with_www, $content ) ) $content[] = $with_www;
692
- }
693
-
694
- return $content;
695
- }
696
-
697
- /**
698
- * Get redirects from the database
699
- *
700
- * @since 1.6
701
- * @param array $args Any arguments to filter by
702
- * @return array $redirects An array of redirects
703
- */
704
- public function get_redirects( $args = array() ) {
705
-
706
- $redirects = array();
707
-
708
- if ( $this->default_max_redirects > 50 )
709
- $posts_per_page = 50;
710
- else
711
- $posts_per_page = $this->default_max_redirects;
712
-
713
- $i = 1;
714
- do {
715
-
716
- $defaults = array(
717
- 'posts_per_page' => $posts_per_page,
718
- 'post_status' => 'publish',
719
- 'paged' => $i,
720
- );
721
-
722
- $query_args = array_merge( $defaults, $args );
723
-
724
- // Some arguments that don't need to be configurable
725
- $query_args['post_type'] = $this->redirect_post_type;
726
- $query_args['no_found_rows'] = false;
727
- $query_args['update_term_cache'] = false;
728
-
729
- $redirect_query = new WP_Query( $query_args );
730
-
731
- foreach( $redirect_query->posts as $redirect ) {
732
- $redirects[] = array(
733
- 'ID' => $redirect->ID,
734
- 'post_status' => $redirect->post_status,
735
- 'redirect_from' => get_post_meta( $redirect->ID, $this->meta_key_redirect_from, true ),
736
- 'redirect_to' => get_post_meta( $redirect->ID, $this->meta_key_redirect_to, true ),
737
- 'status_code' => (int)get_post_meta( $redirect->ID, $this->meta_key_redirect_status_code, true ),
738
- 'enable_regex' => (bool)get_post_meta( $redirect->ID, $this->meta_key_enable_redirect_from_regex, true ),
739
- );
740
- }
741
-
742
- if ( count( $redirects ) == $this->default_max_redirects
743
- || count( $redirect_query->posts ) < $posts_per_page )
744
- $build = false;
745
- else
746
- $build = true;
747
-
748
- $i++;
749
-
750
- } while ( $build );
751
-
752
- return $redirects;
753
- }
754
-
755
- /**
756
- * Force update on the redirect cache and return cache
757
- *
758
- * @since 1.0
759
- * @uses set_transient, get_post_meta, the_post, have_posts, get_the_ID
760
- * @return array
761
- */
762
- public function update_redirect_cache() {
763
-
764
- $redirect_cache = $this->get_redirects();
765
-
766
- set_transient( $this->cache_key_redirects, $redirect_cache );
767
-
768
- return $redirect_cache;
769
- }
770
-
771
- /**
772
- * Check current url against redirects
773
- *
774
- * @since 1.0
775
- * @uses esc_url_raw, wp_safe_redirect, untrailingslashit, get_transient, add_filter
776
- * @return void
777
- */
778
- public function action_parse_request() {
779
-
780
- // Don't redirect from wp-admin
781
- if ( is_admin() )
782
- return;
783
-
784
- // get redirects from cache or recreate it
785
- if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
786
- $redirects = $this->update_redirect_cache();
787
- }
788
-
789
- // If we have no redirects, there is no need to continue
790
- if ( empty( $redirects ) )
791
- return;
792
-
793
- // get requested path and add a / before it
794
- $requested_path = esc_url_raw( $_SERVER['REQUEST_URI'] );
795
- $requested_path = stripslashes( $requested_path );
796
-
797
- /**
798
- * If WordPress resides in a directory that is not the public root, we have to chop
799
- * the pre-WP path off the requested path.
800
- */
801
- $parsed_site_url = parse_url( site_url() );
802
- if ( isset( $parsed_site_url['path'] ) && '/' != $parsed_site_url['path'] ) {
803
- $requested_path = preg_replace( '@' . $parsed_site_url['path'] . '@i', '', $requested_path, 1 );
804
- }
805
-
806
- // Allow redirects to be filtered
807
- $redirects = apply_filters( 'srm_registered_redirects', $redirects, $requested_path );
808
-
809
- foreach ( (array)$redirects as $redirect ) {
810
-
811
- $redirect_from = untrailingslashit( $redirect['redirect_from'] );
812
- if ( empty( $redirect_from ) )
813
- $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
814
-
815
- $redirect_to = $redirect['redirect_to'];
816
- $status_code = $redirect['status_code'];
817
- $enable_regex = ( isset( $redirect['enable_regex'] ) ) ? $redirect['enable_regex'] : false;
818
-
819
- if ( apply_filters( 'srm_case_insensitive_redirects', true ) ) {
820
- $requested_path = strtolower( $requested_path );
821
- $redirect_from = strtolower( $redirect_from );
822
- }
823
-
824
- // check if requested path is the same as the redirect from path
825
- if ( $enable_regex ) {
826
- $matched_path = preg_match( '@' . $redirect_from . '@', $requested_path );
827
- } else {
828
- $matched_path = ( untrailingslashit( $requested_path ) == $redirect_from );
829
-
830
- // check if the redirect_from ends in a wildcard
831
- if ( !$matched_path && (strrpos( $redirect_from, '*' ) === strlen( $redirect_from ) - 1) ) {
832
- $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
833
-
834
- // mark as match if requested path matches the base of the redirect from
835
- $matched_path = (substr( $requested_path, 0, strlen( $wildcard_base ) ) == $wildcard_base);
836
- if ( (strrpos( $redirect_to, '*' ) == strlen( $redirect_to ) - 1 ) ) {
837
- $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
838
- }
839
- }
840
- }
841
-
842
- if ( $matched_path ) {
843
- // whitelist redirect to host if necessary
844
- $parsed_redirect = parse_url( $redirect_to );
845
- if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
846
- $this->whitelist_hosts[] = $parsed_redirect['host'];
847
- add_filter( 'allowed_redirect_hosts' , array( $this, 'filter_allowed_redirect_hosts' ) );
848
- }
849
-
850
- header("X-Safe-Redirect-Manager: true");
851
-
852
- // Allow for regex replacement in $redirect_to
853
- if ( $enable_regex ) {
854
- $redirect_to = preg_replace( '@' . $redirect_from . '@', $redirect_to, $requested_path );
855
- }
856
-
857
- // if we have a valid status code, then redirect with it
858
- if ( in_array( $status_code, $this->valid_status_codes ) )
859
- wp_safe_redirect( esc_url_raw( $redirect_to ), $status_code );
860
- else
861
- wp_safe_redirect( esc_url_raw( $redirect_to ) );
862
- exit;
863
- }
864
- }
865
- }
866
-
867
- /**
868
- * Sanitize redirect to path
869
- *
870
- * The only difference between this function and just calling esc_url_raw is
871
- * esc_url_raw( 'test' ) == 'http://test', whereas sanitize_redirect_path( 'test' ) == '/test'
872
- *
873
- * @since 1.0
874
- * @param string $path
875
- * @uses esc_url_raw
876
- * @return string
877
- */
878
- public function sanitize_redirect_to( $path ) {
879
- $path = trim( $path );
880
-
881
- if ( preg_match( '/^www\./i', $path ) )
882
- $path = 'http://' . $path;
883
-
884
- if ( ! preg_match( '/^https?:\/\//i', $path ) )
885
- if ( strpos( $path, '/' ) !== 0 )
886
- $path = '/' . $path;
887
-
888
- return esc_url_raw( $path );
889
- }
890
-
891
- /**
892
- * Sanitize redirect from path
893
- *
894
- * @since 1.0
895
- * @param string $path
896
- * @param boolean $allow_regex
897
- * @uses esc_url_raw
898
- * @return string
899
- */
900
- public function sanitize_redirect_from( $path, $allow_regex = false ) {
901
-
902
- $path = trim( $path );
903
-
904
- if ( empty( $path ) )
905
- return '';
906
-
907
- // dont accept paths starting with a .
908
- if ( ! $allow_regex && strpos( $path, '.' ) === 0 )
909
- return '';
910
-
911
- // turn path in to absolute
912
- if ( preg_match( '/https?:\/\//i', $path ) )
913
- $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
914
- elseif ( ! $allow_regex && strpos( $path, '/' ) !== 0 )
915
- $path = '/' . $path;
916
-
917
- // the @ symbol will break our regex engine
918
- $path = str_replace( '@', '', $path );
919
-
920
- return $path;
921
- }
922
-
923
- /**
924
- * Return a permalink for a redirect post, which is the "redirect from"
925
- * URL for that redirect.
926
- *
927
- * @since 1.7
928
- * @param string $permalink The permalink
929
- * @param object $post A Post object
930
- * @uses home_url, get_post_meta
931
- * @return string The permalink
932
- */
933
- public function filter_post_type_link( $permalink, $post ) {
934
- if ( $this->redirect_post_type != $post->post_type )
935
- return $permalink;
936
-
937
- // We can't do anything to provide a permalink
938
- // for regex enabled redirects.
939
- if ( get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true ) )
940
- return $permalink;
941
-
942
- // We can't do anything if there is a wildcard in the redirect from
943
- $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
944
- if ( false !== strpos( $redirect_from, '*' ) )
945
- return $permalink;
946
-
947
- // Use default permalink if no $redirect_from exists - this prevents duplicate GUIDs
948
- if ( empty( $redirect_from ) ) {
949
- return $permalink;
950
- }
951
-
952
- return home_url( $redirect_from );
953
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  }
955
 
956
  global $safe_redirect_manager;
957
- $safe_redirect_manager = new SRM_Safe_Redirect_Manager();
4
  Plugin URI: http://www.10up.com
5
  Description: Easily and safely manage HTTP redirects.
6
  Author: Taylor Lovett (10up LLC), VentureBeat
7
+ Version: 1.7.3
8
  Author URI: http://www.10up.com
9
 
10
  GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
26
  */
27
 
28
  if ( defined( 'WP_CLI' ) && WP_CLI )
29
+ require_once dirname( __FILE__ ) . '/inc/wp-cli.php';
30
 
31
  class SRM_Safe_Redirect_Manager {
32
 
33
+ public $redirect_post_type = 'redirect_rule';
34
+ private $redirect_nonce_name = 'srm_redirect_nonce';
35
+ private $redirect_nonce_action = 'srm-save-redirect-meta';
36
+
37
+ public $meta_key_redirect_from = '_redirect_rule_from';
38
+ public $meta_key_redirect_to = '_redirect_rule_to';
39
+ public $meta_key_redirect_status_code = '_redirect_rule_status_code';
40
+ public $meta_key_enable_redirect_from_regex = '_redirect_rule_from_regex';
41
+
42
+ public $cache_key_redirects = '_srm_redirects';
43
+
44
+ public $valid_status_codes = array( 301, 302, 303, 307, 403, 404 );
45
+
46
+ public $status_code_labels = array(
47
+ 301 => 'Moved Permanently',
48
+ 302 => 'Found',
49
+ 303 => 'See Other',
50
+ 307 => 'Temporary Redirect',
51
+ 403 => 'Forbidden',
52
+ 404 => 'Not Found',
53
+ );
54
+
55
+ private $whitelist_hosts = array();
56
+
57
+ public $default_max_redirects = 150;
58
+
59
+ /**
60
+ * Sets up redirect manager
61
+ *
62
+ * @since 1.0
63
+ * @uses add_action, add_filter
64
+ * @return object
65
+ */
66
+ public function __construct() {
67
+ add_action( 'init', array( $this, 'action_init_load_textdomain' ), 9 );
68
+ add_action( 'init', array( $this, 'action_init' ) );
69
+ add_action( 'init', array( $this, 'action_register_post_types' ) );
70
+ add_action( 'parse_request', array( $this, 'action_parse_request' ), 0 );
71
+ add_action( 'save_post', array( $this, 'action_save_post' ) );
72
+ add_filter( 'manage_' . $this->redirect_post_type . '_posts_columns' , array( $this, 'filter_redirect_columns' ) );
73
+ add_action( 'manage_' . $this->redirect_post_type . '_posts_custom_column' , array( $this, 'action_custom_redirect_columns' ), 10, 2 );
74
+ add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
75
+ add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
76
+ add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
77
+ add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
78
+ add_filter( 'bulk_actions-edit-' . $this->redirect_post_type, array( $this, 'filter_bulk_actions' ) );
79
+ add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
80
+ add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
81
+ add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
82
+ add_filter( 'post_type_link', array( $this, 'filter_post_type_link' ), 10, 2 );
83
+
84
+ // Search filters
85
+ add_filter( 'posts_join', array( $this, 'filter_search_join' ) );
86
+ add_filter( 'posts_where', array( $this, 'filter_search_where' ) );
87
+ add_filter( 'posts_distinct', array( $this, 'filter_search_distinct' ) );
88
+ }
89
+
90
+ /**
91
+ * Localize plugin
92
+ *
93
+ * @since 1.7
94
+ * @uses load_plugin_textdomain
95
+ * @return void
96
+ */
97
+ public function action_init_load_textdomain() {
98
+ load_plugin_textdomain( 'safe-redirect-manager', false, basename( dirname( __FILE__ ) ) . '/languages' );
99
+ }
100
+
101
+ /**
102
+ * Join posts table with postmeta table on search
103
+ *
104
+ * @since 1.2
105
+ * @param string $join
106
+ * @uses get_query_var
107
+ * @return string
108
+ */
109
+ public function filter_search_join( $join ) {
110
+ global $wp_query;
111
+
112
+ if ( empty( $wp_query ) || $this->redirect_post_type != get_query_var( 'post_type' ) )
113
+ return $join;
114
+
115
+ global $wpdb;
116
+
117
+ $s = get_query_var( 's' );
118
+ if ( ! empty( $s ) ) {
119
+ $join .= " LEFT JOIN $wpdb->postmeta AS m ON ($wpdb->posts.ID = m.post_id) ";
120
+ }
121
+ return $join;
122
+ }
123
+
124
+ /**
125
+ * Return distinct search results
126
+ *
127
+ * @since 1.2
128
+ * @param string $distinct
129
+ * @uses get_query_var
130
+ * @return string
131
+ */
132
+ public function filter_search_distinct( $distinct ) {
133
+ global $wp_query;
134
+
135
+ if ( empty( $wp_query ) || $this->redirect_post_type != get_query_var( 'post_type' ) )
136
+ return $distinct;
137
+
138
+ return 'DISTINCT';
139
+ }
140
+
141
+ /**
142
+ * Join posts table with postmeta table on search
143
+ *
144
+ * @since 1.2
145
+ * @param string $where
146
+ * @uses is_search, get_query_var
147
+ * @return string
148
+ */
149
+ public function filter_search_where( $where ) {
150
+ global $wp_query;
151
+
152
+ if ( empty( $wp_query ) || $this->redirect_post_type != get_query_var( 'post_type' ) || ! is_search() || empty( $where ) )
153
+ return $where;
154
+
155
+ $exact = get_query_var( 'exact' );
156
+ $n = ( ! empty( $exact ) ) ? '' : '%';
157
+
158
+ $search = '';
159
+ $seperator = '';
160
+ $terms = $this->get_search_terms();
161
+ $search .= '(';
162
+
163
+ // we check the meta values against each term in the search
164
+ foreach ( $terms as $term ) {
165
+ $search .= $seperator;
166
+ $search .= sprintf( "( ( m.meta_value LIKE '%s%s%s' AND m.meta_key = '%s') OR ( m.meta_value LIKE '%s%s%s' AND m.meta_key = '%s') )", $n, $term, $n, $this->meta_key_redirect_from, $n, $term, $n, $this->meta_key_redirect_to );
167
+ $seperator = ' OR ';
168
+ }
169
+
170
+ $search .= ')';
171
+
172
+ $where = preg_replace( '/\(\(\(.*?\)\)\)/is', '((' . $search . '))', $where );
173
+
174
+ return $where;
175
+ }
176
+
177
+ /**
178
+ * Get an array of search terms
179
+ *
180
+ * @since 1.2
181
+ * @uses get_query_var
182
+ * @return array
183
+ */
184
+ private function get_search_terms() {
185
+ $s = get_query_var( 's' );
186
+
187
+ if ( ! empty( $s ) ) {
188
+ preg_match_all( '/".*?("|$)|((?<=[\\s",+])|^)[^\\s",+]+/', stripslashes( $s ), $matches );
189
+ $search_terms = array_map( create_function( '$a', 'return trim( $a, "\\"\'\\n\\r " );' ), $matches[0] );
190
+ }
191
+ return $search_terms;
192
+ }
193
+
194
+ /**
195
+ * Swap tools logo for plugin logo
196
+ *
197
+ * @since 1.1
198
+ * @uses plugins_url
199
+ * @return void
200
+ */
201
+ public function action_print_logo_css() {
202
+ if ( $this->is_plugin_page() ) {
203
+ ?>
204
+ <style type="text/css">
205
+ #icon-tools {
206
+ background: url("<?php echo plugins_url( 'images/icon32x32.png', __FILE__ ); ?>") no-repeat top left !important;
207
+ margin-right: 0;
208
+ }
209
+ #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group {
210
+ display: none;
211
+ }
212
+ #srm<?php echo $this->meta_key_redirect_from; ?> {
213
+ width: 60%;
214
+ }
215
+ </style>
216
+ <?php
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Limit the bulk actions available in the Manage Redirects view
222
+ *
223
+ * @since 1.0
224
+ * @return array
225
+ */
226
+ public function filter_bulk_actions( $actions ) {
227
+
228
+ // No bulk editing at this time
229
+ unset( $actions['edit'] );
230
+
231
+ return $actions;
232
+ }
233
+
234
+ /**
235
+ * Creates a redirect post, this function will be useful for import/exports scripts
236
+ *
237
+ * @param string $redirect_from
238
+ * @param string $redirect_to
239
+ * @param int $status_code
240
+ * @param bool $enable_regex
241
+ * @param string $post_status
242
+ * @since 1.3
243
+ * @uses wp_insert_post, update_post_meta
244
+ * @return int|WP_Error
245
+ */
246
+ public function create_redirect( $redirect_from, $redirect_to, $status_code = 302, $enable_regex = false, $post_status = 'publish' ) {
247
+ global $wpdb;
248
+
249
+ $sanitized_redirect_from = $this->sanitize_redirect_from( $redirect_from );
250
+ $sanitized_redirect_to = $this->sanitize_redirect_to( $redirect_to );
251
+ $sanitized_status_code = absint( $status_code );
252
+ $sanitized_enable_regex = (bool)$enable_regex;
253
+ $sanitized_post_status = sanitize_key( $post_status );
254
+
255
+ // check and make sure no parameters are empty or invalid after sanitation
256
+ if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) )
257
+ return new WP_Error( 'invalid-argument', __( 'Redirect from and/or redirect to arguments are invalid.', 'safe-redirect-manager' ) );
258
+
259
+ if ( ! in_array( $sanitized_status_code, $this->valid_status_codes ) )
260
+ return new WP_Error( 'invalid-argument', __( 'Invalid status code.', 'safe-redirect-manager' ) );
261
+
262
+ // Check to ensure this redirect doesn't already exist
263
+ if ( $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%s", $this->meta_key_redirect_from, $sanitized_redirect_from ) ) )
264
+ return new WP_Error( 'duplicate-redirect', sprintf( __( 'Redirect already exists for %s', 'safe-redirect-manager' ), $sanitized_redirect_from ) );
265
+
266
+ // create the post
267
+ $post_args = array(
268
+ 'post_type' => $this->redirect_post_type,
269
+ 'post_status' => $sanitized_post_status,
270
+ 'post_author' => 1
271
+ );
272
+
273
+ $post_id = wp_insert_post( $post_args );
274
+
275
+ if ( 0 >= $post_id )
276
+ return new WP_Error( 'error-creating', __( 'An error occurred creating the redirect.', 'safe-redirect-manager' ) );
277
+
278
+ // update the posts meta info
279
+ update_post_meta( $post_id, $this->meta_key_redirect_from, $sanitized_redirect_from );
280
+ update_post_meta( $post_id, $this->meta_key_redirect_to, $sanitized_redirect_to );
281
+ update_post_meta( $post_id, $this->meta_key_redirect_status_code, $sanitized_status_code );
282
+ update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $sanitized_enable_regex );
283
+
284
+ // We need to update the cache after creating this redirect
285
+ $this->update_redirect_cache();
286
+
287
+ return $post_id;
288
+ }
289
+
290
+ /**
291
+ * Whether or not this is an admin page specific to the plugin
292
+ *
293
+ * @since 1.1
294
+ * @uses get_post_type
295
+ * @return bool
296
+ */
297
+ private function is_plugin_page() {
298
+ return (bool) ( get_post_type() == $this->redirect_post_type || ( isset( $_GET['post_type'] ) && $this->redirect_post_type == $_GET['post_type'] ) );
299
+ }
300
+
301
+ /**
302
+ * Echoes admin message if redirect chains exist
303
+ *
304
+ * @since 1.0
305
+ * @uses apply_filters
306
+ * @return void
307
+ */
308
+ public function action_redirect_chain_alert() {
309
+ global $hook_suffix;
310
+ if ( $this->is_plugin_page() ) {
311
+
312
+ /**
313
+ * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
314
+ * will be running slow. Let's disable it by default.
315
+ */
316
+ if ( apply_filters( 'srm_check_for_possible_redirect_loops', false ) ) {
317
+ if ( $this->check_for_possible_redirect_loops() ) {
318
+ ?>
319
+ <div class="updated">
320
+ <p><?php _e( 'Safe Redirect Manager Warning: Possible redirect loops and/or chains have been created.', 'safe-redirect-manager' ); ?></p>
321
+ </div>
322
+ <?php
323
+ }
324
+ } if ( $this->max_redirects_reached() ) {
325
+ ?>
326
+ <?php if ( 'post-new.php' == $hook_suffix ) : ?><style type="text/css">#post { display: none; }</style><?php endif; ?>
327
+ <div class="error">
328
+ <p><?php _e( 'Safe Redirect Manager Error: You have reached the maximum allowable number of redirects', 'safe-redirect-manager' ); ?></p>
329
+ </div>
330
+ <?php
331
+ }
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Returns true if max redirects have been reached
337
+ *
338
+ * @since 1.0
339
+ * @uses apply_filters, get_transient
340
+ * @return bool
341
+ */
342
+ public function max_redirects_reached() {
343
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
344
+ $redirects = $this->update_redirect_cache();
345
+ }
346
+
347
+ return ( count( $redirects ) >= $this->default_max_redirects );
348
+ }
349
+
350
+ /**
351
+ * Check for potential redirect loops or chains
352
+ *
353
+ * @since 1.0
354
+ * @uses home_url, get_transient
355
+ * @return boolean
356
+ */
357
+ public function check_for_possible_redirect_loops() {
358
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
359
+ $redirects = $this->update_redirect_cache();
360
+ }
361
+
362
+ $current_url = parse_url( home_url() );
363
+ $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
364
+
365
+ foreach ( $redirects as $redirect ) {
366
+ $redirect_from = $redirect['redirect_from'];
367
+
368
+ // check redirect from against all redirect to's
369
+ foreach ( $redirects as $compare_redirect ) {
370
+ $redirect_to = $compare_redirect['redirect_to'];
371
+
372
+ $redirect_url = parse_url( $redirect_to );
373
+ $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
374
+
375
+ // check if we are redirecting locally
376
+ if ( empty( $redirect_host ) || $redirect_host == $this_host ) {
377
+ $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
378
+ $redirect_to_url = $redirect_to;
379
+ if ( ! preg_match( '/https?:\/\//i', $redirect_to_url ) )
380
+ $redirect_to_url = $this_host . $redirect_to_url;
381
+ else
382
+ $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
383
+
384
+ // possible loop/chain found
385
+ if ( $redirect_to_url == $redirect_from_url )
386
+ return true;
387
+ }
388
+ }
389
+ }
390
+
391
+ return false;
392
+ }
393
+
394
+ /**
395
+ * Filters title out for redirect from in post manager
396
+ *
397
+ * @since 1.0
398
+ * @param string $title
399
+ * @param int $post_id
400
+ * @uses is_admin, get_post_meta
401
+ * @return string
402
+ */
403
+ public function filter_admin_title( $title, $post_id = 0 ) {
404
+ if ( ! is_admin() )
405
+ return $title;
406
+
407
+ $redirect = get_post( $post_id );
408
+ if ( empty( $redirect ) )
409
+ return $title;
410
+
411
+ if ( $redirect->post_type != $this->redirect_post_type )
412
+ return $title;
413
+
414
+ $redirect_from = get_post_meta( $post_id, $this->meta_key_redirect_from, true );
415
+ if ( ! empty( $redirect_from ) )
416
+ return $redirect_from;
417
+
418
+ return $title;
419
+ }
420
+
421
+ /**
422
+ * Customizes updated messages for redirects
423
+ *
424
+ * @since 1.0
425
+ * @param array $messages
426
+ * @uses esc_url, get_permalink, add_query_var, wp_post_revision_title
427
+ * @return array
428
+ */
429
+ public function filter_redirect_updated_messages( $messages ) {
430
+ global $post, $post_ID;
431
+
432
+ $messages[$this->redirect_post_type] = array(
433
+ 0 => '', // Unused. Messages start at index 1.
434
+ 1 => sprintf( __( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
435
+ 2 => __( 'Custom field updated.', 'safe-redirect-manager' ),
436
+ 3 => __( 'Custom field deleted.', 'safe-redirect-manager' ),
437
+ 4 => __( 'Redirect rule updated.', 'safe-redirect-manager' ),
438
+ /* translators: %s: date and time of the revision */
439
+ 5 => isset( $_GET['revision'] ) ? sprintf( __('Redirect rule restored to revision from %s', 'safe-redirect-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
440
+ 6 => sprintf( __( 'Redirect rule published.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
441
+ 7 => __( 'Redirect rule saved.', 'safe-redirect-manager' ),
442
+ 8 => sprintf( __( 'Redirect rule submitted.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
443
+ 9 => sprintf( __( 'Redirect rule scheduled for: <strong>%1$s</strong>.', 'safe-redirect-manager' ),
444
+ // translators: Publish box date format, see http://php.net/date
445
+ date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
446
+ 10 => sprintf( __( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
447
+ );
448
+
449
+ return $messages;
450
+ }
451
+
452
+ /**
453
+ * Clear redirect cache if appropriate post type is transitioned
454
+ *
455
+ * @since 1.0
456
+ * @param string $new_status
457
+ * @param string $old_status
458
+ * @param object $post
459
+ * @uses delete_transient
460
+ * @return void
461
+ */
462
+ public function action_transition_post_status( $new_status, $old_status, $post ) {
463
+ if ( ! is_object( $post ) )
464
+ return;
465
+
466
+ // recreate redirect cache
467
+ if ( $this->redirect_post_type == $post->post_type ) {
468
+ delete_transient( $this->cache_key_redirects );
469
+ $this->update_redirect_cache();
470
+ }
471
+ }
472
+
473
+ /**
474
+ * Displays custom columns on redirect manager screen
475
+ *
476
+ * @since 1.0
477
+ * @param string $column
478
+ * @param int $post_id
479
+ * @uses get_post_meta, esc_html, admin_url
480
+ * @return void
481
+ */
482
+ public function action_custom_redirect_columns( $column, $post_id ) {
483
+ if ( 'srm' . $this->meta_key_redirect_to == $column ) {
484
+ echo esc_html( get_post_meta( $post_id, $this->meta_key_redirect_to, true ) );
485
+ } elseif ( 'srm' . $this->meta_key_redirect_status_code == $column ) {
486
+ echo absint( get_post_meta( $post_id, $this->meta_key_redirect_status_code, true ) );
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Add new columns to manage redirect screen
492
+ *
493
+ * @since 1.0
494
+ * @param array $columns
495
+ * @return array
496
+ */
497
+ public function filter_redirect_columns( $columns ) {
498
+ $columns['srm' . $this->meta_key_redirect_to] = __( 'Redirect To', 'safe-redirect-manager' );
499
+ $columns['srm'. $this->meta_key_redirect_status_code] = __( 'HTTP Status Code', 'safe-redirect-manager' );
500
+
501
+ // Change the title column
502
+ $columns['title'] = __( 'Redirect From', 'safe-redirect-manager' );
503
+
504
+ // Move date column to the back
505
+ unset( $columns['date'] );
506
+ $columns['date'] = __( 'Date', 'safe-redirect-manager' );
507
+
508
+ return $columns;
509
+ }
510
+
511
+ /**
512
+ * Saves meta info for redirect rules
513
+ *
514
+ * @since 1.0
515
+ * @param int $post_id
516
+ * @uses current_user_can, get_post_type, wp_verify_nonce, update_post_meta, delete_post_meta
517
+ * @return void
518
+ */
519
+ public function action_save_post( $post_id ) {
520
+ if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' == get_post_type( $post_id ) )
521
+ return;
522
+
523
+ // Update post meta for redirect rules
524
+ if ( ! empty( $_POST[$this->redirect_nonce_name] ) && wp_verify_nonce( $_POST[$this->redirect_nonce_name], $this->redirect_nonce_action ) ) {
525
+
526
+ if ( ! empty( $_POST['srm' . $this->meta_key_enable_redirect_from_regex] ) ) {
527
+ $allow_regex = (bool) $_POST['srm' . $this->meta_key_enable_redirect_from_regex];
528
+ update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $allow_regex );
529
+ } else {
530
+ $allow_regex = false;
531
+ delete_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex );
532
+ }
533
+
534
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_from] ) ) {
535
+ update_post_meta( $post_id, $this->meta_key_redirect_from, $this->sanitize_redirect_from( $_POST['srm' . $this->meta_key_redirect_from], $allow_regex ) );
536
+ } else {
537
+ delete_post_meta( $post_id, $this->meta_key_redirect_from );
538
+ }
539
+
540
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_to] ) ) {
541
+ update_post_meta( $post_id, $this->meta_key_redirect_to, $this->sanitize_redirect_to( $_POST['srm' . $this->meta_key_redirect_to] ) );
542
+ } else {
543
+ delete_post_meta( $post_id, $this->meta_key_redirect_to );
544
+ }
545
+
546
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_status_code] ) ) {
547
+ update_post_meta( $post_id, $this->meta_key_redirect_status_code, absint( $_POST['srm' . $this->meta_key_redirect_status_code] ) );
548
+ } else {
549
+ delete_post_meta( $post_id, $this->meta_key_redirect_status_code );
550
+ }
551
+
552
+ /**
553
+ * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
554
+ * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
555
+ * redirect info is saved, updating the cache before it is not sufficient.
556
+ */
557
+ $this->update_redirect_cache();
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Registers post types for plugin
563
+ *
564
+ * @since 1.0
565
+ * @uses register_post_type, _x, plugins_url, apply_filters
566
+ * @return void
567
+ */
568
+ public function action_register_post_types() {
569
+ $redirect_labels = array(
570
+ 'name' => _x( 'Safe Redirect Manager', 'post type general name', 'safe-redirect-manager' ),
571
+ 'singular_name' => _x( 'Redirect', 'post type singular name', 'safe-redirect-manager' ),
572
+ 'add_new' => _x( 'Create Redirect Rule', $this->redirect_post_type, 'safe-redirect-manager' ),
573
+ 'add_new_item' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
574
+ 'edit_item' => __( 'Edit Redirect Rule', 'safe-redirect-manager' ),
575
+ 'new_item' => __( 'New Redirect Rule', 'safe-redirect-manager' ),
576
+ 'all_items' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
577
+ 'view_item' => __( 'View Redirect Rule', 'safe-redirect-manager' ),
578
+ 'search_items' => __( 'Search Redirects', 'safe-redirect-manager' ),
579
+ 'not_found' => __( 'No redirect rules found.', 'safe-redirect-manager' ),
580
+ 'not_found_in_trash' => __( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
581
+ 'parent_item_colon' => '',
582
+ 'menu_name' => __( 'Safe Redirect Manager', 'safe-redirect-manager' )
583
+ );
584
+ $redirect_capability = 'manage_options';
585
+ $redirect_capability = apply_filters( 'srm_restrict_to_capability', $redirect_capability );
586
+ $capabilities = array(
587
+ 'edit_post' => $redirect_capability,
588
+ 'read_post' => $redirect_capability,
589
+ 'delete_post' => $redirect_capability,
590
+ 'edit_posts' => $redirect_capability,
591
+ 'edit_others_posts' => $redirect_capability,
592
+ 'publish_posts' => $redirect_capability,
593
+ 'read_private_posts' => $redirect_capability
594
+ );
595
+
596
+ $redirect_args = array(
597
+ 'labels' => $redirect_labels,
598
+ 'public' => false,
599
+ 'publicly_queryable' => true,
600
+ 'show_ui' => true,
601
+ 'show_in_menu' => 'tools.php',
602
+ 'query_var' => false,
603
+ 'rewrite' => false,
604
+ 'capability_type' => 'post',
605
+ 'capabilities' => $capabilities,
606
+ 'has_archive' => false,
607
+ 'hierarchical' => false,
608
+ 'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
609
+ 'menu_position' => 80,
610
+ 'supports' => array( '' )
611
+ );
612
+ register_post_type( $this->redirect_post_type, $redirect_args );
613
+ }
614
+
615
+ /**
616
+ * Registers meta boxes for redirect rule post type
617
+ *
618
+ * @since 1.0
619
+ * @uses add_meta_box
620
+ * @return void
621
+ */
622
+ public function action_redirect_rule_metabox() {
623
+ add_meta_box( 'redirect_settings', __( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), $this->redirect_post_type, 'normal', 'core' );
624
+ }
625
+
626
+ /**
627
+ * Echoes HTML for redirect rule meta box
628
+ *
629
+ * @since 1.0
630
+ * @param object $post
631
+ * @uses wp_nonce_field, get_post_meta, esc_attr, selected
632
+ * @return void
633
+ */
634
+ public function redirect_rule_metabox( $post ) {
635
+ wp_nonce_field( $this->redirect_nonce_action, $this->redirect_nonce_name );
636
+
637
+ $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
638
+ $redirect_to = get_post_meta( $post->ID, $this->meta_key_redirect_to, true );
639
+ $status_code = get_post_meta( $post->ID, $this->meta_key_redirect_status_code, true );
640
+ $enable_regex = get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true );
641
+ if ( empty( $status_code ) )
642
+ $status_code = 302;
643
+ ?>
644
+ <p>
645
+ <label for="srm<?php echo $this->meta_key_redirect_from; ?>"><?php _e( 'Redirect From:', 'safe-redirect-manager' ); ?></label><br />
646
+ <input type="text" name="srm<?php echo $this->meta_key_redirect_from; ?>" id="srm<?php echo $this->meta_key_redirect_from; ?>" value="<?php echo esc_attr( $redirect_from ); ?>" />
647
+ <input type="checkbox" name="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>" id="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>" <?php checked( true, (bool) $enable_regex ); ?> value="1" />
648
+ <label for="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>"><?php _e( 'Enable Regular Expressions (advanced)', 'safe-redirect-manager' ); ?></label><br />
649
+ <p class="description"><?php _e( "This path should be relative to the root of this WordPress installation (or the sub-site, if you are running a multi-site). Appending a (*) wildcard character will match all requests with the base. Warning: Enabling regular expressions will disable wildcards and completely change the way the * symbol is interpretted.", 'safe-redirect-manager' ); ?></p>
650
+ </p>
651
+
652
+ <p>
653
+ <label for="srm<?php echo $this->meta_key_redirect_to; ?>"><?php _e( 'Redirect To:', 'safe-redirect-manager' ); ?></label><br />
654
+ <input class="widefat" type="text" name="srm<?php echo $this->meta_key_redirect_to; ?>" id="srm<?php echo $this->meta_key_redirect_to; ?>" value="<?php echo esc_attr( $redirect_to ); ?>" /><br />
655
+ <p class="description"><?php _e( "This can be a URL or a path relative to the root of your website (not your WordPress installation). Ending with a (*) wildcard character will append the request match to the redirect.", 'safe-redirect-manager'); ?></p>
656
+ </p>
657
+
658
+ <p>
659
+ <label for="srm<?php echo $this->meta_key_redirect_status_code; ?>"><?php _e( 'HTTP Status Code:', 'safe-redirect-manager' ); ?></label>
660
+ <select name="srm<?php echo $this->meta_key_redirect_status_code; ?>" id="srm<?php echo $this->meta_key_redirect_status_code; ?>">
661
+ <?php foreach ( $this->valid_status_codes as $code ) : ?>
662
+ <option value="<?php echo esc_attr( $code ); ?>" <?php selected( $status_code, $code ); ?>><?php echo esc_html( $code . ' ' . $this->status_code_labels[$code] ); ?></option>
663
+ <?php endforeach; ?>
664
+ </select>
665
+ <em><?php _e( "If you don't know what this is, leave it as is.", 'safe-redirect-manager' ); ?></em>
666
+ </p>
667
+ <?php
668
+ }
669
+
670
+ /**
671
+ * Localize plugin
672
+ *
673
+ * @since 1.0
674
+ * @uses load_plugin_textdomain, plugin_basename
675
+ * @return void
676
+ */
677
+ public function action_init() {
678
+ load_plugin_textdomain( 'safe-redirect-manager', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
679
+
680
+ $this->default_max_redirects = apply_filters( 'srm_max_redirects', $this->default_max_redirects );
681
+ }
682
+
683
+ /**
684
+ * Apply whitelisted hosts to allowed_redirect_hosts filter
685
+ *
686
+ * @since 1.0
687
+ * @param array $content
688
+ * @return array
689
+ */
690
+ public function filter_allowed_redirect_hosts( $content ) {
691
+
692
+ foreach ( $this->whitelist_hosts as $host ) {
693
+ $without_www = preg_replace( '/^www\./i', '', $host );
694
+ $with_www = 'www.' . $without_www;
695
+
696
+ if ( ! in_array( $without_www, $content ) ) $content[] = $without_www;
697
+ if ( ! in_array( $with_www, $content ) ) $content[] = $with_www;
698
+ }
699
+
700
+ return $content;
701
+ }
702
+
703
+ /**
704
+ * Get redirects from the database
705
+ *
706
+ * @since 1.6
707
+ * @param array $args Any arguments to filter by
708
+ * @return array $redirects An array of redirects
709
+ */
710
+ public function get_redirects( $args = array() ) {
711
+
712
+ $redirects = array();
713
+
714
+ if ( $this->default_max_redirects > 50 )
715
+ $posts_per_page = 50;
716
+ else
717
+ $posts_per_page = $this->default_max_redirects;
718
+
719
+ $i = 1;
720
+ do {
721
+
722
+ $defaults = array(
723
+ 'posts_per_page' => $posts_per_page,
724
+ 'post_status' => 'publish',
725
+ 'paged' => $i,
726
+ );
727
+
728
+ $query_args = array_merge( $defaults, $args );
729
+
730
+ // Some arguments that don't need to be configurable
731
+ $query_args['post_type'] = $this->redirect_post_type;
732
+ $query_args['no_found_rows'] = false;
733
+ $query_args['update_term_cache'] = false;
734
+
735
+ $redirect_query = new WP_Query( $query_args );
736
+
737
+ foreach( $redirect_query->posts as $redirect ) {
738
+ $redirects[] = array(
739
+ 'ID' => $redirect->ID,
740
+ 'post_status' => $redirect->post_status,
741
+ 'redirect_from' => get_post_meta( $redirect->ID, $this->meta_key_redirect_from, true ),
742
+ 'redirect_to' => get_post_meta( $redirect->ID, $this->meta_key_redirect_to, true ),
743
+ 'status_code' => (int)get_post_meta( $redirect->ID, $this->meta_key_redirect_status_code, true ),
744
+ 'enable_regex' => (bool)get_post_meta( $redirect->ID, $this->meta_key_enable_redirect_from_regex, true ),
745
+ );
746
+ }
747
+
748
+ if ( count( $redirects ) == $this->default_max_redirects
749
+ || count( $redirect_query->posts ) < $posts_per_page )
750
+ $build = false;
751
+ else
752
+ $build = true;
753
+
754
+ $i++;
755
+
756
+ } while ( $build );
757
+
758
+ return $redirects;
759
+ }
760
+
761
+ /**
762
+ * Force update on the redirect cache and return cache
763
+ *
764
+ * @since 1.0
765
+ * @uses set_transient, get_post_meta, the_post, have_posts, get_the_ID
766
+ * @return array
767
+ */
768
+ public function update_redirect_cache() {
769
+
770
+ $redirect_cache = $this->get_redirects();
771
+
772
+ set_transient( $this->cache_key_redirects, $redirect_cache );
773
+
774
+ return $redirect_cache;
775
+ }
776
+
777
+ /**
778
+ * Check current url against redirects
779
+ *
780
+ * @since 1.0
781
+ * @uses esc_url_raw, wp_safe_redirect, untrailingslashit, get_transient, add_filter
782
+ * @return void
783
+ */
784
+ public function action_parse_request() {
785
+
786
+ // Don't redirect from wp-admin
787
+ if ( is_admin() )
788
+ return;
789
+
790
+ // get redirects from cache or recreate it
791
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
792
+ $redirects = $this->update_redirect_cache();
793
+ }
794
+
795
+ // If we have no redirects, there is no need to continue
796
+ if ( empty( $redirects ) )
797
+ return;
798
+
799
+ // get requested path and add a / before it
800
+ $requested_path = esc_url_raw( $_SERVER['REQUEST_URI'] );
801
+ $requested_path = stripslashes( $requested_path );
802
+
803
+ $unslashed_requested_path = untrailingslashit( $requested_path );
804
+ if ( empty( $unslashed_requested_path ) ){
805
+ $unslashed_requested_path = '/';
806
+ }
807
+
808
+ /**
809
+ * If WordPress resides in a directory that is not the public root, we have to chop
810
+ * the pre-WP path off the requested path.
811
+ */
812
+ $parsed_site_url = parse_url( site_url() );
813
+ if ( isset( $parsed_site_url['path'] ) && '/' != $parsed_site_url['path'] ) {
814
+ $requested_path = preg_replace( '@' . $parsed_site_url['path'] . '@i', '', $requested_path, 1 );
815
+ }
816
+
817
+ // Allow redirects to be filtered
818
+ $redirects = apply_filters( 'srm_registered_redirects', $redirects, $requested_path );
819
+
820
+ foreach ( (array)$redirects as $redirect ) {
821
+
822
+ $redirect_from = untrailingslashit( $redirect['redirect_from'] );
823
+ if ( empty( $redirect_from ) )
824
+ $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
825
+
826
+ $redirect_to = $redirect['redirect_to'];
827
+ $status_code = $redirect['status_code'];
828
+ $enable_regex = ( isset( $redirect['enable_regex'] ) ) ? $redirect['enable_regex'] : false;
829
+
830
+ if ( apply_filters( 'srm_case_insensitive_redirects', true ) ) {
831
+ $requested_path = strtolower( $requested_path );
832
+ $redirect_from = strtolower( $redirect_from );
833
+ }
834
+
835
+ // check if requested path is the same as the redirect from path
836
+ if ( $enable_regex ) {
837
+ $matched_path = preg_match( '@' . $redirect_from . '@', $requested_path );
838
+ } else {
839
+ $matched_path = ( $unslashed_requested_path == $redirect_from );
840
+
841
+ // check if the redirect_from ends in a wildcard
842
+ if ( !$matched_path && (strrpos( $redirect_from, '*' ) === strlen( $redirect_from ) - 1) ) {
843
+ $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
844
+
845
+ // mark as match if requested path matches the base of the redirect from
846
+ $matched_path = (substr( $requested_path, 0, strlen( $wildcard_base ) ) == $wildcard_base);
847
+ if ( (strrpos( $redirect_to, '*' ) == strlen( $redirect_to ) - 1 ) ) {
848
+ $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
849
+ }
850
+ }
851
+ }
852
+
853
+ if ( $matched_path ) {
854
+ // whitelist redirect to host if necessary
855
+ $parsed_redirect = parse_url( $redirect_to );
856
+ if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
857
+ $this->whitelist_hosts[] = $parsed_redirect['host'];
858
+ add_filter( 'allowed_redirect_hosts' , array( $this, 'filter_allowed_redirect_hosts' ) );
859
+ }
860
+
861
+ // Allow for regex replacement in $redirect_to
862
+ if ( $enable_regex ) {
863
+ $redirect_to = preg_replace( '@' . $redirect_from . '@', $redirect_to, $requested_path );
864
+ }
865
+
866
+ $sanitized_redirect_to = esc_url_raw( $redirect_to );
867
+
868
+ do_action( 'srm_do_redirect', $requested_path, $sanitized_redirect_to, $status_code );
869
+
870
+ if ( defined( 'PHPUNIT_SRM_TESTSUITE' ) && PHPUNIT_SRM_TESTSUITE ) {
871
+ // Don't actually redirect if we are testing
872
+ return;
873
+ }
874
+
875
+ header( 'X-Safe-Redirect-Manager: true' );
876
+
877
+ // if we have a valid status code, then redirect with it
878
+ if ( in_array( $status_code, $this->valid_status_codes ) )
879
+ wp_safe_redirect( $sanitized_redirect_to, $status_code );
880
+ else
881
+ wp_safe_redirect( $sanitized_redirect_to );
882
+ exit;
883
+ }
884
+ }
885
+ }
886
+
887
+ /**
888
+ * Sanitize redirect to path
889
+ *
890
+ * The only difference between this function and just calling esc_url_raw is
891
+ * esc_url_raw( 'test' ) == 'http://test', whereas sanitize_redirect_path( 'test' ) == '/test'
892
+ *
893
+ * @since 1.0
894
+ * @param string $path
895
+ * @uses esc_url_raw
896
+ * @return string
897
+ */
898
+ public function sanitize_redirect_to( $path ) {
899
+ $path = trim( $path );
900
+
901
+ if ( preg_match( '/^www\./i', $path ) )
902
+ $path = 'http://' . $path;
903
+
904
+ if ( ! preg_match( '/^https?:\/\//i', $path ) )
905
+ if ( strpos( $path, '/' ) !== 0 )
906
+ $path = '/' . $path;
907
+
908
+ return esc_url_raw( $path );
909
+ }
910
+
911
+ /**
912
+ * Sanitize redirect from path
913
+ *
914
+ * @since 1.0
915
+ * @param string $path
916
+ * @param boolean $allow_regex
917
+ * @uses esc_url_raw
918
+ * @return string
919
+ */
920
+ public function sanitize_redirect_from( $path, $allow_regex = false ) {
921
+
922
+ $path = trim( $path );
923
+
924
+ if ( empty( $path ) )
925
+ return '';
926
+
927
+ // dont accept paths starting with a .
928
+ if ( ! $allow_regex && strpos( $path, '.' ) === 0 )
929
+ return '';
930
+
931
+ // turn path in to absolute
932
+ if ( preg_match( '/https?:\/\//i', $path ) )
933
+ $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
934
+ elseif ( ! $allow_regex && strpos( $path, '/' ) !== 0 )
935
+ $path = '/' . $path;
936
+
937
+ // the @ symbol will break our regex engine
938
+ $path = str_replace( '@', '', $path );
939
+
940
+ return $path;
941
+ }
942
+
943
+ /**
944
+ * Return a permalink for a redirect post, which is the "redirect from"
945
+ * URL for that redirect.
946
+ *
947
+ * @since 1.7
948
+ * @param string $permalink The permalink
949
+ * @param object $post A Post object
950
+ * @uses home_url, get_post_meta
951
+ * @return string The permalink
952
+ */
953
+ public function filter_post_type_link( $permalink, $post ) {
954
+ if ( $this->redirect_post_type != $post->post_type )
955
+ return $permalink;
956
+
957
+ // We can't do anything to provide a permalink
958
+ // for regex enabled redirects.
959
+ if ( get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true ) )
960
+ return $permalink;
961
+
962
+ // We can't do anything if there is a wildcard in the redirect from
963
+ $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
964
+ if ( false !== strpos( $redirect_from, '*' ) )
965
+ return $permalink;
966
+
967
+ // Use default permalink if no $redirect_from exists - this prevents duplicate GUIDs
968
+ if ( empty( $redirect_from ) ) {
969
+ return $permalink;
970
+ }
971
+
972
+ return home_url( $redirect_from );
973
+ }
974
  }
975
 
976
  global $safe_redirect_manager;
977
+ $safe_redirect_manager = new SRM_Safe_Redirect_Manager();
tests/bootstrap.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $_tests_dir = getenv( 'WP_TESTS_DIR' );
4
+ if ( ! $_tests_dir ) {
5
+ $_tests_dir = '/tmp/wordpress-tests-lib';
6
+ }
7
+
8
+ define( 'PHPUNIT_SRM_TESTSUITE', 1 );
9
+
10
+ require_once ( $_tests_dir . '/includes/functions.php' );
11
+
12
+ function _manually_load_plugin() {
13
+ require dirname( __FILE__ ) . '/../safe-redirect-manager.php';
14
+ }
15
+ tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
16
+
17
+ require( $_tests_dir . '/includes/bootstrap.php' );
18
+
tests/test-core.php ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class SRMTestCore extends WP_UnitTestCase {
4
+
5
+ /**
6
+ * Test root redirect
7
+ *
8
+ * @since 1.8
9
+ */
10
+ public function testRootRedirect() {
11
+ global $safe_redirect_manager;
12
+
13
+ $_SERVER['REQUEST_URI'] = '/';
14
+ $redirected = false;
15
+ $redirect_to = '/gohere';
16
+ $safe_redirect_manager->create_redirect( '/', $redirect_to );
17
+
18
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
19
+ if ( $redirected_to === $redirect_to ) {
20
+ $redirected = true;
21
+ }
22
+ }, 10, 3 );
23
+
24
+ $safe_redirect_manager->action_parse_request();
25
+
26
+ $this->assertTrue( $redirected );
27
+ }
28
+
29
+ /**
30
+ * Test lots of permutations of URL trailing slashes with and without regex
31
+ *
32
+ * @since 1.8
33
+ */
34
+ public function testTrailingSlashes() {
35
+ /**
36
+ * First without regex
37
+ */
38
+
39
+ global $safe_redirect_manager;
40
+
41
+ $_SERVER['REQUEST_URI'] = '/one';
42
+ $redirected = false;
43
+ $redirect_to = '/gohere';
44
+ $safe_redirect_manager->create_redirect( '/one/', $redirect_to );
45
+
46
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
47
+ if ( $redirected_to === $redirect_to ) {
48
+ $redirected = true;
49
+ }
50
+ }, 10, 3 );
51
+
52
+ $safe_redirect_manager->action_parse_request();
53
+
54
+ $this->assertTrue( $redirected );
55
+
56
+ $_SERVER['REQUEST_URI'] = '/one/';
57
+ $redirected = false;
58
+ $redirect_to = '/gohere';
59
+ $safe_redirect_manager->create_redirect( '/one', $redirect_to );
60
+
61
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
62
+ if ( $redirected_to === $redirect_to ) {
63
+ $redirected = true;
64
+ }
65
+ }, 10, 3 );
66
+
67
+ $safe_redirect_manager->action_parse_request();
68
+
69
+ $this->assertTrue( $redirected );
70
+
71
+ $_SERVER['REQUEST_URI'] = '/one/two';
72
+ $redirected = false;
73
+ $redirect_to = '/gohere';
74
+ $safe_redirect_manager->create_redirect( '/one/two/', $redirect_to );
75
+
76
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
77
+ if ( $redirected_to === $redirect_to ) {
78
+ $redirected = true;
79
+ }
80
+ }, 10, 3 );
81
+
82
+ $safe_redirect_manager->action_parse_request();
83
+
84
+ $this->assertTrue( $redirected );
85
+
86
+ $_SERVER['REQUEST_URI'] = '/one/two/';
87
+ $redirected = false;
88
+ $redirect_to = '/gohere';
89
+ $safe_redirect_manager->create_redirect( '/one/two', $redirect_to );
90
+
91
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
92
+ if ( $redirected_to === $redirect_to ) {
93
+ $redirected = true;
94
+ }
95
+ }, 10, 3 );
96
+
97
+ $safe_redirect_manager->action_parse_request();
98
+
99
+ $this->assertTrue( $redirected );
100
+
101
+ /**
102
+ * Now with regex
103
+ */
104
+
105
+ $_SERVER['REQUEST_URI'] = '/one/two';
106
+ $redirected = false;
107
+ $redirect_to = '/gohere';
108
+ $safe_redirect_manager->create_redirect( '/.*/', $redirect_to, 301, true );
109
+
110
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
111
+ if ( $redirected_to === $redirect_to ) {
112
+ $redirected = true;
113
+ }
114
+ }, 10, 3 );
115
+
116
+ $safe_redirect_manager->action_parse_request();
117
+
118
+ $this->assertTrue( $redirected );
119
+
120
+ $_SERVER['REQUEST_URI'] = '/one/two/';
121
+ $redirected = false;
122
+ $redirect_to = '/gohere';
123
+ $safe_redirect_manager->create_redirect( '/.*', $redirect_to, 301, true );
124
+
125
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
126
+ if ( $redirected_to === $redirect_to ) {
127
+ $redirected = true;
128
+ }
129
+ }, 10, 3 );
130
+
131
+ $safe_redirect_manager->action_parse_request();
132
+
133
+ $this->assertTrue( $redirected );
134
+ }
135
+
136
+ /**
137
+ * Test some simple redirections
138
+ *
139
+ * @since 1.8
140
+ */
141
+ public function testSimplePath() {
142
+ global $safe_redirect_manager;
143
+
144
+ $_SERVER['REQUEST_URI'] = '/test';
145
+ $redirected = false;
146
+ $redirect_to = '/gohere';
147
+ $safe_redirect_manager->create_redirect( '/test', $redirect_to );
148
+
149
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
150
+ if ( $redirected_to === $redirect_to ) {
151
+ $redirected = true;
152
+ }
153
+ }, 10, 3 );
154
+
155
+ $safe_redirect_manager->action_parse_request();
156
+
157
+ $this->assertTrue( $redirected );
158
+
159
+ /**
160
+ * Test longer path with no trailing slash
161
+ */
162
+
163
+ $_SERVER['REQUEST_URI'] = '/test/this/path';
164
+ $redirected = false;
165
+ $redirect_to = '/gohere';
166
+ $safe_redirect_manager->create_redirect( '/test/this/path/', $redirect_to );
167
+
168
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
169
+ if ( $redirected_to === $redirect_to ) {
170
+ $redirected = true;
171
+ }
172
+ }, 10, 3 );
173
+
174
+ $safe_redirect_manager->action_parse_request();
175
+
176
+ $this->assertTrue( $redirected );
177
+
178
+ /**
179
+ * Test a redirect miss
180
+ */
181
+
182
+ $_SERVER['REQUEST_URI'] = '/test/wrong/path';
183
+ $redirected = false;
184
+ $redirect_to = '/gohere';
185
+ $safe_redirect_manager->create_redirect( '/test/right/path/', $redirect_to );
186
+
187
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
188
+ if ( $redirected_to === $redirect_to ) {
189
+ $redirected = true;
190
+ }
191
+ }, 10, 3 );
192
+
193
+ $safe_redirect_manager->action_parse_request();
194
+
195
+ $this->assertTrue( ! $redirected );
196
+ }
197
+
198
+ /**
199
+ * Test regex redirections
200
+ *
201
+ * @since 1.8
202
+ */
203
+ public function testSimplePathRegex() {
204
+ global $safe_redirect_manager;
205
+
206
+ $_SERVER['REQUEST_URI'] = '/tet/555/path/sdfsfsdf';
207
+ $redirected = false;
208
+ $redirect_to = '/gohere';
209
+ $safe_redirect_manager->create_redirect( '/tes?t/[0-9]+/path/[^/]+/?', $redirect_to, 301, true );
210
+
211
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
212
+ if ( $redirected_to === $redirect_to ) {
213
+ $redirected = true;
214
+ }
215
+ }, 10, 3 );
216
+
217
+ $safe_redirect_manager->action_parse_request();
218
+
219
+ $this->assertTrue( $redirected );
220
+
221
+ /**
222
+ * Test regex replacement
223
+ */
224
+
225
+ $_SERVER['REQUEST_URI'] = '/well/everything-else/strip';
226
+ $redirected = false;
227
+ $redirect_to = '/$1';
228
+ $safe_redirect_manager->create_redirect( '/([a-z]+)/.*', $redirect_to, 301, true );
229
+
230
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
231
+ if ( $redirected_to === '/well' ) {
232
+ $redirected = true;
233
+ }
234
+ }, 10, 3 );
235
+
236
+ $safe_redirect_manager->action_parse_request();
237
+
238
+ $this->assertTrue( $redirected );
239
+
240
+ /**
241
+ * Test regex miss
242
+ */
243
+
244
+ $_SERVER['REQUEST_URI'] = '/another/test';
245
+ $redirected = false;
246
+ $redirect_to = '/gohere';
247
+ $safe_redirect_manager->create_redirect( '/[0-9]+', $redirect_to, 301, true );
248
+
249
+ add_action( 'srm_do_redirect', function( $requested_path, $redirected_to, $status_code ) use ( &$redirect_to, &$redirected ) {
250
+ if ( $redirected_to === $redirect_to ) {
251
+ $redirected = true;
252
+ }
253
+ }, 10, 3 );
254
+
255
+ $safe_redirect_manager->action_parse_request();
256
+
257
+ $this->assertTrue( ! $redirected );
258
+ }
259
+ }