Safe Redirect Manager - Version 1.5

Version Description

(Nov. 7 2012) = * Regular expressions allowed in redirects * New filter 'srm_registered_redirects' allows you to conditionally unset redirects based on context, user permissions, etc. Thanks jtsternberg for the pull request.

Download this release

Release Info

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

Code changes from version 1.4.2 to 1.5

Files changed (4) hide show
  1. readme.txt +5 -1
  2. safe-redirect-manager.php +166 -137
  3. screenshot-1.png +0 -0
  4. screenshot-2.png +0 -0
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
4
  Requires at least: 3.1
5
  Tested up to: 3.4.2
6
- Stable tag: 1.4.2
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
@@ -24,6 +24,10 @@ Extract the zip file and just drop the contents in the wp-content/plugins/ direc
24
 
25
  == Changelog ==
26
 
 
 
 
 
27
  = 1.4.2 (Oct. 17, 2012) =
28
  * Disable redirect loop checking by default. You can filter srm_check_for_possible_redirect_loops to enable it.
29
  * Only return published redirects in update_redirect_cache. - bug fix
3
  Tags: http redirects, redirect manager, url redirection, safe http redirection
4
  Requires at least: 3.1
5
  Tested up to: 3.4.2
6
+ Stable tag: 1.5
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
24
 
25
  == Changelog ==
26
 
27
+ = 1.5 (Nov. 7 2012) =
28
+ * Regular expressions allowed in redirects
29
+ * New filter 'srm_registered_redirects' allows you to conditionally unset redirects based on context, user permissions, etc. Thanks [jtsternberg](https://github.com/jtsternberg) for the pull request.
30
+
31
  = 1.4.2 (Oct. 17, 2012) =
32
  * Disable redirect loop checking by default. You can filter srm_check_for_possible_redirect_loops to enable it.
33
  * Only return published redirects in update_redirect_cache. - bug fix
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.4.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,23 +26,24 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26
  */
27
 
28
  class SRM_Safe_Redirect_Manager {
29
-
30
  public $redirect_post_type = 'redirect_rule';
31
  private $redirect_nonce_name = 'srm_redirect_nonce';
32
  private $redirect_nonce_action = 'srm-save-redirect-meta';
33
-
34
  public $meta_key_redirect_from = '_redirect_rule_from';
35
  public $meta_key_redirect_to = '_redirect_rule_to';
36
  public $meta_key_redirect_status_code = '_redirect_rule_status_code';
37
-
 
38
  public $cache_key_redirects = '_srm_redirects';
39
-
40
  public $valid_status_codes = array( 301, 302, 303, 403, 404 );
41
-
42
  private $whitelist_hosts = array();
43
-
44
  public $default_max_redirects = 150;
45
-
46
  /**
47
  * Sets up redirect manager
48
  *
@@ -60,19 +61,19 @@ class SRM_Safe_Redirect_Manager {
60
  add_action( 'manage_' . $this->redirect_post_type . '_posts_custom_column' , array( $this, 'action_custom_redirect_columns' ), 10, 2 );
61
  add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
62
  add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
63
- add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
64
  add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
65
  add_filter( 'bulk_actions-' . 'edit-redirect_rule', array( $this, 'filter_bulk_actions' ) );
66
  add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
67
  add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
68
  add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
69
-
70
  // Search filters
71
  add_filter( 'posts_join', array( $this, 'filter_search_join' ) );
72
  add_filter( 'posts_where', array( $this, 'filter_search_where' ) );
73
  add_filter( 'posts_distinct', array( $this, 'filter_search_distinct' ) );
74
  }
75
-
76
  /**
77
  * Join posts table with postmeta table on search
78
  *
@@ -84,16 +85,16 @@ class SRM_Safe_Redirect_Manager {
84
  public function filter_search_join( $join ) {
85
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
86
  return $join;
87
-
88
  global $wpdb;
89
-
90
  $s = get_query_var( 's' );
91
  if ( ! empty( $s ) ) {
92
  $join .= " LEFT JOIN $wpdb->postmeta AS m ON ($wpdb->posts.ID = m.post_id) ";
93
  }
94
  return $join;
95
  }
96
-
97
  /**
98
  * Return distinct search results
99
  *
@@ -105,10 +106,10 @@ class SRM_Safe_Redirect_Manager {
105
  public function filter_search_distinct( $distinct ) {
106
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
107
  return $distinct;
108
-
109
  return 'DISTINCT';
110
  }
111
-
112
  /**
113
  * Join posts table with postmeta table on search
114
  *
@@ -120,7 +121,7 @@ class SRM_Safe_Redirect_Manager {
120
  public function filter_search_where( $where ) {
121
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) || ! is_search() || empty( $where ) )
122
  return $where;
123
-
124
  $exact = get_query_var( 'exact' );
125
  $n = ( ! empty( $exact ) ) ? '' : '%';
126
 
@@ -128,21 +129,21 @@ class SRM_Safe_Redirect_Manager {
128
  $seperator = '';
129
  $terms = $this->get_search_terms();
130
  $search .= '(';
131
-
132
  // we check the meta values against each term in the search
133
  foreach ( $terms as $term ) {
134
- $search .= $seperator;
135
- $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 );
136
  $seperator = ' OR ';
137
  }
138
-
139
  $search .= ')';
140
-
141
  $where = preg_replace( '/\(\(\(.*?\)\)\)/is', '((' . $search . '))', $where );
142
-
143
  return $where;
144
  }
145
-
146
  /**
147
  * Get an array of search terms
148
  *
@@ -159,7 +160,7 @@ class SRM_Safe_Redirect_Manager {
159
  }
160
  return $search_terms;
161
  }
162
-
163
  /**
164
  * Swap tools logo for plugin logo
165
  *
@@ -178,11 +179,14 @@ class SRM_Safe_Redirect_Manager {
178
  #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group {
179
  display: none;
180
  }
 
 
 
181
  </style>
182
  <?php
183
  }
184
  }
185
-
186
  /**
187
  * Removes bulk actions from post manager
188
  *
@@ -192,7 +196,7 @@ class SRM_Safe_Redirect_Manager {
192
  public function filter_bulk_actions() {
193
  return array();
194
  }
195
-
196
  /**
197
  * Creates a redirect post, this function will be useful for import/exports scripts
198
  *
@@ -207,34 +211,34 @@ class SRM_Safe_Redirect_Manager {
207
  $sanitized_redirect_from = $this->sanitize_redirect_from( $redirect_from );
208
  $sanitized_redirect_to = $this->sanitize_redirect_to( $redirect_to );
209
  $sanitized_status_code = absint( $status_code );
210
-
211
  // check and make sure no parameters are empty or invalid after sanitation
212
  if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) || ! in_array( $sanitized_status_code, $this->valid_status_codes ) )
213
  return 0;
214
-
215
  // create the post
216
  $post_args = array(
217
  'post_type' => $this->redirect_post_type,
218
  'post_status' => 'publish',
219
  'post_author' => 1
220
  );
221
-
222
  $post_id = wp_insert_post( $post_args );
223
-
224
  if ( 0 >= $post_id )
225
  return 0;
226
-
227
  // update the posts meta info
228
  update_post_meta( $post_id, $this->meta_key_redirect_from, $sanitized_redirect_from );
229
  update_post_meta( $post_id, $this->meta_key_redirect_to, $sanitized_redirect_to );
230
  update_post_meta( $post_id, $this->meta_key_redirect_status_code, $sanitized_status_code );
231
-
232
  // We need to update the cache after creating this redirect
233
  $this->update_redirect_cache();
234
-
235
  return $post_id;
236
  }
237
-
238
  /**
239
  * Whether or not this is an admin page specific to the plugin
240
  *
@@ -243,9 +247,9 @@ class SRM_Safe_Redirect_Manager {
243
  * @return bool
244
  */
245
  private function is_plugin_page() {
246
- return (bool) ( get_post_type() == $this->redirect_post_type || ( isset( $_GET['post_type'] ) && $this->redirect_post_type == $_GET['post_type'] ) );
247
- }
248
-
249
  /**
250
  * Echoes admin message if redirect chains exist
251
  *
@@ -256,7 +260,7 @@ class SRM_Safe_Redirect_Manager {
256
  public function action_redirect_chain_alert() {
257
  global $hook_suffix;
258
  if ( $this->is_plugin_page() ) {
259
-
260
  /**
261
  * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
262
  * will be running slow. Let's disable it by default.
@@ -279,7 +283,7 @@ class SRM_Safe_Redirect_Manager {
279
  }
280
  }
281
  }
282
-
283
  /**
284
  * Returns true if max redirects have been reached
285
  *
@@ -291,12 +295,12 @@ class SRM_Safe_Redirect_Manager {
291
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
292
  $redirects = $this->update_redirect_cache();
293
  }
294
-
295
  $max_redirects = apply_filters( 'srm_max_redirects', $this->default_max_redirects );
296
-
297
  return ( count( $redirects ) >= $max_redirects );
298
  }
299
-
300
  /**
301
  * Check for potential redirect loops or chains
302
  *
@@ -308,20 +312,20 @@ class SRM_Safe_Redirect_Manager {
308
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
309
  $redirects = $this->update_redirect_cache();
310
  }
311
-
312
  $current_url = parse_url( home_url() );
313
  $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
314
-
315
  foreach ( $redirects as $redirect ) {
316
  $redirect_from = $redirect['redirect_from'];
317
-
318
  // check redirect from against all redirect to's
319
  foreach ( $redirects as $compare_redirect ) {
320
  $redirect_to = $compare_redirect['redirect_to'];
321
-
322
  $redirect_url = parse_url( $redirect_to );
323
  $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
324
-
325
  // check if we are redirecting locally
326
  if ( empty( $redirect_host ) || $redirect_host == $this_host ) {
327
  $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
@@ -330,17 +334,17 @@ class SRM_Safe_Redirect_Manager {
330
  $redirect_to_url = $this_host . $redirect_to_url;
331
  else
332
  $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
333
-
334
  // possible loop/chain found
335
  if ( $redirect_to_url == $redirect_from_url )
336
  return true;
337
  }
338
  }
339
  }
340
-
341
  return false;
342
  }
343
-
344
  /**
345
  * Filters title out for redirect from in post manager
346
  *
@@ -353,14 +357,14 @@ class SRM_Safe_Redirect_Manager {
353
  public function filter_admin_title( $title, $post_id = 0 ) {
354
  if ( ! is_admin() || false === ( $redirect = get_post( $post_id ) ) || $redirect->post_type != $this->redirect_post_type )
355
  return $title;
356
-
357
  $redirect_from = get_post_meta( $post_id, $this->meta_key_redirect_from, true );
358
  if ( ! empty( $redirect_from ) )
359
  return $redirect_from;
360
-
361
  return $title;
362
  }
363
-
364
  /**
365
  * Customizes updated messages for redirects
366
  *
@@ -371,7 +375,7 @@ class SRM_Safe_Redirect_Manager {
371
  */
372
  public function filter_redirect_updated_messages( $messages ) {
373
  global $post, $post_ID;
374
-
375
  $messages[$this->redirect_post_type] = array(
376
  0 => '', // Unused. Messages start at index 1.
377
  1 => sprintf( __( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
@@ -388,10 +392,10 @@ class SRM_Safe_Redirect_Manager {
388
  date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
389
  10 => sprintf( __( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
390
  );
391
-
392
  return $messages;
393
  }
394
-
395
  /**
396
  * Clear redirect cache if appropriate post type is transitioned
397
  *
@@ -405,14 +409,14 @@ class SRM_Safe_Redirect_Manager {
405
  public function action_transition_post_status( $new_status, $old_status, $post ) {
406
  if ( ! is_object( $post ) )
407
  return;
408
-
409
  // recreate redirect cache
410
  if ( $this->redirect_post_type == $post->post_type ) {
411
  delete_transient( $this->cache_key_redirects );
412
  $this->update_redirect_cache();
413
  }
414
  }
415
-
416
  /**
417
  * Displays custom columns on redirect manager screen
418
  *
@@ -429,7 +433,7 @@ class SRM_Safe_Redirect_Manager {
429
  echo absint( get_post_meta( $post_id, $this->meta_key_redirect_status_code, true ) );
430
  }
431
  }
432
-
433
  /**
434
  * Add new columns to manage redirect screen
435
  *
@@ -440,20 +444,20 @@ class SRM_Safe_Redirect_Manager {
440
  public function filter_redirect_columns( $columns ) {
441
  $columns['srm' . $this->meta_key_redirect_to] = __( 'Redirect To', 'safe-redirect-manager' );
442
  $columns['srm'. $this->meta_key_redirect_status_code] = __( 'HTTP Status Code', 'safe-redirect-manager' );
443
-
444
  // Change the title column
445
  $columns['title'] = __( 'Redirect From', 'safe-redirect-manager' );
446
-
447
  // Move date column to the back
448
  unset( $columns['date'] );
449
  $columns['date'] = __( 'Date', 'safe-redirect-manager' );
450
-
451
  // get rid of checkboxes
452
  unset( $columns['cb'] );
453
-
454
  return $columns;
455
  }
456
-
457
  /**
458
  * Saves meta info for redirect rules
459
  *
@@ -465,28 +469,36 @@ class SRM_Safe_Redirect_Manager {
465
  public function action_save_post( $post_id ) {
466
  if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' == get_post_type( $post_id ) )
467
  return;
468
-
469
  // Update post meta for redirect rules
470
  if ( ! empty( $_POST[$this->redirect_nonce_name] ) && wp_verify_nonce( $_POST[$this->redirect_nonce_name], $this->redirect_nonce_action ) ) {
471
-
 
 
 
 
 
 
 
 
472
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_from] ) ) {
473
- update_post_meta( $post_id, $this->meta_key_redirect_from, $this->sanitize_redirect_from( $_POST['srm' . $this->meta_key_redirect_from] ) );
474
  } else {
475
  delete_post_meta( $post_id, $this->meta_key_redirect_from );
476
  }
477
-
478
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_to] ) ) {
479
  update_post_meta( $post_id, $this->meta_key_redirect_to, $this->sanitize_redirect_to( $_POST['srm' . $this->meta_key_redirect_to] ) );
480
  } else {
481
  delete_post_meta( $post_id, $this->meta_key_redirect_to );
482
  }
483
-
484
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_status_code] ) ) {
485
  update_post_meta( $post_id, $this->meta_key_redirect_status_code, absint( $_POST['srm' . $this->meta_key_redirect_status_code] ) );
486
  } else {
487
  delete_post_meta( $post_id, $this->meta_key_redirect_status_code );
488
  }
489
-
490
  /**
491
  * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
492
  * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
@@ -495,7 +507,7 @@ class SRM_Safe_Redirect_Manager {
495
  $this->update_redirect_cache();
496
  }
497
  }
498
-
499
  /**
500
  * Registers post types for plugin
501
  *
@@ -515,7 +527,7 @@ class SRM_Safe_Redirect_Manager {
515
  'view_item' => __( 'View Redirect Rule', 'safe-redirect-manager' ),
516
  'search_items' => __( 'Search Redirects', 'safe-redirect-manager' ),
517
  'not_found' => __( 'No redirect rules found.', 'safe-redirect-manager' ),
518
- 'not_found_in_trash' => __( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
519
  'parent_item_colon' => '',
520
  'menu_name' => __( 'Safe Redirect Manager', 'safe-redirect-manager' )
521
  );
@@ -530,26 +542,26 @@ class SRM_Safe_Redirect_Manager {
530
  'publish_posts' => $redirect_capability,
531
  'read_private_posts' => $redirect_capability
532
  );
533
-
534
  $redirect_args = array(
535
  'labels' => $redirect_labels,
536
  'public' => false,
537
  'publicly_queryable' => true,
538
- 'show_ui' => true,
539
- 'show_in_menu' => 'tools.php',
540
  'query_var' => false,
541
  'rewrite' => false,
542
  'capability_type' => 'post',
543
  'capabilities' => $capabilities,
544
- 'has_archive' => false,
545
  'hierarchical' => false,
546
  'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
547
  'menu_position' => 80,
548
  'supports' => array( '' )
549
- );
550
  register_post_type( $this->redirect_post_type, $redirect_args );
551
  }
552
-
553
  /**
554
  * Registers meta boxes for redirect rule post type
555
  *
@@ -560,10 +572,10 @@ class SRM_Safe_Redirect_Manager {
560
  public function action_redirect_rule_metabox() {
561
  add_meta_box( 'redirect_settings', __( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), $this->redirect_post_type, 'normal', 'core' );
562
  }
563
-
564
  /**
565
  * Echoes HTML for redirect rule meta box
566
- *
567
  * @since 1.0
568
  * @param object $post
569
  * @uses wp_nonce_field, get_post_meta, esc_attr, selected
@@ -571,27 +583,30 @@ class SRM_Safe_Redirect_Manager {
571
  */
572
  public function redirect_rule_metabox( $post ) {
573
  wp_nonce_field( $this->redirect_nonce_action, $this->redirect_nonce_name );
574
-
575
  $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
576
  $redirect_to = get_post_meta( $post->ID, $this->meta_key_redirect_to, true );
577
  $status_code = get_post_meta( $post->ID, $this->meta_key_redirect_status_code, true );
 
578
  if ( empty( $status_code ) )
579
  $status_code = 302;
580
  ?>
581
  <p>
582
  <label for="srm<?php echo $this->meta_key_redirect_from; ?>"><?php _e( 'Redirect From:', 'safe-redirect-manager' ); ?></label><br />
583
- <input class="widefat" 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 ); ?>" /><br />
584
- <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.", 'safe-redirect-manager' ); ?></p>
 
 
585
  </p>
586
-
587
  <p>
588
  <label for="srm<?php echo $this->meta_key_redirect_to; ?>"><?php _e( 'Redirect To:', 'safe-redirect-manager' ); ?></label><br />
589
  <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 />
590
  <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>
591
  </p>
592
-
593
  <p>
594
- <label for="srm<?php echo $this->meta_key_redirect_status_code; ?>"><?php _e( 'HTTP Status Code:', 'safe-redirect-manager' ); ?></label>
595
  <select name="srm<?php echo $this->meta_key_redirect_status_code; ?>" id="srm<?php echo $this->meta_key_redirect_status_code; ?>">
596
  <?php foreach ( $this->valid_status_codes as $code ) : ?>
597
  <option <?php selected( $status_code, $code ); ?>><?php echo $code; ?></option>
@@ -601,7 +616,7 @@ class SRM_Safe_Redirect_Manager {
601
  </p>
602
  <?php
603
  }
604
-
605
  /**
606
  * Localize plugin
607
  *
@@ -612,7 +627,7 @@ class SRM_Safe_Redirect_Manager {
612
  public function action_init() {
613
  load_plugin_textdomain( 'safe-redirect-manager', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
614
  }
615
-
616
  /**
617
  * Apply whitelisted hosts to allowed_redirect_hosts filter
618
  *
@@ -621,18 +636,18 @@ class SRM_Safe_Redirect_Manager {
621
  * @return array
622
  */
623
  public function filter_allowed_redirect_hosts( $content ) {
624
-
625
  foreach ( $this->whitelist_hosts as $host ) {
626
  $without_www = preg_replace( '/^www\./i', '', $host );
627
  $with_www = 'www.' . $without_www;
628
-
629
  if ( ! in_array( $without_www, $content ) ) $content[] = $without_www;
630
  if ( ! in_array( $with_www, $content ) ) $content[] = $with_www;
631
  }
632
-
633
  return $content;
634
  }
635
-
636
  /**
637
  * Force update on the redirect cache and return cache
638
  *
@@ -643,7 +658,7 @@ class SRM_Safe_Redirect_Manager {
643
  public function update_redirect_cache() {
644
  global $post;
645
  $old_post = $post;
646
-
647
  $args = array(
648
  'posts_per_page' => 1000,
649
  'post_type' => $this->redirect_post_type,
@@ -653,30 +668,32 @@ class SRM_Safe_Redirect_Manager {
653
  );
654
  $redirect_query = new WP_Query( $args );
655
  $redirect_cache = array();
656
-
657
  if ( $redirect_query->have_posts() ) {
658
  while ( $redirect_query->have_posts() ) {
659
  $redirect_query->the_post();
660
-
661
  $redirect_from = get_post_meta( get_the_ID(), $this->meta_key_redirect_from, true );
662
  $redirect_to = get_post_meta( get_the_ID(), $this->meta_key_redirect_to, true );
663
  $status_code = get_post_meta( get_the_ID(), $this->meta_key_redirect_status_code, true );
664
-
 
665
  if ( ! empty( $redirect_from ) && ! empty( $redirect_to ) ) {
666
  $redirect_cache[] = array(
667
  'redirect_from' => $redirect_from,
668
  'redirect_to' => $redirect_to,
669
- 'status_code' => absint( $status_code )
 
670
  );
671
  }
672
  }
673
  }
674
  $post = $old_post;
675
  set_transient( $this->cache_key_redirects, $redirect_cache );
676
-
677
  return $redirect_cache;
678
  }
679
-
680
  /**
681
  * Check current url against redirects
682
  *
@@ -685,66 +702,74 @@ class SRM_Safe_Redirect_Manager {
685
  * @return void
686
  */
687
  public function action_parse_request() {
688
-
689
  // get redirects from cache or recreate it
690
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
691
  $redirects = $this->update_redirect_cache();
692
  }
693
-
694
  // If we have no redirects, there is no need to continue
695
  if ( empty( $redirects ) )
696
  return;
697
-
698
  // get requested path and add a / before it
699
  $requested_path = sanitize_text_field( $_SERVER['REQUEST_URI'] );
700
-
701
  /**
702
  * If WordPress resides in a directory that is not the public root, we have to chop
703
  * the pre-WP path off the requested path.
704
  */
705
  $parsed_site_url = parse_url( site_url() );
706
- if ( '/' != $parsed_site_url['path'] ) {
707
  $requested_path = preg_replace( '@' . $parsed_site_url['path'] . '@i', '', $requested_path, 1 );
708
  }
709
-
710
- foreach ( $redirects as $redirect ) {
711
-
 
 
 
712
  $redirect_from = untrailingslashit( $redirect['redirect_from'] );
713
  if ( empty( $redirect_from ) )
714
  $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
715
-
716
  $redirect_to = $redirect['redirect_to'];
717
  $status_code = $redirect['status_code'];
 
718
 
719
  if ( apply_filters( 'srm_case_insensitive_redirects', true ) ) {
720
  $requested_path = strtolower( $requested_path );
721
  $redirect_from = strtolower( $redirect_from );
722
  }
723
-
724
  // check if requested path is the same as the redirect from path
725
- $matched_path = ( untrailingslashit( $requested_path ) == $redirect_from );
726
-
727
- // check if the redirect_from ends in a wildcard
728
- if ( !$matched_path && (strrpos( $redirect_from, '*' ) == strlen( $redirect_from ) - 1) ) {
729
- $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
730
-
731
- // mark as match if requested path matches the base of the redirect from
732
- $matched_path = (substr( $requested_path, 0, strlen( $wildcard_base ) ) == $wildcard_base);
733
- if ( (strrpos( $redirect_to, '*' ) == strlen( $redirect_to ) - 1 ) ) {
734
- $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
 
 
 
 
735
  }
736
  }
737
 
738
- if ( $matched_path ) {
739
  // whitelist redirect to host if necessary
740
  $parsed_redirect = parse_url( $redirect_to );
741
  if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
742
  $this->whitelist_hosts[] = $parsed_redirect['host'];
743
  add_filter( 'allowed_redirect_hosts' , array( $this, 'filter_allowed_redirect_hosts' ) );
744
  }
745
-
746
  header("X-Safe-Redirect-Manager: true");
747
-
748
  // if we have a valid status code, then redirect with it
749
  if ( in_array( $status_code, $this->valid_status_codes ) )
750
  wp_safe_redirect( esc_url_raw( $redirect_to ), $status_code );
@@ -754,7 +779,7 @@ class SRM_Safe_Redirect_Manager {
754
  }
755
  }
756
  }
757
-
758
  /**
759
  * Sanitize redirect to path
760
  *
@@ -768,43 +793,47 @@ class SRM_Safe_Redirect_Manager {
768
  */
769
  public function sanitize_redirect_to( $path ) {
770
  $path = trim( $path );
771
-
772
  if ( preg_match( '/^www\./i', $path ) )
773
  $path = 'http://' . $path;
774
-
775
  if ( ! preg_match( '/^https?:\/\//i', $path ) )
776
  if ( strpos( $path, '/' ) !== 0 )
777
  $path = '/' . $path;
778
-
779
  return esc_url_raw( $path );
780
  }
781
-
782
  /**
783
  * Sanitize redirect from path
784
  *
785
  * @since 1.0
786
  * @param string $path
 
787
  * @uses esc_url_raw
788
  * @return string
789
  */
790
- public function sanitize_redirect_from( $path ) {
791
-
792
  $path = trim( $path );
793
-
794
  if ( empty( $path ) )
795
  return '';
796
-
797
  // dont accept paths starting with a .
798
- if ( strpos( $path, '.' ) === 0 )
799
  return '';
800
-
801
  // turn path in to absolute
802
  if ( preg_match( '/https?:\/\//i', $path ) )
803
  $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
804
- elseif ( strpos( $path, '/' ) !== 0 )
805
  $path = '/' . $path;
806
-
807
- return esc_url_raw( $path );
 
 
 
808
  }
809
  }
810
 
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.5
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
  class SRM_Safe_Redirect_Manager {
29
+
30
  public $redirect_post_type = 'redirect_rule';
31
  private $redirect_nonce_name = 'srm_redirect_nonce';
32
  private $redirect_nonce_action = 'srm-save-redirect-meta';
33
+
34
  public $meta_key_redirect_from = '_redirect_rule_from';
35
  public $meta_key_redirect_to = '_redirect_rule_to';
36
  public $meta_key_redirect_status_code = '_redirect_rule_status_code';
37
+ public $meta_key_enable_redirect_from_regex = '_redirect_rule_from_regex';
38
+
39
  public $cache_key_redirects = '_srm_redirects';
40
+
41
  public $valid_status_codes = array( 301, 302, 303, 403, 404 );
42
+
43
  private $whitelist_hosts = array();
44
+
45
  public $default_max_redirects = 150;
46
+
47
  /**
48
  * Sets up redirect manager
49
  *
61
  add_action( 'manage_' . $this->redirect_post_type . '_posts_custom_column' , array( $this, 'action_custom_redirect_columns' ), 10, 2 );
62
  add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
63
  add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
64
+ add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
65
  add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
66
  add_filter( 'bulk_actions-' . 'edit-redirect_rule', array( $this, 'filter_bulk_actions' ) );
67
  add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
68
  add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
69
  add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
70
+
71
  // Search filters
72
  add_filter( 'posts_join', array( $this, 'filter_search_join' ) );
73
  add_filter( 'posts_where', array( $this, 'filter_search_where' ) );
74
  add_filter( 'posts_distinct', array( $this, 'filter_search_distinct' ) );
75
  }
76
+
77
  /**
78
  * Join posts table with postmeta table on search
79
  *
85
  public function filter_search_join( $join ) {
86
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
87
  return $join;
88
+
89
  global $wpdb;
90
+
91
  $s = get_query_var( 's' );
92
  if ( ! empty( $s ) ) {
93
  $join .= " LEFT JOIN $wpdb->postmeta AS m ON ($wpdb->posts.ID = m.post_id) ";
94
  }
95
  return $join;
96
  }
97
+
98
  /**
99
  * Return distinct search results
100
  *
106
  public function filter_search_distinct( $distinct ) {
107
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) )
108
  return $distinct;
109
+
110
  return 'DISTINCT';
111
  }
112
+
113
  /**
114
  * Join posts table with postmeta table on search
115
  *
121
  public function filter_search_where( $where ) {
122
  if ( $this->redirect_post_type != get_query_var( 'post_type' ) || ! is_search() || empty( $where ) )
123
  return $where;
124
+
125
  $exact = get_query_var( 'exact' );
126
  $n = ( ! empty( $exact ) ) ? '' : '%';
127
 
129
  $seperator = '';
130
  $terms = $this->get_search_terms();
131
  $search .= '(';
132
+
133
  // we check the meta values against each term in the search
134
  foreach ( $terms as $term ) {
135
+ $search .= $seperator;
136
+ $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 );
137
  $seperator = ' OR ';
138
  }
139
+
140
  $search .= ')';
141
+
142
  $where = preg_replace( '/\(\(\(.*?\)\)\)/is', '((' . $search . '))', $where );
143
+
144
  return $where;
145
  }
146
+
147
  /**
148
  * Get an array of search terms
149
  *
160
  }
161
  return $search_terms;
162
  }
163
+
164
  /**
165
  * Swap tools logo for plugin logo
166
  *
179
  #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group {
180
  display: none;
181
  }
182
+ #srm<?php echo $this->meta_key_redirect_from; ?> {
183
+ width: 60%;
184
+ }
185
  </style>
186
  <?php
187
  }
188
  }
189
+
190
  /**
191
  * Removes bulk actions from post manager
192
  *
196
  public function filter_bulk_actions() {
197
  return array();
198
  }
199
+
200
  /**
201
  * Creates a redirect post, this function will be useful for import/exports scripts
202
  *
211
  $sanitized_redirect_from = $this->sanitize_redirect_from( $redirect_from );
212
  $sanitized_redirect_to = $this->sanitize_redirect_to( $redirect_to );
213
  $sanitized_status_code = absint( $status_code );
214
+
215
  // check and make sure no parameters are empty or invalid after sanitation
216
  if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) || ! in_array( $sanitized_status_code, $this->valid_status_codes ) )
217
  return 0;
218
+
219
  // create the post
220
  $post_args = array(
221
  'post_type' => $this->redirect_post_type,
222
  'post_status' => 'publish',
223
  'post_author' => 1
224
  );
225
+
226
  $post_id = wp_insert_post( $post_args );
227
+
228
  if ( 0 >= $post_id )
229
  return 0;
230
+
231
  // update the posts meta info
232
  update_post_meta( $post_id, $this->meta_key_redirect_from, $sanitized_redirect_from );
233
  update_post_meta( $post_id, $this->meta_key_redirect_to, $sanitized_redirect_to );
234
  update_post_meta( $post_id, $this->meta_key_redirect_status_code, $sanitized_status_code );
235
+
236
  // We need to update the cache after creating this redirect
237
  $this->update_redirect_cache();
238
+
239
  return $post_id;
240
  }
241
+
242
  /**
243
  * Whether or not this is an admin page specific to the plugin
244
  *
247
  * @return bool
248
  */
249
  private function is_plugin_page() {
250
+ return (bool) ( get_post_type() == $this->redirect_post_type || ( isset( $_GET['post_type'] ) && $this->redirect_post_type == $_GET['post_type'] ) );
251
+ }
252
+
253
  /**
254
  * Echoes admin message if redirect chains exist
255
  *
260
  public function action_redirect_chain_alert() {
261
  global $hook_suffix;
262
  if ( $this->is_plugin_page() ) {
263
+
264
  /**
265
  * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
266
  * will be running slow. Let's disable it by default.
283
  }
284
  }
285
  }
286
+
287
  /**
288
  * Returns true if max redirects have been reached
289
  *
295
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
296
  $redirects = $this->update_redirect_cache();
297
  }
298
+
299
  $max_redirects = apply_filters( 'srm_max_redirects', $this->default_max_redirects );
300
+
301
  return ( count( $redirects ) >= $max_redirects );
302
  }
303
+
304
  /**
305
  * Check for potential redirect loops or chains
306
  *
312
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
313
  $redirects = $this->update_redirect_cache();
314
  }
315
+
316
  $current_url = parse_url( home_url() );
317
  $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
318
+
319
  foreach ( $redirects as $redirect ) {
320
  $redirect_from = $redirect['redirect_from'];
321
+
322
  // check redirect from against all redirect to's
323
  foreach ( $redirects as $compare_redirect ) {
324
  $redirect_to = $compare_redirect['redirect_to'];
325
+
326
  $redirect_url = parse_url( $redirect_to );
327
  $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
328
+
329
  // check if we are redirecting locally
330
  if ( empty( $redirect_host ) || $redirect_host == $this_host ) {
331
  $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
334
  $redirect_to_url = $this_host . $redirect_to_url;
335
  else
336
  $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
337
+
338
  // possible loop/chain found
339
  if ( $redirect_to_url == $redirect_from_url )
340
  return true;
341
  }
342
  }
343
  }
344
+
345
  return false;
346
  }
347
+
348
  /**
349
  * Filters title out for redirect from in post manager
350
  *
357
  public function filter_admin_title( $title, $post_id = 0 ) {
358
  if ( ! is_admin() || false === ( $redirect = get_post( $post_id ) ) || $redirect->post_type != $this->redirect_post_type )
359
  return $title;
360
+
361
  $redirect_from = get_post_meta( $post_id, $this->meta_key_redirect_from, true );
362
  if ( ! empty( $redirect_from ) )
363
  return $redirect_from;
364
+
365
  return $title;
366
  }
367
+
368
  /**
369
  * Customizes updated messages for redirects
370
  *
375
  */
376
  public function filter_redirect_updated_messages( $messages ) {
377
  global $post, $post_ID;
378
+
379
  $messages[$this->redirect_post_type] = array(
380
  0 => '', // Unused. Messages start at index 1.
381
  1 => sprintf( __( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
392
  date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
393
  10 => sprintf( __( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
394
  );
395
+
396
  return $messages;
397
  }
398
+
399
  /**
400
  * Clear redirect cache if appropriate post type is transitioned
401
  *
409
  public function action_transition_post_status( $new_status, $old_status, $post ) {
410
  if ( ! is_object( $post ) )
411
  return;
412
+
413
  // recreate redirect cache
414
  if ( $this->redirect_post_type == $post->post_type ) {
415
  delete_transient( $this->cache_key_redirects );
416
  $this->update_redirect_cache();
417
  }
418
  }
419
+
420
  /**
421
  * Displays custom columns on redirect manager screen
422
  *
433
  echo absint( get_post_meta( $post_id, $this->meta_key_redirect_status_code, true ) );
434
  }
435
  }
436
+
437
  /**
438
  * Add new columns to manage redirect screen
439
  *
444
  public function filter_redirect_columns( $columns ) {
445
  $columns['srm' . $this->meta_key_redirect_to] = __( 'Redirect To', 'safe-redirect-manager' );
446
  $columns['srm'. $this->meta_key_redirect_status_code] = __( 'HTTP Status Code', 'safe-redirect-manager' );
447
+
448
  // Change the title column
449
  $columns['title'] = __( 'Redirect From', 'safe-redirect-manager' );
450
+
451
  // Move date column to the back
452
  unset( $columns['date'] );
453
  $columns['date'] = __( 'Date', 'safe-redirect-manager' );
454
+
455
  // get rid of checkboxes
456
  unset( $columns['cb'] );
457
+
458
  return $columns;
459
  }
460
+
461
  /**
462
  * Saves meta info for redirect rules
463
  *
469
  public function action_save_post( $post_id ) {
470
  if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' == get_post_type( $post_id ) )
471
  return;
472
+
473
  // Update post meta for redirect rules
474
  if ( ! empty( $_POST[$this->redirect_nonce_name] ) && wp_verify_nonce( $_POST[$this->redirect_nonce_name], $this->redirect_nonce_action ) ) {
475
+
476
+ if ( ! empty( $_POST['srm' . $this->meta_key_enable_redirect_from_regex] ) ) {
477
+ $allow_regex = (bool) $_POST['srm' . $this->meta_key_enable_redirect_from_regex];
478
+ update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $allow_regex );
479
+ } else {
480
+ $allow_regex = false;
481
+ delete_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex );
482
+ }
483
+
484
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_from] ) ) {
485
+ update_post_meta( $post_id, $this->meta_key_redirect_from, $this->sanitize_redirect_from( $_POST['srm' . $this->meta_key_redirect_from], $allow_regex ) );
486
  } else {
487
  delete_post_meta( $post_id, $this->meta_key_redirect_from );
488
  }
489
+
490
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_to] ) ) {
491
  update_post_meta( $post_id, $this->meta_key_redirect_to, $this->sanitize_redirect_to( $_POST['srm' . $this->meta_key_redirect_to] ) );
492
  } else {
493
  delete_post_meta( $post_id, $this->meta_key_redirect_to );
494
  }
495
+
496
  if ( ! empty( $_POST['srm' . $this->meta_key_redirect_status_code] ) ) {
497
  update_post_meta( $post_id, $this->meta_key_redirect_status_code, absint( $_POST['srm' . $this->meta_key_redirect_status_code] ) );
498
  } else {
499
  delete_post_meta( $post_id, $this->meta_key_redirect_status_code );
500
  }
501
+
502
  /**
503
  * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
504
  * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
507
  $this->update_redirect_cache();
508
  }
509
  }
510
+
511
  /**
512
  * Registers post types for plugin
513
  *
527
  'view_item' => __( 'View Redirect Rule', 'safe-redirect-manager' ),
528
  'search_items' => __( 'Search Redirects', 'safe-redirect-manager' ),
529
  'not_found' => __( 'No redirect rules found.', 'safe-redirect-manager' ),
530
+ 'not_found_in_trash' => __( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
531
  'parent_item_colon' => '',
532
  'menu_name' => __( 'Safe Redirect Manager', 'safe-redirect-manager' )
533
  );
542
  'publish_posts' => $redirect_capability,
543
  'read_private_posts' => $redirect_capability
544
  );
545
+
546
  $redirect_args = array(
547
  'labels' => $redirect_labels,
548
  'public' => false,
549
  'publicly_queryable' => true,
550
+ 'show_ui' => true,
551
+ 'show_in_menu' => 'tools.php',
552
  'query_var' => false,
553
  'rewrite' => false,
554
  'capability_type' => 'post',
555
  'capabilities' => $capabilities,
556
+ 'has_archive' => false,
557
  'hierarchical' => false,
558
  'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
559
  'menu_position' => 80,
560
  'supports' => array( '' )
561
+ );
562
  register_post_type( $this->redirect_post_type, $redirect_args );
563
  }
564
+
565
  /**
566
  * Registers meta boxes for redirect rule post type
567
  *
572
  public function action_redirect_rule_metabox() {
573
  add_meta_box( 'redirect_settings', __( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), $this->redirect_post_type, 'normal', 'core' );
574
  }
575
+
576
  /**
577
  * Echoes HTML for redirect rule meta box
578
+ *
579
  * @since 1.0
580
  * @param object $post
581
  * @uses wp_nonce_field, get_post_meta, esc_attr, selected
583
  */
584
  public function redirect_rule_metabox( $post ) {
585
  wp_nonce_field( $this->redirect_nonce_action, $this->redirect_nonce_name );
586
+
587
  $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
588
  $redirect_to = get_post_meta( $post->ID, $this->meta_key_redirect_to, true );
589
  $status_code = get_post_meta( $post->ID, $this->meta_key_redirect_status_code, true );
590
+ $enable_regex = get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true );
591
  if ( empty( $status_code ) )
592
  $status_code = 302;
593
  ?>
594
  <p>
595
  <label for="srm<?php echo $this->meta_key_redirect_from; ?>"><?php _e( 'Redirect From:', 'safe-redirect-manager' ); ?></label><br />
596
+ <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 ); ?>" />
597
+ <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" />
598
+ <label for="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>"><?php _e( 'Enable Regular Expressions (advanced)', 'safe-redirect-manager' ); ?></label><br />
599
+ <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>
600
  </p>
601
+
602
  <p>
603
  <label for="srm<?php echo $this->meta_key_redirect_to; ?>"><?php _e( 'Redirect To:', 'safe-redirect-manager' ); ?></label><br />
604
  <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 />
605
  <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>
606
  </p>
607
+
608
  <p>
609
+ <label for="srm<?php echo $this->meta_key_redirect_status_code; ?>"><?php _e( 'HTTP Status Code:', 'safe-redirect-manager' ); ?></label>
610
  <select name="srm<?php echo $this->meta_key_redirect_status_code; ?>" id="srm<?php echo $this->meta_key_redirect_status_code; ?>">
611
  <?php foreach ( $this->valid_status_codes as $code ) : ?>
612
  <option <?php selected( $status_code, $code ); ?>><?php echo $code; ?></option>
616
  </p>
617
  <?php
618
  }
619
+
620
  /**
621
  * Localize plugin
622
  *
627
  public function action_init() {
628
  load_plugin_textdomain( 'safe-redirect-manager', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
629
  }
630
+
631
  /**
632
  * Apply whitelisted hosts to allowed_redirect_hosts filter
633
  *
636
  * @return array
637
  */
638
  public function filter_allowed_redirect_hosts( $content ) {
639
+
640
  foreach ( $this->whitelist_hosts as $host ) {
641
  $without_www = preg_replace( '/^www\./i', '', $host );
642
  $with_www = 'www.' . $without_www;
643
+
644
  if ( ! in_array( $without_www, $content ) ) $content[] = $without_www;
645
  if ( ! in_array( $with_www, $content ) ) $content[] = $with_www;
646
  }
647
+
648
  return $content;
649
  }
650
+
651
  /**
652
  * Force update on the redirect cache and return cache
653
  *
658
  public function update_redirect_cache() {
659
  global $post;
660
  $old_post = $post;
661
+
662
  $args = array(
663
  'posts_per_page' => 1000,
664
  'post_type' => $this->redirect_post_type,
668
  );
669
  $redirect_query = new WP_Query( $args );
670
  $redirect_cache = array();
671
+
672
  if ( $redirect_query->have_posts() ) {
673
  while ( $redirect_query->have_posts() ) {
674
  $redirect_query->the_post();
675
+
676
  $redirect_from = get_post_meta( get_the_ID(), $this->meta_key_redirect_from, true );
677
  $redirect_to = get_post_meta( get_the_ID(), $this->meta_key_redirect_to, true );
678
  $status_code = get_post_meta( get_the_ID(), $this->meta_key_redirect_status_code, true );
679
+ $enable_regex = get_post_meta( get_the_ID(), $this->meta_key_enable_redirect_from_regex, true );
680
+
681
  if ( ! empty( $redirect_from ) && ! empty( $redirect_to ) ) {
682
  $redirect_cache[] = array(
683
  'redirect_from' => $redirect_from,
684
  'redirect_to' => $redirect_to,
685
+ 'status_code' => absint( $status_code ),
686
+ 'enable_regex' => (bool) $enable_regex
687
  );
688
  }
689
  }
690
  }
691
  $post = $old_post;
692
  set_transient( $this->cache_key_redirects, $redirect_cache );
693
+
694
  return $redirect_cache;
695
  }
696
+
697
  /**
698
  * Check current url against redirects
699
  *
702
  * @return void
703
  */
704
  public function action_parse_request() {
705
+
706
  // get redirects from cache or recreate it
707
  if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
708
  $redirects = $this->update_redirect_cache();
709
  }
710
+
711
  // If we have no redirects, there is no need to continue
712
  if ( empty( $redirects ) )
713
  return;
714
+
715
  // get requested path and add a / before it
716
  $requested_path = sanitize_text_field( $_SERVER['REQUEST_URI'] );
717
+
718
  /**
719
  * If WordPress resides in a directory that is not the public root, we have to chop
720
  * the pre-WP path off the requested path.
721
  */
722
  $parsed_site_url = parse_url( site_url() );
723
+ if ( isset( $parsed_site_url['path'] ) && '/' != $parsed_site_url['path'] ) {
724
  $requested_path = preg_replace( '@' . $parsed_site_url['path'] . '@i', '', $requested_path, 1 );
725
  }
726
+
727
+ // Allow redirects to be filtered
728
+ $redirects = apply_filters( 'srm_registered_redirects', $redirects, $requested_path );
729
+
730
+ foreach ( (array)$redirects as $redirect ) {
731
+
732
  $redirect_from = untrailingslashit( $redirect['redirect_from'] );
733
  if ( empty( $redirect_from ) )
734
  $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
735
+
736
  $redirect_to = $redirect['redirect_to'];
737
  $status_code = $redirect['status_code'];
738
+ $enable_regex = ( isset( $redirect['enable_regex'] ) ) ? $redirect['enable_regex'] : false;
739
 
740
  if ( apply_filters( 'srm_case_insensitive_redirects', true ) ) {
741
  $requested_path = strtolower( $requested_path );
742
  $redirect_from = strtolower( $redirect_from );
743
  }
744
+
745
  // check if requested path is the same as the redirect from path
746
+ if ( $enable_regex ) {
747
+ $matched_path = preg_match( '@' . $redirect_from . '@', $requested_path );
748
+ } else {
749
+ $matched_path = ( untrailingslashit( $requested_path ) == $redirect_from );
750
+
751
+ // check if the redirect_from ends in a wildcard
752
+ if ( !$matched_path && (strrpos( $redirect_from, '*' ) === strlen( $redirect_from ) - 1) ) {
753
+ $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
754
+
755
+ // mark as match if requested path matches the base of the redirect from
756
+ $matched_path = (substr( $requested_path, 0, strlen( $wildcard_base ) ) == $wildcard_base);
757
+ if ( (strrpos( $redirect_to, '*' ) == strlen( $redirect_to ) - 1 ) ) {
758
+ $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
759
+ }
760
  }
761
  }
762
 
763
+ if ( $matched_path ) {
764
  // whitelist redirect to host if necessary
765
  $parsed_redirect = parse_url( $redirect_to );
766
  if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
767
  $this->whitelist_hosts[] = $parsed_redirect['host'];
768
  add_filter( 'allowed_redirect_hosts' , array( $this, 'filter_allowed_redirect_hosts' ) );
769
  }
770
+
771
  header("X-Safe-Redirect-Manager: true");
772
+
773
  // if we have a valid status code, then redirect with it
774
  if ( in_array( $status_code, $this->valid_status_codes ) )
775
  wp_safe_redirect( esc_url_raw( $redirect_to ), $status_code );
779
  }
780
  }
781
  }
782
+
783
  /**
784
  * Sanitize redirect to path
785
  *
793
  */
794
  public function sanitize_redirect_to( $path ) {
795
  $path = trim( $path );
796
+
797
  if ( preg_match( '/^www\./i', $path ) )
798
  $path = 'http://' . $path;
799
+
800
  if ( ! preg_match( '/^https?:\/\//i', $path ) )
801
  if ( strpos( $path, '/' ) !== 0 )
802
  $path = '/' . $path;
803
+
804
  return esc_url_raw( $path );
805
  }
806
+
807
  /**
808
  * Sanitize redirect from path
809
  *
810
  * @since 1.0
811
  * @param string $path
812
+ * @param boolean $allow_regex
813
  * @uses esc_url_raw
814
  * @return string
815
  */
816
+ public function sanitize_redirect_from( $path, $allow_regex = false ) {
817
+
818
  $path = trim( $path );
819
+
820
  if ( empty( $path ) )
821
  return '';
822
+
823
  // dont accept paths starting with a .
824
+ if ( ! $allow_regex && strpos( $path, '.' ) === 0 )
825
  return '';
826
+
827
  // turn path in to absolute
828
  if ( preg_match( '/https?:\/\//i', $path ) )
829
  $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
830
+ elseif ( ! $allow_regex && strpos( $path, '/' ) !== 0 )
831
  $path = '/' . $path;
832
+
833
+ // the @ symbol will break our regex engine
834
+ $path = str_replace( '@', '', $path );
835
+
836
+ return $path;
837
  }
838
  }
839
 
screenshot-1.png CHANGED
Binary file
screenshot-2.png CHANGED
Binary file