Safe Redirect Manager - Version 1.7.9

Version Description

(Nov. 25, 2018) = * Fix SQL injection bug in search functionality

Download this release

Release Info

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

Code changes from version 1.9 to 1.7.9

README.md CHANGED
@@ -1,12 +1,8 @@
1
- Safe Redirect Manager [![Build Status](https://travis-ci.org/10up/safe-redirect-manager.svg?branch=master)](https://travis-ci.org/10up/safe-redirect-manager)
2
  ==============
3
 
4
  A WordPress plugin to safely and easily manage your website's HTTP redirects.
5
 
6
- <p align="center">
7
- <a href="http://10up.com/contact/"><img src="https://10updotcom-wpengine.s3.amazonaws.com/uploads/2016/10/10up-Github-Banner.png" width="850"></a>
8
- </p>
9
-
10
  ## Purpose
11
 
12
  Easily and safely manage your site's redirects the WordPress way. There are many redirect plugins available. Most of
@@ -14,12 +10,12 @@ them store redirects in the options table or in custom tables. Most of them prov
14
  of them have serious performance implications (404 error logging). Safe Redirect Manager stores redirects as Custom
15
  Post Types. This makes your data portable and your website scalable. Safe Redirect Manager is built to handle enterprise
16
  level traffic and is used on major publishing websites. The plugin comes with only what you need following the
17
- WordPress mantra, decisions not options. Actions in filters make the plugin very extensible.
18
 
19
  ## Installation
20
 
21
  Install the plugin in WordPress. You can download a
22
- [zip via Github](https://github.com/10up/safe-redirect-manager/archive/master.zip) and upload it using the WP
23
  plugin uploader.
24
 
25
  ## Non-English Usage
@@ -59,7 +55,7 @@ temporarily moved, or 301, permanently moved.
59
 
60
  * Redirects are cached using the Transients API. Cache busts occur when redirects are added, updated, and deleted
61
  so you shouldn't be serving stale redirects.
62
- * By default the plugin only allows at most 250 redirects to prevent performance issues. There is a filter
63
  `srm_max_redirects` that you can utilize to up this number.
64
  * "Redirect From" and requested paths are case insensitive by default.
65
 
@@ -75,16 +71,35 @@ add_filter( 'my_srm_redirect_loop_filter', '__return_true' );
75
 
76
  #### Setup
77
  Follow the configuration instructions above to setup the plugin. I recommend developing the plugin locally in an
78
- environment such as [WP Local Docker](https://github.com/10up/wp-local-docker).
 
 
 
 
 
79
 
80
  #### Testing
81
  Within the terminal change directories to the plugin folder. Initialize your unit testing environment by running the
82
  following command:
83
 
 
84
  ```bash
85
- bash bin/install-wp-tests.sh database username password host version
86
  ```
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  Run the plugin tests:
89
  ```bash
90
  phpunit
@@ -92,4 +107,4 @@ phpunit
92
 
93
  #### Issues
94
  If you identify any errors or have an idea for improving the plugin, please
95
- [open an issue](https://github.com/10up/safe-redirect-manager/issues?state=open).
1
+ Safe Redirect Manager [![Build Status](https://travis-ci.org/tlovett1/Safe-Redirect-Manager.svg?branch=master)](https://travis-ci.org/tlovett1/Safe-Redirect-Manager) [![Dockunit Status](https://dockunit.io/svg/tlovett1/Safe-Redirect-Manager?master)](https://dockunit.io/projects/tlovett1/Safe-Redirect-Manager#master)
2
  ==============
3
 
4
  A WordPress plugin to safely and easily manage your website's HTTP redirects.
5
 
 
 
 
 
6
  ## Purpose
7
 
8
  Easily and safely manage your site's redirects the WordPress way. There are many redirect plugins available. Most of
10
  of them have serious performance implications (404 error logging). Safe Redirect Manager stores redirects as Custom
11
  Post Types. This makes your data portable and your website scalable. Safe Redirect Manager is built to handle enterprise
12
  level traffic and is used on major publishing websites. The plugin comes with only what you need following the
13
+ WordPress mantra decisions not options. Actions in filters make the plugin very extensible.
14
 
15
  ## Installation
16
 
17
  Install the plugin in WordPress. You can download a
18
+ [zip via Github](https://github.com/tlovett1/safe-redirect-manager/archive/master.zip) and upload it using the WP
19
  plugin uploader.
20
 
21
  ## Non-English Usage
55
 
56
  * Redirects are cached using the Transients API. Cache busts occur when redirects are added, updated, and deleted
57
  so you shouldn't be serving stale redirects.
58
+ * By default the plugin only allows at most 150 redirects to prevent performance issues. There is a filter
59
  `srm_max_redirects` that you can utilize to up this number.
60
  * "Redirect From" and requested paths are case insensitive by default.
61
 
71
 
72
  #### Setup
73
  Follow the configuration instructions above to setup the plugin. I recommend developing the plugin locally in an
74
+ environment such as [Varying Vagrant Vagrants](https://github.com/Varying-Vagrant-Vagrants/VVV).
75
+
76
+ #### Translation
77
+ Safe Redirect Manager has a [.pot file](https://github.com/tlovett1/Safe-Redirect-Manager/blob/master/languages/safe-redirect-manager.pot)
78
+ containing strings ready for translation. You can use a program like [POedit](http://poedit.net) to generate .po/.mo
79
+ files for your language.
80
 
81
  #### Testing
82
  Within the terminal change directories to the plugin folder. Initialize your unit testing environment by running the
83
  following command:
84
 
85
+ For VVV users:
86
  ```bash
87
+ bash bin/install-wp-tests.sh wordpress_test root root localhost latest
88
  ```
89
 
90
+ For VIP Quickstart users:
91
+ ```bash
92
+ bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
93
+ ```
94
+
95
+ where:
96
+
97
+ * wordpress_test is the name of the test database (all data will be deleted!)
98
+ * root is the MySQL user name
99
+ * root is the MySQL user password (if you're running VVV). Blank if you're running VIP Quickstart.
100
+ * localhost is the MySQL server host
101
+ * latest is the WordPress version; could also be 3.7, 3.6.2 etc.
102
+
103
  Run the plugin tests:
104
  ```bash
105
  phpunit
107
 
108
  #### Issues
109
  If you identify any errors or have an idea for improving the plugin, please
110
+ [open an issue](https://github.com/tlovett1/safe-redirect-manager/issues?state=open).
inc/classes/class-srm-post-type.php DELETED
@@ -1,660 +0,0 @@
1
- <?php
2
- /**
3
- * Setup SRM post type
4
- */
5
-
6
- class SRM_Post_Type {
7
-
8
- public $status_code_labels = array(); // Defined later to allow i18n
9
-
10
- private $whitelist_hosts = array();
11
-
12
- /**
13
- * Sets up redirect manager
14
- *
15
- * @since 1.8
16
- */
17
- public function setup() {
18
- $this->status_code_labels = array(
19
- 301 => esc_html__( 'Moved Permanently', 'safe-redirect-manager' ),
20
- 302 => esc_html__( 'Found', 'safe-redirect-manager' ),
21
- 303 => esc_html__( 'See Other', 'safe-redirect-manager' ),
22
- 307 => esc_html__( 'Temporary Redirect', 'safe-redirect-manager' ),
23
- 403 => esc_html__( 'Forbidden', 'safe-redirect-manager' ),
24
- 404 => esc_html__( 'Not Found', 'safe-redirect-manager' ),
25
- );
26
-
27
- add_action( 'init', array( $this, 'action_register_post_types' ) );
28
- add_action( 'save_post', array( $this, 'action_save_post' ) );
29
- add_filter( 'manage_redirect_rule_posts_columns', array( $this, 'filter_redirect_columns' ) );
30
- add_filter( 'manage_edit-redirect_rule_sortable_columns', array( $this, 'filter_redirect_sortable_columns' ) );
31
- add_action( 'manage_redirect_rule_posts_custom_column', array( $this, 'action_custom_redirect_columns' ), 10, 2 );
32
- add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
33
- add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
34
- add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
35
- add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
36
- add_filter( 'bulk_actions-edit-redirect_rule', array( $this, 'filter_bulk_actions' ) );
37
- add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
38
- add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
39
- add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
40
- add_filter( 'post_type_link', array( $this, 'filter_post_type_link' ), 10, 2 );
41
- add_filter( 'default_hidden_columns', array( $this, 'filter_hidden_columns' ), 10, 1 );
42
-
43
- // Search filters
44
- add_filter( 'posts_join', array( $this, 'filter_search_join' ) );
45
- add_filter( 'posts_where', array( $this, 'filter_search_where' ) );
46
- add_filter( 'posts_distinct', array( $this, 'filter_search_distinct' ) );
47
- add_filter( 'post_row_actions', array( $this, 'filter_disable_quick_edit' ), 10, 2 );
48
- }
49
-
50
- /**
51
- * Hide order column by default
52
- *
53
- * @param array $hidden
54
- * @since 1.9
55
- * @return array
56
- */
57
- public function filter_hidden_columns( $hidden ) {
58
-
59
- if ( empty( $_GET['post_type'] ) || 'redirect_rule' !== $_GET['post_type'] ) {
60
- return $hidden;
61
- }
62
-
63
- $hidden[] = 'menu_order';
64
-
65
- return $hidden;
66
- }
67
-
68
- /**
69
- * Remove quick edit
70
- *
71
- * @param array $actions
72
- * @param WP_Post $post
73
- * @since 1.8
74
- * @return array
75
- */
76
- public function filter_disable_quick_edit( $actions = array(), $post ) {
77
- if ( 'redirect_rule' === get_post_type( $post ) && isset( $actions['inline hide-if-no-js'] ) ) {
78
- unset( $actions['inline hide-if-no-js'] );
79
- unset( $actions['view'] );
80
- }
81
-
82
- return $actions;
83
- }
84
-
85
- /**
86
- * Join posts table with postmeta table on search
87
- *
88
- * @since 1.2
89
- * @param string $join
90
- * @uses get_query_var
91
- * @return string
92
- */
93
- public function filter_search_join( $join ) {
94
- global $wp_query;
95
-
96
- if ( empty( $wp_query ) || 'redirect_rule' !== get_query_var( 'post_type' ) ) {
97
- return $join;
98
- }
99
-
100
- global $wpdb;
101
-
102
- $s = get_query_var( 's' );
103
- if ( ! empty( $s ) ) {
104
- $join .= " LEFT JOIN $wpdb->postmeta AS m ON ($wpdb->posts.ID = m.post_id) ";
105
- }
106
- return $join;
107
- }
108
-
109
- /**
110
- * Return distinct search results
111
- *
112
- * @since 1.2
113
- * @param string $distinct
114
- * @uses get_query_var
115
- * @return string
116
- */
117
- public function filter_search_distinct( $distinct ) {
118
- global $wp_query;
119
-
120
- if ( empty( $wp_query ) || 'redirect_rule' !== get_query_var( 'post_type' ) ) {
121
- return $distinct;
122
- }
123
-
124
- return 'DISTINCT';
125
- }
126
-
127
- /**
128
- * Join posts table with postmeta table on search
129
- *
130
- * @since 1.2
131
- * @param string $where
132
- * @uses is_search, get_query_var
133
- * @return string
134
- */
135
- public function filter_search_where( $where ) {
136
- global $wp_query;
137
-
138
- if ( empty( $wp_query ) || 'redirect_rule' !== get_query_var( 'post_type' ) || ! is_search() || empty( $where ) ) {
139
- return $where;
140
- }
141
-
142
- $terms = $this->get_search_terms();
143
-
144
- if ( empty( $terms ) ) {
145
- return $where;
146
- }
147
-
148
- $exact = get_query_var( 'exact' );
149
- $n = ( ! empty( $exact ) ) ? '' : '%';
150
-
151
- $search = '';
152
- $seperator = '';
153
- $search .= '(';
154
-
155
- // we check the meta values against each term in the search
156
- foreach ( $terms as $term ) {
157
- $search .= $seperator;
158
- // Used esc_sql instead of wpdb->prepare since wpdb->prepare wraps things in quotes
159
- $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, esc_sql( $term ), $n, esc_sql( '_redirect_rule_from' ), $n, esc_sql( $term ), $n, esc_sql( '_redirect_rule_to' ) );
160
-
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( array( 'SRM_Post_Type', 'clean_search_term' ), $matches[0] );
184
- }
185
- return $search_terms;
186
- }
187
-
188
- public static function clean_search_term( $a ) {
189
- return trim( $a, "\\\"'\\n\\r " );
190
- }
191
-
192
- /**
193
- * Swap tools logo for plugin logo
194
- *
195
- * @since 1.1
196
- * @uses plugins_url
197
- * @return void
198
- */
199
- public function action_print_logo_css() {
200
- if ( $this->is_plugin_page() ) {
201
- ?>
202
- <style type="text/css">
203
- #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group, #preview-action {
204
- display: none;
205
- }
206
- #srm_redirect_rule_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
- * Whether or not this is an admin page specific to the plugin
230
- *
231
- * @since 1.1
232
- * @uses get_post_type
233
- * @return bool
234
- */
235
- private function is_plugin_page() {
236
- return (bool) ( get_post_type() === 'redirect_rule' || ( isset( $_GET['post_type'] ) && 'redirect_rule' === $_GET['post_type'] ) );
237
- }
238
-
239
- /**
240
- * Echoes admin message if redirect chains exist
241
- *
242
- * @since 1.0
243
- * @uses apply_filters
244
- * @return void
245
- */
246
- public function action_redirect_chain_alert() {
247
- global $hook_suffix;
248
- if ( $this->is_plugin_page() ) {
249
-
250
- /**
251
- * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
252
- * will be running slow. Let's disable it by default.
253
- */
254
- if ( apply_filters( 'srm_check_for_possible_redirect_loops', false ) ) {
255
- if ( srm_check_for_possible_redirect_loops() ) {
256
- ?>
257
- <div class="updated">
258
- <p><?php esc_html_e( 'Safe Redirect Manager Warning: Possible redirect loops and/or chains have been created.', 'safe-redirect-manager' ); ?></p>
259
- </div>
260
- <?php
261
- }
262
- }
263
- if ( srm_max_redirects_reached() ) {
264
-
265
- if ( 'post-new.php' === $hook_suffix ) {
266
- ?>
267
- <style type="text/css">#post { display: none; }</style>
268
- <?php
269
- }
270
- ?>
271
- <div class="error">
272
- <p><?php esc_html_e( 'Safe Redirect Manager Error: You have reached the maximum allowable number of redirects', 'safe-redirect-manager' ); ?></p>
273
- </div>
274
- <?php
275
- }
276
- }
277
- }
278
-
279
- /**
280
- * Filters title out for redirect from in post manager
281
- *
282
- * @since 1.0
283
- * @param string $title
284
- * @param int $post_id
285
- * @uses is_admin, get_post_meta
286
- * @return string
287
- */
288
- public function filter_admin_title( $title, $post_id = 0 ) {
289
- if ( ! is_admin() ) {
290
- return $title;
291
- }
292
-
293
- $redirect = get_post( $post_id );
294
- if ( empty( $redirect ) ) {
295
- return $title;
296
- }
297
-
298
- if ( $redirect->post_type !== 'redirect_rule' ) {
299
- return $title;
300
- }
301
-
302
- $redirect_from = get_post_meta( $post_id, '_redirect_rule_from', true );
303
- if ( ! empty( $redirect_from ) ) {
304
- return $redirect_from;
305
- }
306
-
307
- return $title;
308
- }
309
-
310
- /**
311
- * Customizes updated messages for redirects
312
- *
313
- * @since 1.0
314
- * @param array $messages
315
- * @uses esc_url, get_permalink, add_query_var, wp_post_revision_title
316
- * @return array
317
- */
318
- public function filter_redirect_updated_messages( $messages ) {
319
- global $post, $post_ID;
320
-
321
- $messages['redirect_rule'] = array(
322
- 0 => '', // Unused. Messages start at index 1.
323
- 1 => sprintf( esc_html__( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
324
- 2 => esc_html__( 'Custom field updated.', 'safe-redirect-manager' ),
325
- 3 => esc_html__( 'Custom field deleted.', 'safe-redirect-manager' ),
326
- 4 => esc_html__( 'Redirect rule updated.', 'safe-redirect-manager' ),
327
- /* translators: %s: date and time of the revision */
328
- 5 => isset( $_GET['revision'] ) ? sprintf( esc_html__( 'Redirect rule restored to revision from %s', 'safe-redirect-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
329
- 6 => sprintf( esc_html__( 'Redirect rule published.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
330
- 7 => esc_html__( 'Redirect rule saved.', 'safe-redirect-manager' ),
331
- 8 => sprintf( esc_html__( 'Redirect rule submitted.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
332
- 9 => sprintf(
333
- esc_html__( 'Redirect rule scheduled for: %1$s.', 'safe-redirect-manager' ),
334
- // translators: Publish box date format, see http://php.net/date
335
- date_i18n( esc_html__( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink( $post_ID ) )
336
- ),
337
- 10 => sprintf( esc_html__( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
338
- );
339
-
340
- return $messages;
341
- }
342
-
343
- /**
344
- * Clear redirect cache if appropriate post type is transitioned
345
- *
346
- * @since 1.0
347
- * @param string $new_status
348
- * @param string $old_status
349
- * @param object $post
350
- * @return void
351
- */
352
- public function action_transition_post_status( $new_status, $old_status, $post ) {
353
- if ( ! is_object( $post ) ) {
354
- return;
355
- }
356
-
357
- // recreate redirect cache
358
- if ( 'redirect_rule' === $post->post_type ) {
359
- srm_flush_cache();
360
- }
361
- }
362
-
363
- /**
364
- * Displays custom columns on redirect manager screen
365
- *
366
- * @since 1.0
367
- * @param string $column
368
- * @param int $post_id
369
- * @uses get_post_meta, esc_html, admin_url
370
- * @return void
371
- */
372
- public function action_custom_redirect_columns( $column, $post_id ) {
373
- if ( 'srm_redirect_rule_to' === $column ) {
374
- echo esc_html( get_post_meta( $post_id, '_redirect_rule_to', true ) );
375
- } elseif ( 'srm_redirect_rule_status_code' === $column ) {
376
- echo absint( get_post_meta( $post_id, '_redirect_rule_status_code', true ) );
377
- } elseif ( 'menu_order' == $column ) {
378
- global $post;
379
- echo $post->menu_order;
380
- }
381
- }
382
-
383
- /**
384
- * Add new columns to manage redirect screen
385
- *
386
- * @since 1.0
387
- * @param array $columns
388
- * @return array
389
- */
390
- public function filter_redirect_columns( $columns ) {
391
- $columns['srm_redirect_rule_to'] = esc_html__( 'Redirect To', 'safe-redirect-manager' );
392
- $columns['srm_redirect_rule_status_code'] = esc_html__( 'HTTP Status Code', 'safe-redirect-manager' );
393
- $columns['menu_order'] = esc_html__( 'Order', 'safe-redirect-manager' );
394
-
395
- // Change the title column
396
- $columns['title'] = esc_html__( 'Redirect From', 'safe-redirect-manager' );
397
-
398
- // Move date column to the back
399
- unset( $columns['date'] );
400
- $columns['date'] = esc_html__( 'Date', 'safe-redirect-manager' );
401
-
402
- return $columns;
403
- }
404
-
405
- /**
406
- * Allow menu_order column to be sortable.
407
- *
408
- * @param $columns
409
- * @since 1.9
410
- * @return mixed
411
- */
412
- public function filter_redirect_sortable_columns( $columns ) {
413
- $columns['menu_order'] = 'menu_order';
414
- return $columns;
415
- }
416
-
417
- /**
418
- * Saves meta info for redirect rules
419
- *
420
- * @since 1.0
421
- * @param int $post_id
422
- * @uses current_user_can, get_post_type, wp_verify_nonce, update_post_meta, delete_post_meta
423
- * @return void
424
- */
425
- public function action_save_post( $post_id ) {
426
- if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' === get_post_type( $post_id ) ) {
427
- return;
428
- }
429
-
430
- // Update post meta for redirect rules
431
- if ( ! empty( $_POST['srm_redirect_nonce'] ) && wp_verify_nonce( $_POST['srm_redirect_nonce'], 'srm-save-redirect-meta' ) ) {
432
-
433
- if ( ! empty( $_POST['srm_redirect_rule_from_regex'] ) ) {
434
- $allow_regex = (bool) $_POST['srm_redirect_rule_from_regex'];
435
- update_post_meta( $post_id, '_redirect_rule_from_regex', $allow_regex );
436
- } else {
437
- $allow_regex = false;
438
- delete_post_meta( $post_id, '_redirect_rule_from_regex' );
439
- }
440
-
441
- if ( ! empty( $_POST['srm_redirect_rule_from'] ) ) {
442
- update_post_meta( $post_id, '_redirect_rule_from', srm_sanitize_redirect_from( $_POST['srm_redirect_rule_from'], $allow_regex ) );
443
- } else {
444
- delete_post_meta( $post_id, '_redirect_rule_from' );
445
- }
446
-
447
- if ( ! empty( $_POST['srm_redirect_rule_to'] ) ) {
448
- update_post_meta( $post_id, '_redirect_rule_to', srm_sanitize_redirect_to( $_POST['srm_redirect_rule_to'] ) );
449
- } else {
450
- delete_post_meta( $post_id, '_redirect_rule_to' );
451
- }
452
-
453
- if ( ! empty( $_POST['srm_redirect_rule_status_code'] ) ) {
454
- update_post_meta( $post_id, '_redirect_rule_status_code', absint( $_POST['srm_redirect_rule_status_code'] ) );
455
- } else {
456
- delete_post_meta( $post_id, '_redirect_rule_status_code' );
457
- }
458
-
459
- if ( ! empty( $_POST['srm_redirect_rule_status_code'] ) ) {
460
- update_post_meta( $post_id, '_redirect_rule_notes', sanitize_text_field( $_POST['srm_redirect_rule_notes'] ) );
461
- } else {
462
- delete_post_meta( $post_id, '_redirect_rule_notes' );
463
- }
464
-
465
- /**
466
- * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
467
- * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
468
- * redirect info is saved, updating the cache before it is not sufficient.
469
- */
470
- srm_flush_cache();
471
- }
472
- }
473
-
474
- /**
475
- * Registers post types for plugin
476
- *
477
- * @since 1.0
478
- * @uses register_post_type, _x, plugins_url, apply_filters
479
- * @return void
480
- */
481
- public function action_register_post_types() {
482
- $redirect_labels = array(
483
- 'name' => esc_html_x( 'Safe Redirect Manager', 'post type general name', 'safe-redirect-manager' ),
484
- 'singular_name' => esc_html_x( 'Redirect', 'post type singular name', 'safe-redirect-manager' ),
485
- 'add_new' => _x( 'Create Redirect Rule', 'redirect rule', 'safe-redirect-manager' ),
486
- 'add_new_item' => esc_html__( 'Safe Redirect Manager', 'safe-redirect-manager' ),
487
- 'edit_item' => esc_html__( 'Edit Redirect Rule', 'safe-redirect-manager' ),
488
- 'new_item' => esc_html__( 'New Redirect Rule', 'safe-redirect-manager' ),
489
- 'all_items' => esc_html__( 'Safe Redirect Manager', 'safe-redirect-manager' ),
490
- 'view_item' => esc_html__( 'View Redirect Rule', 'safe-redirect-manager' ),
491
- 'search_items' => esc_html__( 'Search Redirects', 'safe-redirect-manager' ),
492
- 'not_found' => esc_html__( 'No redirect rules found.', 'safe-redirect-manager' ),
493
- 'not_found_in_trash' => esc_html__( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
494
- 'parent_item_colon' => '',
495
- 'menu_name' => esc_html__( 'Safe Redirect Manager', 'safe-redirect-manager' ),
496
- );
497
-
498
- $redirect_capability = 'srm_manage_redirects';
499
-
500
- $roles = array( 'administrator' );
501
-
502
- foreach ( $roles as $role ) {
503
- $role = get_role( $role );
504
-
505
- if ( empty( $role ) || $role->has_cap( $redirect_capability ) ) {
506
- continue;
507
- }
508
-
509
- $role->add_cap( $redirect_capability );
510
- }
511
-
512
- $redirect_capability = apply_filters( 'srm_restrict_to_capability', $redirect_capability );
513
-
514
- $capabilities = array(
515
- 'edit_post' => $redirect_capability,
516
- 'read_post' => $redirect_capability,
517
- 'delete_post' => $redirect_capability,
518
- 'delete_posts' => $redirect_capability,
519
- 'edit_posts' => $redirect_capability,
520
- 'edit_others_posts' => $redirect_capability,
521
- 'publish_posts' => $redirect_capability,
522
- 'read_private_posts' => $redirect_capability,
523
- );
524
-
525
- $redirect_args = array(
526
- 'labels' => $redirect_labels,
527
- 'public' => false,
528
- 'publicly_queryable' => true,
529
- 'show_ui' => true,
530
- 'show_in_menu' => 'tools.php',
531
- 'query_var' => false,
532
- 'rewrite' => false,
533
- 'capability_type' => 'post',
534
- 'capabilities' => $capabilities,
535
- 'has_archive' => false,
536
- 'hierarchical' => false,
537
- 'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
538
- 'menu_position' => 80,
539
- 'supports' => array( 'page-attributes' ),
540
- );
541
- register_post_type( 'redirect_rule', $redirect_args );
542
- }
543
-
544
- /**
545
- * Registers meta boxes for redirect rule post type
546
- *
547
- * @since 1.0
548
- * @uses add_meta_box
549
- * @return void
550
- */
551
- public function action_redirect_rule_metabox() {
552
- add_meta_box( 'redirect_settings', esc_html__( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), 'redirect_rule', 'normal', 'core' );
553
- }
554
-
555
- /**
556
- * Echoes HTML for redirect rule meta box
557
- *
558
- * @since 1.0
559
- * @param object $post
560
- * @uses wp_nonce_field, get_post_meta, esc_attr, selected
561
- * @return void
562
- */
563
- public function redirect_rule_metabox( $post ) {
564
- wp_nonce_field( 'srm-save-redirect-meta', 'srm_redirect_nonce' );
565
-
566
- $redirect_from = get_post_meta( $post->ID, '_redirect_rule_from', true );
567
- $redirect_to = get_post_meta( $post->ID, '_redirect_rule_to', true );
568
- $redirect_notes = get_post_meta( $post->ID, '_redirect_rule_notes', true );
569
- $status_code = get_post_meta( $post->ID, '_redirect_rule_status_code', true );
570
- $enable_regex = get_post_meta( $post->ID, '_redirect_rule_from_regex', true );
571
-
572
- if ( empty( $status_code ) ) {
573
- $status_code = apply_filters( 'srm_default_direct_status', 302 );
574
- }
575
- ?>
576
- <p>
577
- <label for="srm_redirect_rule_from"><strong><?php esc_html_e( '* Redirect From:', 'safe-redirect-manager' ); ?></strong></label><br />
578
- <input type="text" name="srm_redirect_rule_from" id="srm_redirect_rule_from" value="<?php echo esc_attr( $redirect_from ); ?>" />
579
- <input type="checkbox" name="srm_redirect_rule_from_regex" id="srm_redirect_rule_from_regex" <?php checked( true, (bool) $enable_regex ); ?> value="1" />
580
- <label for="srm_redirect_rule_from_regex"><?php esc_html_e( 'Enable Regular Expressions (advanced)', 'safe-redirect-manager' ); ?></label>
581
- </p>
582
- <p class="description"><?php esc_html_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>
583
-
584
- <p>
585
- <label for="srm_redirect_rule_to"><strong><?php esc_html_e( '* Redirect To:', 'safe-redirect-manager' ); ?></strong></label><br />
586
- <input class="widefat" type="text" name="srm_redirect_rule_to" id="srm_redirect_rule_to" value="<?php echo esc_attr( $redirect_to ); ?>" />
587
- </p>
588
- <p class="description"><?php esc_html_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>
589
-
590
- <p>
591
- <label for="srm_redirect_rule_status_code"><strong><?php esc_html_e( '* HTTP Status Code:', 'safe-redirect-manager' ); ?></strong></label>
592
- <select name="srm_redirect_rule_status_code" id="srm_redirect_rule_status_code">
593
- <?php foreach ( srm_get_valid_status_codes() as $code ) : ?>
594
- <option value="<?php echo esc_attr( $code ); ?>" <?php selected( $status_code, $code ); ?>><?php echo esc_html( $code . ' ' . $this->status_code_labels[ $code ] ); ?></option>
595
- <?php endforeach; ?>
596
- </select>
597
- <em><?php esc_html_e( "If you don't know what this is, leave it as is.", 'safe-redirect-manager' ); ?></em>
598
- </p>
599
-
600
- <p>
601
- <label for="srm_redirect_rule_notes"><strong><?php esc_html_e( 'Notes:', 'safe-redirect-manager' ); ?></strong></label>
602
- <textarea name="srm_redirect_rule_notes" id="srm_redirect_rule_notes" class="widefat"><?php echo esc_attr( $redirect_notes ); ?></textarea>
603
- <em><?php esc_html_e( 'Optionally leave notes on this redirect e.g. why was it created.', 'safe-redirect-manager' ); ?></em>
604
- </p>
605
- <?php
606
- }
607
-
608
- /**
609
- * Return a permalink for a redirect post, which is the "redirect from"
610
- * URL for that redirect.
611
- *
612
- * @since 1.7
613
- * @param string $permalink The permalink
614
- * @param object $post A Post object
615
- * @uses home_url, get_post_meta
616
- * @return string The permalink
617
- */
618
- public function filter_post_type_link( $permalink, $post ) {
619
- if ( 'redirect_rule' !== $post->post_type ) {
620
- return $permalink;
621
- }
622
-
623
- // We can't do anything to provide a permalink
624
- // for regex enabled redirects.
625
- if ( get_post_meta( $post->ID, '_redirect_rule_from_regex', true ) ) {
626
- return $permalink;
627
- }
628
-
629
- // We can't do anything if there is a wildcard in the redirect from
630
- $redirect_from = get_post_meta( $post->ID, '_redirect_rule_from', true );
631
- if ( false !== strpos( $redirect_from, '*' ) ) {
632
- return $permalink;
633
- }
634
-
635
- // Use default permalink if no $redirect_from exists - this prevents duplicate GUIDs
636
- if ( empty( $redirect_from ) ) {
637
- return $permalink;
638
- }
639
-
640
- return home_url( $redirect_from );
641
- }
642
-
643
- /**
644
- * Return singleton instance of class
645
- *
646
- * @return object
647
- * @since 1.8
648
- */
649
- public static function factory() {
650
- static $instance = false;
651
-
652
- if ( ! $instance ) {
653
- $instance = new self();
654
- $instance->setup();
655
- }
656
-
657
- return $instance;
658
- }
659
- }
660
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/classes/class-srm-redirect.php DELETED
@@ -1,202 +0,0 @@
1
- <?php
2
- /**
3
- * Handle redirection
4
- */
5
-
6
- class SRM_Redirect {
7
- /**
8
- * Store whitelisted host
9
- *
10
- * @var array
11
- */
12
- private $whitelist_host;
13
-
14
- /**
15
- * Initialize redirect listening
16
- *
17
- * @since 1.8
18
- */
19
- public function setup() {
20
- /**
21
- * To only redirect on 404 pages, use:
22
- * add_filter( 'srm_redirect_only_on_404', '__return_true' );
23
- */
24
- if ( apply_filters( 'srm_redirect_only_on_404', false ) ) {
25
- add_action( 'template_redirect', array( $this, 'maybe_redirect' ), 0 );
26
- } else {
27
- add_action( 'parse_request', array( $this, 'maybe_redirect' ), 0 );
28
- }
29
- }
30
-
31
- /**
32
- * Apply whitelisted host to allowed_redirect_hosts filter
33
- *
34
- * @since 1.8
35
- * @param array $hosts
36
- * @return array
37
- */
38
- public function filter_allowed_redirect_hosts( $hosts ) {
39
- $without_www = preg_replace( '/^www\./i', '', $this->whitelist_host );
40
- $with_www = 'www.' . $without_www;
41
-
42
- $hosts[] = $without_www;
43
- $hosts[] = $with_www;
44
-
45
- return array_unique( $hosts );
46
- }
47
-
48
- /**
49
- * Check current url against redirects
50
- *
51
- * @since 1.8
52
- */
53
- public function maybe_redirect() {
54
-
55
- // Don't redirect unless not on admin. If 404 filter enabled, require query is a 404.
56
- if ( is_admin() || ( apply_filters( 'srm_redirect_only_on_404', false ) && ! is_404() ) ) {
57
- return;
58
- }
59
-
60
- $redirects = srm_get_redirects();
61
-
62
- // If we have no redirects, there is no need to continue
63
- if ( empty( $redirects ) ) {
64
- return;
65
- }
66
-
67
- // get requested path and add a / before it
68
- $requested_path = esc_url_raw( apply_filters( 'srm_requested_path', $_SERVER['REQUEST_URI'] ) );
69
- $requested_path = untrailingslashit( stripslashes( $requested_path ) );
70
-
71
- /**
72
- * If WordPress resides in a directory that is not the public root, we have to chop
73
- * the pre-WP path off the requested path.
74
- */
75
- if ( function_exists( 'wp_parse_url' ) ) {
76
- $parsed_home_url = wp_parse_url( home_url() );
77
- } else {
78
- $parsed_home_url = parse_url( home_url() );
79
- }
80
-
81
- if ( isset( $parsed_home_url['path'] ) && '/' !== $parsed_home_url['path'] ) {
82
- $requested_path = preg_replace( '@' . $parsed_home_url['path'] . '@i', '', $requested_path, 1 );
83
- }
84
-
85
- if ( empty( $requested_path ) ) {
86
- $requested_path = '/';
87
- }
88
-
89
- // Allow redirects to be filtered
90
- $redirects = apply_filters( 'srm_registered_redirects', $redirects, $requested_path );
91
-
92
- // Allow for case insensitive redirects
93
- $case_insensitive = apply_filters( 'srm_case_insensitive_redirects', true );
94
-
95
- if ( $case_insensitive ) {
96
- $regex_flag = 'i';
97
- // normalized path is used for matching but not for replace
98
- $normalized_requested_path = strtolower( $requested_path );
99
- } else {
100
- $regex_flag = '';
101
- $normalized_requested_path = $requested_path;
102
- }
103
-
104
- foreach ( (array) $redirects as $redirect ) {
105
-
106
- $redirect_from = untrailingslashit( $redirect['redirect_from'] );
107
- if ( empty( $redirect_from ) ) {
108
- $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
109
- }
110
-
111
- $redirect_to = $redirect['redirect_to'];
112
- $status_code = $redirect['status_code'];
113
- $enable_regex = ( isset( $redirect['enable_regex'] ) ) ? $redirect['enable_regex'] : false;
114
-
115
- // check if the redirection destination is valid, otherwise just skip it
116
- if ( empty( $redirect_to ) ) {
117
- continue;
118
- }
119
-
120
- // check if requested path is the same as the redirect from path
121
- if ( $enable_regex ) {
122
- $matched_path = preg_match( '@' . $redirect_from . '@' . $regex_flag, $requested_path );
123
- } else {
124
- if ( $case_insensitive ) {
125
- $redirect_from = strtolower( $redirect_from );
126
- }
127
-
128
- $matched_path = ( $normalized_requested_path === $redirect_from );
129
-
130
- // check if the redirect_from ends in a wildcard
131
- if ( ! $matched_path && ( strrpos( $redirect_from, '*' ) === strlen( $redirect_from ) - 1 ) ) {
132
- $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
133
-
134
- // mark as match if requested path matches the base of the redirect from
135
- $matched_path = ( substr( $normalized_requested_path, 0, strlen( $wildcard_base ) ) === $wildcard_base );
136
- if ( ( strrpos( $redirect_to, '*' ) === strlen( $redirect_to ) - 1 ) ) {
137
- $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
138
- }
139
- }
140
- }
141
-
142
- if ( $matched_path ) {
143
- /**
144
- * Whitelist redirect host
145
- */
146
- if ( function_exists( 'wp_parse_url' ) ) {
147
- $parsed_redirect = wp_parse_url( $redirect_to );
148
- } else {
149
- $parsed_redirect = parse_url( $redirect_to );
150
- }
151
-
152
- if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
153
- $this->whitelist_host = $parsed_redirect['host'];
154
- add_filter( 'allowed_redirect_hosts', array( $this, 'filter_allowed_redirect_hosts' ) );
155
- }
156
-
157
- // Allow for regex replacement in $redirect_to
158
- if ( $enable_regex ) {
159
- $redirect_to = preg_replace( '@' . $redirect_from . '@' . $regex_flag, $redirect_to, $requested_path );
160
- }
161
-
162
- $sanitized_redirect_to = esc_url_raw( apply_filters( 'srm_redirect_to', $redirect_to ) );
163
-
164
- do_action( 'srm_do_redirect', $requested_path, $sanitized_redirect_to, $status_code );
165
-
166
- if ( defined( 'PHPUNIT_SRM_TESTSUITE' ) && PHPUNIT_SRM_TESTSUITE ) {
167
- // Don't actually redirect if we are testing
168
- return;
169
- }
170
-
171
- header( 'X-Safe-Redirect-Manager: true' );
172
-
173
- // if we have a valid status code, then redirect with it
174
- if ( in_array( $status_code, srm_get_valid_status_codes(), true ) ) {
175
- wp_safe_redirect( $sanitized_redirect_to, $status_code );
176
- } else {
177
- wp_safe_redirect( $sanitized_redirect_to );
178
- }
179
-
180
- exit;
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * Return singleton instance of class
187
- *
188
- * @return object
189
- * @since 1.8
190
- */
191
- public static function factory() {
192
- static $instance = false;
193
-
194
- if ( ! $instance ) {
195
- $instance = new self();
196
- $instance->setup();
197
- }
198
-
199
- return $instance;
200
- }
201
- }
202
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/functions.php DELETED
@@ -1,350 +0,0 @@
1
- <?php
2
-
3
- /**
4
- * Get redirects from the database
5
- *
6
- * @since 1.8
7
- * @param array $args Any arguments to filter by
8
- * @param bool $hard Force cache refresh
9
- * @return array $redirects An array of redirects
10
- */
11
- function srm_get_redirects( $args = array(), $hard = false ) {
12
-
13
- if ( $hard || false === ( $redirects = get_transient( '_srm_redirects' ) ) ) {
14
-
15
- $redirects = array();
16
-
17
- $posts_per_page = 100;
18
-
19
- $i = 1;
20
-
21
- $default_max_redirects = apply_filters( 'srm_max_redirects', 250 );
22
-
23
- while ( true ) {
24
- if ( count( $redirects ) >= $default_max_redirects ) {
25
- break;
26
- }
27
-
28
- $defaults = array(
29
- 'posts_per_page' => $posts_per_page,
30
- 'post_status' => 'publish',
31
- 'paged' => $i,
32
- 'fields' => 'ids',
33
- 'orderby' => 'menu_order ID',
34
- 'order' => 'ASC',
35
- );
36
-
37
- $query_args = array_merge( $defaults, $args );
38
-
39
- // Some arguments that don't need to be configurable
40
- $query_args['post_type'] = 'redirect_rule';
41
- $query_args['update_term_cache'] = false;
42
-
43
- $redirect_query = new WP_Query( $query_args );
44
-
45
- if ( ! $redirect_query->have_posts() ) {
46
- break;
47
- }
48
-
49
- foreach ( $redirect_query->posts as $redirect_id ) {
50
- if ( count( $redirects ) >= $default_max_redirects ) {
51
- break 2;
52
- }
53
-
54
- $redirects[] = array(
55
- 'ID' => $redirect_id,
56
- 'post_status' => get_post_status( $redirect_id ),
57
- 'redirect_from' => get_post_meta( $redirect_id, '_redirect_rule_from', true ),
58
- 'redirect_to' => get_post_meta( $redirect_id, '_redirect_rule_to', true ),
59
- 'status_code' => (int) get_post_meta( $redirect_id, '_redirect_rule_status_code', true ),
60
- 'enable_regex' => (bool) get_post_meta( $redirect_id, '_redirect_rule_from_regex', true ),
61
- );
62
- }
63
-
64
- $i++;
65
-
66
- }
67
-
68
- set_transient( '_srm_redirects', $redirects );
69
- }
70
-
71
- return $redirects;
72
- }
73
-
74
- /**
75
- * Returns true if max redirects have been reached
76
- *
77
- * @since 1.8
78
- * @return bool
79
- */
80
- function srm_max_redirects_reached() {
81
- $default_max_redirects = apply_filters( 'srm_max_redirects', 250 );
82
-
83
- $redirects = srm_get_redirects();
84
-
85
- return ( count( $redirects ) >= $default_max_redirects );
86
- }
87
-
88
- /**
89
- * Get valid HTTP status codes
90
- *
91
- * @since 1.8
92
- * @return array
93
- */
94
- function srm_get_valid_status_codes() {
95
- return apply_filters( 'srm_valid_status_codes', array( 301, 302, 303, 307, 403, 404 ) );
96
- }
97
-
98
- /**
99
- * Flush redirect cache
100
- *
101
- * @since 1.8
102
- */
103
- function srm_flush_cache() {
104
- delete_transient( '_srm_redirects' );
105
- }
106
-
107
-
108
- /**
109
- * Check for potential redirect loops or chains
110
- *
111
- * @since 1.8
112
- * @return boolean
113
- */
114
- function srm_check_for_possible_redirect_loops() {
115
- $redirects = srm_get_redirects();
116
-
117
- $current_url = parse_url( home_url() );
118
- $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
119
-
120
- foreach ( $redirects as $redirect ) {
121
- $redirect_from = $redirect['redirect_from'];
122
-
123
- // check redirect from against all redirect to's
124
- foreach ( $redirects as $compare_redirect ) {
125
- $redirect_to = $compare_redirect['redirect_to'];
126
-
127
- $redirect_url = parse_url( $redirect_to );
128
- $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
129
-
130
- // check if we are redirecting locally
131
- if ( empty( $redirect_host ) || $redirect_host === $this_host ) {
132
- $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
133
- $redirect_to_url = $redirect_to;
134
- if ( ! preg_match( '/https?:\/\//i', $redirect_to_url ) ) {
135
- $redirect_to_url = $this_host . $redirect_to_url;
136
- } else {
137
- $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
138
- }
139
-
140
- // possible loop/chain found
141
- if ( $redirect_to_url === $redirect_from_url ) {
142
- return true;
143
- }
144
- }
145
- }
146
- }
147
-
148
- return false;
149
- }
150
-
151
- /**
152
- * Creates a redirect post, this function will be useful for import/exports scripts
153
- *
154
- * @param string $redirect_from
155
- * @param string $redirect_to
156
- * @param int $status_code
157
- * @param bool $enable_regex
158
- * @param string $post_status
159
- * @param int $menu_order
160
- * @since 1.8
161
- * @uses wp_insert_post, update_post_meta
162
- * @return int|WP_Error
163
- */
164
- function srm_create_redirect( $redirect_from, $redirect_to, $status_code = 302, $enable_regex = false, $post_status = 'publish', $menu_order = 0 ) {
165
- global $wpdb;
166
-
167
- $sanitized_redirect_from = srm_sanitize_redirect_from( $redirect_from );
168
- $sanitized_redirect_to = srm_sanitize_redirect_to( $redirect_to );
169
- $sanitized_status_code = absint( $status_code );
170
- $sanitized_enable_regex = (bool) $enable_regex;
171
- $sanitized_post_status = sanitize_key( $post_status );
172
- $sanitized_menu_order = absint( $menu_order );
173
-
174
- // check and make sure no parameters are empty or invalid after sanitation
175
- if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) ) {
176
- return new WP_Error( 'invalid-argument', esc_html__( 'Redirect from and/or redirect to arguments are invalid.', 'safe-redirect-manager' ) );
177
- }
178
-
179
- if ( ! in_array( $sanitized_status_code, srm_get_valid_status_codes(), true ) ) {
180
- return new WP_Error( 'invalid-argument', esc_html__( 'Invalid status code.', 'safe-redirect-manager' ) );
181
- }
182
-
183
- // Check to ensure this redirect doesn't already exist
184
- if ( $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key=%s AND meta_value=%s", '_redirect_rule_from', $sanitized_redirect_from ) ) ) {
185
- return new WP_Error( 'duplicate-redirect', sprintf( esc_html__( 'Redirect already exists for %s', 'safe-redirect-manager' ), $sanitized_redirect_from ) );
186
- }
187
-
188
- // create the post
189
- $post_args = array(
190
- 'post_type' => 'redirect_rule',
191
- 'post_status' => $sanitized_post_status,
192
- 'post_author' => 1,
193
- 'menu_order' => $sanitized_menu_order,
194
- );
195
-
196
- $post_id = wp_insert_post( $post_args );
197
-
198
- if ( 0 >= $post_id ) {
199
- return new WP_Error( 'error-creating', esc_html__( 'An error occurred creating the redirect.', 'safe-redirect-manager' ) );
200
- }
201
-
202
- // update the posts meta info
203
- update_post_meta( $post_id, '_redirect_rule_from', $sanitized_redirect_from );
204
- update_post_meta( $post_id, '_redirect_rule_to', $sanitized_redirect_to );
205
- update_post_meta( $post_id, '_redirect_rule_status_code', $sanitized_status_code );
206
- update_post_meta( $post_id, '_redirect_rule_from_regex', $sanitized_enable_regex );
207
-
208
- // We need to update the cache after creating this redirect
209
- srm_flush_cache();
210
-
211
- return $post_id;
212
- }
213
-
214
-
215
-
216
- /**
217
- * Sanitize redirect to path
218
- *
219
- * The only difference between this function and just calling esc_url_raw is
220
- * esc_url_raw( 'test' ) == 'http://test', whereas sanitize_redirect_path( 'test' ) == '/test'
221
- *
222
- * @since 1.8
223
- * @param string $path
224
- * @return string
225
- */
226
- function srm_sanitize_redirect_to( $path ) {
227
- $path = trim( $path );
228
-
229
- if ( preg_match( '/^www\./i', $path ) ) {
230
- $path = 'http://' . $path;
231
- }
232
-
233
- if ( ! preg_match( '/^https?:\/\//i', $path ) ) {
234
- if ( strpos( $path, '/' ) !== 0 ) {
235
- $path = '/' . $path;
236
- }
237
- }
238
-
239
- return esc_url_raw( $path );
240
- }
241
-
242
- /**
243
- * Sanitize redirect from path
244
- *
245
- * @since 1.8
246
- * @param string $path
247
- * @param boolean $allow_regex
248
- * @return string
249
- */
250
- function srm_sanitize_redirect_from( $path, $allow_regex = false ) {
251
-
252
- $path = trim( $path );
253
-
254
- if ( empty( $path ) ) {
255
- return '';
256
- }
257
-
258
- // dont accept paths starting with a .
259
- if ( ! $allow_regex && strpos( $path, '.' ) === 0 ) {
260
- return '';
261
- }
262
-
263
- // turn path in to absolute
264
- if ( preg_match( '/https?:\/\//i', $path ) ) {
265
- $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
266
- } elseif ( ! $allow_regex && strpos( $path, '/' ) !== 0 ) {
267
- $path = '/' . $path;
268
- }
269
-
270
- // the @ symbol will break our regex engine
271
- $path = str_replace( '@', '', $path );
272
-
273
- return $path;
274
- }
275
-
276
- /**
277
- * Imports redirects from CSV file or stream.
278
- *
279
- * @since 1.8
280
- *
281
- * @access public
282
- * @param string|resource $file File path, file pointer or stream to read redirects from.
283
- * @param array $args The array of arguments. Includes column mapping and CSV settings.
284
- * @return array Returns importing statistic on success, otherwise FALSE.
285
- */
286
- function srm_import_file( $file, $args ) {
287
- $handle = $file;
288
- $close_handle = false;
289
- $doing_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
290
-
291
- // filter arguments
292
- $args = apply_filters( 'srm_import_file_args', $args );
293
-
294
- // enable line endings auto detection
295
- @ini_set( 'auto_detect_line_endings', true );
296
-
297
- // open file pointer if $file is not a resource
298
- if ( ! is_resource( $file ) ) {
299
- $handle = fopen( $file, 'rb' );
300
- if ( ! $handle ) {
301
- $doing_wp_cli && WP_CLI::error( sprintf( 'Error retrieving %s file', basename( $file ) ) );
302
- return false;
303
- }
304
-
305
- $close_handle = true;
306
- }
307
-
308
- // process all rows of the file
309
- $created = $skipped = 0;
310
- $headers = fgetcsv( $handle );
311
-
312
- while ( ( $row = fgetcsv( $handle ) ) ) {
313
- // validate
314
- $rule = array_combine( $headers, $row );
315
- if ( empty( $rule[ $args['source'] ] ) || empty( $rule[ $args['target'] ] ) ) {
316
- $doing_wp_cli && WP_CLI::warning( 'Skipping - redirection rule is formatted improperly.' );
317
- $skipped++;
318
- continue;
319
- }
320
-
321
- // sanitize
322
- $redirect_from = srm_sanitize_redirect_from( $rule[ $args['source'] ] );
323
- $redirect_to = srm_sanitize_redirect_to( $rule[ $args['target'] ] );
324
- $status_code = ! empty( $rule[ $args['code'] ] ) ? $rule[ $args['code'] ] : 302;
325
- $regex = ! empty( $rule[ $args['regex'] ] ) ? filter_var( $rule[ $args['regex'] ], FILTER_VALIDATE_BOOLEAN ) : false;
326
- $menu_order = ! empty( $rule[ $args['order'] ] ) ? $rule[ $args['order'] ] : 0;
327
-
328
- // import
329
- $id = srm_create_redirect( $redirect_from, $redirect_to, $status_code, $regex, 'publish', $menu_order );
330
-
331
- if ( is_wp_error( $id ) ) {
332
- $doing_wp_cli && WP_CLI::warning( $id );
333
- $skipped++;
334
- } else {
335
- $doing_wp_cli && WP_CLI::line( "Success - Created redirect from '{$redirect_from}' to '{$redirect_to}'" );
336
- $created++;
337
- }
338
- }
339
-
340
- // close an open file pointer if we've opened it
341
- if ( $close_handle ) {
342
- fclose( $handle );
343
- }
344
-
345
- // return result statistic
346
- return array(
347
- 'created' => $created,
348
- 'skipped' => $skipped,
349
- );
350
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
inc/{classes/class-srm-wp-cli.php → wp-cli.php} RENAMED
@@ -1,9 +1,11 @@
1
  <?php
2
  /**
3
- * Setup SRM WP CLI commands
4
  */
5
 
6
- class SRM_WP_CLI extends WP_CLI_Command {
 
 
7
 
8
 
9
  /**
@@ -12,36 +14,34 @@ class SRM_WP_CLI extends WP_CLI_Command {
12
  * @subcommand list
13
  */
14
  public function _list() {
 
 
15
  $fields = array(
16
- 'ID',
17
- 'redirect_from',
18
- 'redirect_to',
19
- 'status_code',
20
- 'enable_regex',
21
- 'post_status',
22
- );
23
 
24
  $table = new \cli\Table();
25
  $table->setHeaders( $fields );
26
 
27
- $redirects = srm_get_redirects( array( 'post_status' => 'any' ), true );
28
-
29
- foreach ( $redirects as $redirect ) {
30
  $line = array();
31
- foreach ( $fields as $field ) {
32
- if ( 'enable_regex' === $field ) {
33
- $line[] = ( $redirect[ $field ] ) ? 'true' : 'false';
34
- } else {
35
- $line[] = $redirect[ $field ];
36
- }
37
  }
38
-
39
  $table->addRow( $line );
40
  }
41
-
42
  $table->display();
43
 
44
- WP_CLI::line( 'Total of ' . count( $redirects ) . ' redirects' );
45
  }
46
 
47
  /**
@@ -51,37 +51,34 @@ class SRM_WP_CLI extends WP_CLI_Command {
51
  * @synopsis <from> <to> [<status-code>] [<enable-regex>] [<post-status>]
52
  */
53
  public function create( $args ) {
 
 
54
  $defaults = array(
55
- '',
56
- '',
57
- 302,
58
- false,
59
- 'publish',
60
- );
61
  // array_merge() doesn't work here because our keys are numeric
62
- foreach ( $defaults as $key => $value ) {
63
- if ( ! isset( $args[ $key ] ) ) {
64
- $args[ $key ] = $defaults[ $key ];
65
- }
66
  }
67
  list( $from, $to, $status_code, $enable_regex, $post_status ) = $args;
68
 
69
  // User might've passed as string.
70
- if ( empty( $enable_regex ) || 'false' === $enable_regex ) {
71
  $enable_regex = false;
72
- }
73
-
74
- if ( empty( $from ) || empty( $to ) ) {
75
- WP_CLI::error( '<from> and <to> are required arguments.' );
76
- }
77
 
78
- $ret = srm_create_redirect( $from, $to, $status_code, $enable_regex, $post_status );
 
79
 
80
- if ( is_wp_error( $ret ) ) {
 
81
  WP_CLI::error( $ret->get_error_message() );
82
- } else {
83
  WP_CLI::success( "Created redirect as #{$ret}" );
84
- }
85
  }
86
 
87
  /**
@@ -91,17 +88,16 @@ class SRM_WP_CLI extends WP_CLI_Command {
91
  * @synopsis <id>
92
  */
93
  public function delete( $args ) {
94
- $id = ( ! empty( $args[0] ) ) ? (int) $args[0] : 0;
 
 
95
 
96
  $redirect = get_post( $id );
97
- if ( ! $redirect || 'redirect_rule' !== $redirect->post_type ) {
98
  WP_CLI::error( "{$id} isn't a valid redirect." );
99
- }
100
 
101
  wp_delete_post( $id );
102
-
103
- srm_flush_cache();
104
-
105
  WP_CLI::success( "Redirect #{$id} has been deleted." );
106
  }
107
 
@@ -111,9 +107,10 @@ class SRM_WP_CLI extends WP_CLI_Command {
111
  * @subcommand update-cache
112
  */
113
  public function update_cache() {
114
- srm_flush_cache();
115
 
116
- WP_CLI::success( 'Redirect cache has been updated.' );
 
117
  }
118
 
119
  /**
@@ -123,22 +120,22 @@ class SRM_WP_CLI extends WP_CLI_Command {
123
  * @synopsis <file>
124
  */
125
  public function import_htaccess( $args, $assoc_args ) {
 
 
126
  list( $file ) = $args;
127
 
128
  $contents = file_get_contents( $file );
129
- if ( ! $contents ) {
130
- WP_CLI::error( 'Error retrieving .htaccess file' );
131
- }
132
 
133
- $pieces = explode( PHP_EOL, $contents );
134
  $created = 0;
135
  $skipped = 0;
136
- foreach ( $pieces as $piece ) {
137
 
138
  // Ignore if this line isn't a redirect
139
- if ( ! preg_match( '/^Redirect( permanent)?/i', $piece ) ) {
140
  continue;
141
- }
142
 
143
  // Parse the redirect
144
  $redirect = preg_replace( '/\s{2,}/', ' ', $piece );
@@ -146,14 +143,14 @@ class SRM_WP_CLI extends WP_CLI_Command {
146
  $redirect = explode( ' ', $redirect );
147
 
148
  // if there are three parts to the redirect, we assume the first part is a status code
149
- if ( 2 === count( $redirect ) ) {
150
- $from = $redirect[0];
151
- $to = $redirect[1];
152
  $http_status = 301;
153
- } elseif ( 3 === count( $redirect ) ) {
154
  $http_status = $redirect[0];
155
- $from = $redirect[1];
156
- $to = $redirect[2];
157
  } else {
158
  continue;
159
  }
@@ -164,12 +161,12 @@ class SRM_WP_CLI extends WP_CLI_Command {
164
  continue;
165
  }
166
 
167
- $sanitized_redirect_from = srm_sanitize_redirect_from( $from );
168
- $sanitized_redirect_to = srm_sanitize_redirect_to( $to );
169
 
170
- $id = srm_create_redirect( $sanitized_redirect_from, $sanitized_redirect_to, $http_status );
171
  if ( is_wp_error( $id ) ) {
172
- WP_CLI::warning( 'Error - ' . $id->get_error_message() );
173
  $skipped++;
174
  } else {
175
  WP_CLI::line( "Success - Created redirect from '{$sanitized_redirect_from}' to '{$sanitized_redirect_to}'" );
@@ -185,16 +182,16 @@ class SRM_WP_CLI extends WP_CLI_Command {
185
  * ## OPTIONS
186
  *
187
  * <file>
188
- * : Path to one or more valid CSV file for import. This file should contain
189
  * redirection from and to URLs, regex flag and HTTP redirection code. Here
190
  * is example table:
191
- *
192
- * | source | target | regex | code | order |
193
- * |----------------------------|--------------------|-------|------|-------|
194
- * | /legacy-url | /new-url | 0 | 301 | 0 |
195
- * | /category-1 | /new-category-slug | 0 | 302 | 1 |
196
- * | /tes?t/[0-9]+/path/[^/]+/? | /go/here | 1 | 302 | 3 |
197
- * | ... | ... | ... | ... | ... |
198
  *
199
  * You can also use exported redirects from "Redirection" plugin, which you
200
  * can download here: /wp-admin/tools.php?page=redirection.php&sub=modules
@@ -211,44 +208,41 @@ class SRM_WP_CLI extends WP_CLI_Command {
211
  * <code-column>
212
  * : Header title for code column mapping.
213
  *
214
- * <order-column>
215
- * : Header title for order column mapping.
216
- *
217
- *
218
  * ## EXAMPLE
219
  *
220
  * wp safe-redirect-manager import redirections.csv
221
  *
222
- * @synopsis <file> [--source=<source-column>] [--target=<target-column>] [--regex=<regex-column>] [--code=<code-column>] [--order=<order-column>]
223
  *
224
  * @since 1.7.6
225
- *
226
  * @access public
 
227
  * @param array $args The array of input files.
228
  * @param array $assoc_args The array of column mappings.
229
  */
230
  public function import( $args, $assoc_args ) {
231
- $mapping = wp_parse_args(
232
- $assoc_args, array(
233
- 'source' => 'source',
234
- 'target' => 'target',
235
- 'regex' => 'regex',
236
- 'code' => 'code',
237
- )
238
- );
239
 
240
- $created = $skipped = 0;
 
 
 
 
 
241
 
 
242
  foreach ( $args as $file ) {
243
- $processed = srm_import_file( $file, $mapping );
244
  if ( ! empty( $processed ) ) {
245
  $created += $processed['created'];
246
  $skipped += $processed['skipped'];
247
-
248
  WP_CLI::success( basename( $file ) . ' file processed successfully.' );
249
  }
250
  }
251
 
252
  WP_CLI::success( "All done! {$created} redirects were imported, {$skipped} were skipped" );
253
  }
254
- }
 
1
  <?php
2
  /**
3
+ * wp-cli integration
4
  */
5
 
6
+ WP_CLI::add_command( 'safe-redirect-manager', 'Safe_Redirect_Manager_CLI' );
7
+
8
+ class Safe_Redirect_Manager_CLI extends WP_CLI_Command {
9
 
10
 
11
  /**
14
  * @subcommand list
15
  */
16
  public function _list() {
17
+ global $safe_redirect_manager;
18
+
19
  $fields = array(
20
+ 'ID',
21
+ 'redirect_from',
22
+ 'redirect_to',
23
+ 'status_code',
24
+ 'enable_regex',
25
+ 'post_status',
26
+ );
27
 
28
  $table = new \cli\Table();
29
  $table->setHeaders( $fields );
30
 
31
+ $redirects = $safe_redirect_manager->get_redirects( array( 'post_status' => 'any' ) );
32
+ foreach( $redirects as $redirect ) {
 
33
  $line = array();
34
+ foreach( $fields as $field ) {
35
+ if ( 'enable_regex' == $field )
36
+ $line[] = ( $redirect[$field] ) ? 'true' : 'false';
37
+ else
38
+ $line[] = $redirect[$field];
 
39
  }
 
40
  $table->addRow( $line );
41
  }
 
42
  $table->display();
43
 
44
+ WP_CLI::line( "Total of " . count( $redirects ) . " redirects" );
45
  }
46
 
47
  /**
51
  * @synopsis <from> <to> [<status-code>] [<enable-regex>] [<post-status>]
52
  */
53
  public function create( $args ) {
54
+ global $safe_redirect_manager;
55
+
56
  $defaults = array(
57
+ '',
58
+ '',
59
+ 302,
60
+ false,
61
+ 'publish',
62
+ );
63
  // array_merge() doesn't work here because our keys are numeric
64
+ foreach( $defaults as $key => $value ) {
65
+ if ( ! isset( $args[$key] ) )
66
+ $args[$key] = $defaults[$key];
 
67
  }
68
  list( $from, $to, $status_code, $enable_regex, $post_status ) = $args;
69
 
70
  // User might've passed as string.
71
+ if ( 'false' == $enable_regex )
72
  $enable_regex = false;
 
 
 
 
 
73
 
74
+ if ( empty( $from ) || empty( $to ) )
75
+ WP_CLI::error( "<from> and <to> are required arguments." );
76
 
77
+ $ret = $safe_redirect_manager->create_redirect( $from, $to, $status_code, $enable_regex, $post_status );
78
+ if ( is_wp_error( $ret ) )
79
  WP_CLI::error( $ret->get_error_message() );
80
+ else
81
  WP_CLI::success( "Created redirect as #{$ret}" );
 
82
  }
83
 
84
  /**
88
  * @synopsis <id>
89
  */
90
  public function delete( $args ) {
91
+ global $safe_redirect_manager;
92
+
93
+ $id = ( ! empty( $args[0] ) ) ? (int)$args[0] : 0;
94
 
95
  $redirect = get_post( $id );
96
+ if ( ! $redirect || $safe_redirect_manager->redirect_post_type != $redirect->post_type )
97
  WP_CLI::error( "{$id} isn't a valid redirect." );
 
98
 
99
  wp_delete_post( $id );
100
+ $safe_redirect_manager->update_redirect_cache();
 
 
101
  WP_CLI::success( "Redirect #{$id} has been deleted." );
102
  }
103
 
107
  * @subcommand update-cache
108
  */
109
  public function update_cache() {
110
+ global $safe_redirect_manager;
111
 
112
+ $safe_redirect_manager->update_redirect_cache();
113
+ WP_CLI::success( "Redirect cache has been updated." );
114
  }
115
 
116
  /**
120
  * @synopsis <file>
121
  */
122
  public function import_htaccess( $args, $assoc_args ) {
123
+ global $safe_redirect_manager;
124
+
125
  list( $file ) = $args;
126
 
127
  $contents = file_get_contents( $file );
128
+ if ( ! $contents )
129
+ WP_CLI::error( "Error retrieving .htaccess file" );
 
130
 
131
+ $pieces = explode( PHP_EOL, $contents );
132
  $created = 0;
133
  $skipped = 0;
134
+ foreach( $pieces as $piece ) {
135
 
136
  // Ignore if this line isn't a redirect
137
+ if ( ! preg_match( '/^Redirect( permanent)?/i', $piece ) )
138
  continue;
 
139
 
140
  // Parse the redirect
141
  $redirect = preg_replace( '/\s{2,}/', ' ', $piece );
143
  $redirect = explode( ' ', $redirect );
144
 
145
  // if there are three parts to the redirect, we assume the first part is a status code
146
+ if ( 2 == count( $redirect ) ) {
147
+ $from = $redirect[0];
148
+ $to = $redirect[1];
149
  $http_status = 301;
150
+ } elseif ( 3 == count( $redirect ) ) {
151
  $http_status = $redirect[0];
152
+ $from = $redirect[1];
153
+ $to = $redirect[2];
154
  } else {
155
  continue;
156
  }
161
  continue;
162
  }
163
 
164
+ $sanitized_redirect_from = $safe_redirect_manager->sanitize_redirect_from( $from );
165
+ $sanitized_redirect_to = $safe_redirect_manager->sanitize_redirect_to( $to );
166
 
167
+ $id = $safe_redirect_manager->create_redirect( $sanitized_redirect_from, $sanitized_redirect_to, $http_status );
168
  if ( is_wp_error( $id ) ) {
169
+ WP_CLI::warning( "Error - " . $id->get_error_message() );
170
  $skipped++;
171
  } else {
172
  WP_CLI::line( "Success - Created redirect from '{$sanitized_redirect_from}' to '{$sanitized_redirect_to}'" );
182
  * ## OPTIONS
183
  *
184
  * <file>
185
+ * : Path to one or more valid CSV file for import. This file should contain
186
  * redirection from and to URLs, regex flag and HTTP redirection code. Here
187
  * is example table:
188
+ *
189
+ * | source | target | regex | code |
190
+ * |----------------------------|--------------------|-------|------|
191
+ * | /legacy-url | /new-url | 0 | 301 |
192
+ * | /category-1 | /new-category-slug | 0 | 302 |
193
+ * | /tes?t/[0-9]+/path/[^/]+/? | /go/here | 1 | 302 |
194
+ * | ... | ... | ... | ... |
195
  *
196
  * You can also use exported redirects from "Redirection" plugin, which you
197
  * can download here: /wp-admin/tools.php?page=redirection.php&sub=modules
208
  * <code-column>
209
  * : Header title for code column mapping.
210
  *
 
 
 
 
211
  * ## EXAMPLE
212
  *
213
  * wp safe-redirect-manager import redirections.csv
214
  *
215
+ * @synopsis <file> [--source=<source-column>] [--target=<target-column>] [--regex=<regex-column>] [--code=<code-column>]
216
  *
217
  * @since 1.7.6
218
+ *
219
  * @access public
220
+ * @global SRM_Safe_Redirect_Manager $safe_redirect_manager The plugin instance.
221
  * @param array $args The array of input files.
222
  * @param array $assoc_args The array of column mappings.
223
  */
224
  public function import( $args, $assoc_args ) {
225
+ global $safe_redirect_manager;
 
 
 
 
 
 
 
226
 
227
+ $mapping = wp_parse_args( $assoc_args, array(
228
+ 'source' => 'source',
229
+ 'target' => 'target',
230
+ 'regex' => 'regex',
231
+ 'code' => 'code',
232
+ ) );
233
 
234
+ $created = $skipped = 0;
235
  foreach ( $args as $file ) {
236
+ $processed = $safe_redirect_manager->import_file( $file, $mapping );
237
  if ( ! empty( $processed ) ) {
238
  $created += $processed['created'];
239
  $skipped += $processed['skipped'];
240
+
241
  WP_CLI::success( basename( $file ) . ' file processed successfully.' );
242
  }
243
  }
244
 
245
  WP_CLI::success( "All done! {$created} redirects were imported, {$skipped} were skipped" );
246
  }
247
+
248
+ }
lang/safe-redirect-manager.pot DELETED
@@ -1,233 +0,0 @@
1
- # Copyright (C) 2018 10up
2
- # This file is distributed under the GPLv2 or later.
3
- msgid ""
4
- msgstr ""
5
- "Project-Id-Version: Safe Redirect Manager 1.9\n"
6
- "Report-Msgid-Bugs-To: "
7
- "https://wordpress.org/support/plugin/safe-redirect-manager\n"
8
- "POT-Creation-Date: 2018-04-04 03:26:38+00:00\n"
9
- "MIME-Version: 1.0\n"
10
- "Content-Type: text/plain; charset=utf-8\n"
11
- "Content-Transfer-Encoding: 8bit\n"
12
- "PO-Revision-Date: 2018-MO-DA HO:MI+ZONE\n"
13
- "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
- "Language-Team: LANGUAGE <LL@li.org>\n"
15
- "X-Generator: node-wp-i18n 1.0.5\n"
16
-
17
- #: inc/classes/class-srm-post-type.php:19
18
- msgid "Moved Permanently"
19
- msgstr ""
20
-
21
- #: inc/classes/class-srm-post-type.php:20
22
- msgid "Found"
23
- msgstr ""
24
-
25
- #: inc/classes/class-srm-post-type.php:21
26
- msgid "See Other"
27
- msgstr ""
28
-
29
- #: inc/classes/class-srm-post-type.php:22
30
- msgid "Temporary Redirect"
31
- msgstr ""
32
-
33
- #: inc/classes/class-srm-post-type.php:23
34
- msgid "Forbidden"
35
- msgstr ""
36
-
37
- #: inc/classes/class-srm-post-type.php:24
38
- msgid "Not Found"
39
- msgstr ""
40
-
41
- #: inc/classes/class-srm-post-type.php:258
42
- msgid ""
43
- "Safe Redirect Manager Warning: Possible redirect loops and/or chains have "
44
- "been created."
45
- msgstr ""
46
-
47
- #: inc/classes/class-srm-post-type.php:270
48
- msgid ""
49
- "Safe Redirect Manager Error: You have reached the maximum allowable number "
50
- "of redirects"
51
- msgstr ""
52
-
53
- #: inc/classes/class-srm-post-type.php:321
54
- #: inc/classes/class-srm-post-type.php:324
55
- msgid "Redirect rule updated."
56
- msgstr ""
57
-
58
- #: inc/classes/class-srm-post-type.php:322
59
- msgid "Custom field updated."
60
- msgstr ""
61
-
62
- #: inc/classes/class-srm-post-type.php:323
63
- msgid "Custom field deleted."
64
- msgstr ""
65
-
66
- #: inc/classes/class-srm-post-type.php:326
67
- #. translators: %s: date and time of the revision
68
- msgid "Redirect rule restored to revision from %s"
69
- msgstr ""
70
-
71
- #: inc/classes/class-srm-post-type.php:327
72
- msgid "Redirect rule published."
73
- msgstr ""
74
-
75
- #: inc/classes/class-srm-post-type.php:328
76
- msgid "Redirect rule saved."
77
- msgstr ""
78
-
79
- #: inc/classes/class-srm-post-type.php:329
80
- msgid "Redirect rule submitted."
81
- msgstr ""
82
-
83
- #: inc/classes/class-srm-post-type.php:331
84
- msgid "Redirect rule scheduled for: %1$s."
85
- msgstr ""
86
-
87
- #: inc/classes/class-srm-post-type.php:333
88
- #. translators: Publish box date format, see http:php.net/date
89
- msgid "M j, Y @ G:i"
90
- msgstr ""
91
-
92
- #: inc/classes/class-srm-post-type.php:335
93
- msgid "Redirect rule draft updated."
94
- msgstr ""
95
-
96
- #: inc/classes/class-srm-post-type.php:389
97
- msgid "Redirect To"
98
- msgstr ""
99
-
100
- #: inc/classes/class-srm-post-type.php:390
101
- msgid "HTTP Status Code"
102
- msgstr ""
103
-
104
- #: inc/classes/class-srm-post-type.php:391
105
- msgid "Order"
106
- msgstr ""
107
-
108
- #: inc/classes/class-srm-post-type.php:394
109
- msgid "Redirect From"
110
- msgstr ""
111
-
112
- #: inc/classes/class-srm-post-type.php:398
113
- msgid "Date"
114
- msgstr ""
115
-
116
- #. Plugin Name of the plugin/theme
117
- msgid "Safe Redirect Manager"
118
- msgstr ""
119
-
120
- #: inc/classes/class-srm-post-type.php:485
121
- msgid "Edit Redirect Rule"
122
- msgstr ""
123
-
124
- #: inc/classes/class-srm-post-type.php:486
125
- msgid "New Redirect Rule"
126
- msgstr ""
127
-
128
- #: inc/classes/class-srm-post-type.php:488
129
- msgid "View Redirect Rule"
130
- msgstr ""
131
-
132
- #: inc/classes/class-srm-post-type.php:489
133
- msgid "Search Redirects"
134
- msgstr ""
135
-
136
- #: inc/classes/class-srm-post-type.php:490
137
- msgid "No redirect rules found."
138
- msgstr ""
139
-
140
- #: inc/classes/class-srm-post-type.php:491
141
- msgid "No redirect rules found in trash."
142
- msgstr ""
143
-
144
- #: inc/classes/class-srm-post-type.php:550
145
- msgid "Redirect Settings"
146
- msgstr ""
147
-
148
- #: inc/classes/class-srm-post-type.php:575
149
- msgid "* Redirect From:"
150
- msgstr ""
151
-
152
- #: inc/classes/class-srm-post-type.php:578
153
- msgid "Enable Regular Expressions (advanced)"
154
- msgstr ""
155
-
156
- #: inc/classes/class-srm-post-type.php:580
157
- msgid ""
158
- "This path should be relative to the root of this WordPress installation (or "
159
- "the sub-site, if you are running a multi-site). Appending a (*) wildcard "
160
- "character will match all requests with the base. Warning: Enabling regular "
161
- "expressions will disable wildcards and completely change the way the * "
162
- "symbol is interpretted."
163
- msgstr ""
164
-
165
- #: inc/classes/class-srm-post-type.php:583
166
- msgid "* Redirect To:"
167
- msgstr ""
168
-
169
- #: inc/classes/class-srm-post-type.php:586
170
- msgid ""
171
- "This can be a URL or a path relative to the root of your website (not your "
172
- "WordPress installation). Ending with a (*) wildcard character will append "
173
- "the request match to the redirect."
174
- msgstr ""
175
-
176
- #: inc/classes/class-srm-post-type.php:589
177
- msgid "* HTTP Status Code:"
178
- msgstr ""
179
-
180
- #: inc/classes/class-srm-post-type.php:595
181
- msgid "If you don't know what this is, leave it as is."
182
- msgstr ""
183
-
184
- #: inc/classes/class-srm-post-type.php:599
185
- msgid "Notes:"
186
- msgstr ""
187
-
188
- #: inc/classes/class-srm-post-type.php:601
189
- msgid "Optionally leave notes on this redirect e.g. why was it created."
190
- msgstr ""
191
-
192
- #: inc/functions.php:176
193
- msgid "Redirect from and/or redirect to arguments are invalid."
194
- msgstr ""
195
-
196
- #: inc/functions.php:180
197
- msgid "Invalid status code."
198
- msgstr ""
199
-
200
- #: inc/functions.php:185
201
- msgid "Redirect already exists for %s"
202
- msgstr ""
203
-
204
- #: inc/functions.php:199
205
- msgid "An error occurred creating the redirect."
206
- msgstr ""
207
-
208
- #. Author URI of the plugin/theme
209
- msgid "https://10up.com"
210
- msgstr ""
211
-
212
- #. Description of the plugin/theme
213
- msgid "Easily and safely manage HTTP redirects."
214
- msgstr ""
215
-
216
- #. Author of the plugin/theme
217
- msgid "10up"
218
- msgstr ""
219
-
220
- #: inc/classes/class-srm-post-type.php:481
221
- msgctxt "post type general name"
222
- msgid "Safe Redirect Manager"
223
- msgstr ""
224
-
225
- #: inc/classes/class-srm-post-type.php:482
226
- msgctxt "post type singular name"
227
- msgid "Redirect"
228
- msgstr ""
229
-
230
- #: inc/classes/class-srm-post-type.php:483
231
- msgctxt "redirect rule"
232
- msgid "Create Redirect Rule"
233
- msgstr ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
languages/safe-redirect-manager-fr_FR.mo ADDED
Binary file
languages/safe-redirect-manager-fr_FR.po ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2012 Safe Redirect Manager
2
+ # This file is distributed under the same license as the Safe Redirect Manager package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Safe Redirect Manager 1.4\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/safe-redirect-manager\n"
7
+ "POT-Creation-Date: 2012-10-09 23:02:23+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2014-01-22 14:16+0100\n"
12
+ "Last-Translator: Jean-Christophe Brebion <pro@jcbrebion.com>\n"
13
+ "Language-Team: Jean-Christophe Brebion <pro@jcbrebion.com>\n"
14
+ "X-Generator: Poedit 1.6.3\n"
15
+ "Plural-Forms: nplurals=2; plural=(n > 1);\n"
16
+ "Language: fr_FR\n"
17
+ "X-Poedit-SourceCharset: UTF-8\n"
18
+
19
+ #: safe-redirect-manager.php:259
20
+ msgid ""
21
+ "Safe Redirect Manager Warning: Possible redirect loops and/or chains have "
22
+ "been created."
23
+ msgstr ""
24
+ "Avertissement de Safe Redirect Manager: il est possible que vous ayez créé "
25
+ "des boucles et/ou chaînes de redirections."
26
+
27
+ #: safe-redirect-manager.php:266
28
+ msgid ""
29
+ "Safe Redirect Manager Error: You have reached the maximum allowable number "
30
+ "of redirects"
31
+ msgstr ""
32
+ "Erreur de Safe Redirect Manager: Vous avez atteint le nombre maximal "
33
+ "autorisé de redirections."
34
+
35
+ #: safe-redirect-manager.php:367 safe-redirect-manager.php:370
36
+ msgid "Redirect rule updated."
37
+ msgstr "Règle de redirection mise à jour."
38
+
39
+ #: safe-redirect-manager.php:368
40
+ msgid "Custom field updated."
41
+ msgstr "Champ personnalisé mis à jour."
42
+
43
+ #: safe-redirect-manager.php:369
44
+ msgid "Custom field deleted."
45
+ msgstr "Champ personnalisé supprimé."
46
+
47
+ #. translators: %s: date and time of the revision
48
+ #: safe-redirect-manager.php:372
49
+ msgid "Redirect rule restored to revision from %s"
50
+ msgstr "Règle de redirection restaurée de la révision à partir de %s"
51
+
52
+ #: safe-redirect-manager.php:373
53
+ msgid "Redirect rule published."
54
+ msgstr "Règle de redirection publiée."
55
+
56
+ #: safe-redirect-manager.php:374
57
+ msgid "Redirect rule saved."
58
+ msgstr "Règle de redirection enregistrée."
59
+
60
+ #: safe-redirect-manager.php:375
61
+ msgid "Redirect rule submitted."
62
+ msgstr "Règle de redirection envoyée."
63
+
64
+ #: safe-redirect-manager.php:376
65
+ msgid "Redirect rule scheduled for: <strong>%1$s</strong>."
66
+ msgstr "Règle de redirection planifiée pour: <strong>%1$s</strong>."
67
+
68
+ #. translators: Publish box date format, see http:php.net/date
69
+ #: safe-redirect-manager.php:378
70
+ msgid "M j, Y @ G:i"
71
+ msgstr "j M Y @ G:i"
72
+
73
+ #: safe-redirect-manager.php:379
74
+ msgid "Redirect rule draft updated."
75
+ msgstr "Brouillon de règle de redirection mis à jour."
76
+
77
+ #: safe-redirect-manager.php:431
78
+ msgid "Redirect To"
79
+ msgstr "Redirection vers"
80
+
81
+ #: safe-redirect-manager.php:432
82
+ msgid "HTTP Status Code"
83
+ msgstr "Code de statut HTTP"
84
+
85
+ #: safe-redirect-manager.php:435
86
+ msgid "Redirect From"
87
+ msgstr "Redirection à partir de"
88
+
89
+ #: safe-redirect-manager.php:439
90
+ msgid "Date"
91
+ msgstr "Date"
92
+
93
+ #: safe-redirect-manager.php:491
94
+ msgctxt "post type general name"
95
+ msgid "Safe Redirect Manager"
96
+ msgstr "Safe Redirect Manager"
97
+
98
+ #: safe-redirect-manager.php:492
99
+ msgctxt "post type singular name"
100
+ msgid "Redirect"
101
+ msgstr "Redirection"
102
+
103
+ #. Plugin Name of the plugin/theme
104
+ #: safe-redirect-manager.php:494 safe-redirect-manager.php:497
105
+ #: safe-redirect-manager.php:503
106
+ msgid "Safe Redirect Manager"
107
+ msgstr "Redirections"
108
+
109
+ #: safe-redirect-manager.php:495
110
+ msgid "Edit Redirect Rule"
111
+ msgstr "Modifier la règle de redirection"
112
+
113
+ #: safe-redirect-manager.php:496
114
+ msgid "New Redirect Rule"
115
+ msgstr "Nouvelle règle de redirection"
116
+
117
+ #: safe-redirect-manager.php:498
118
+ msgid "View Redirect Rule"
119
+ msgstr "Voir la règle de redirection"
120
+
121
+ #: safe-redirect-manager.php:499
122
+ msgid "Search Redirects"
123
+ msgstr "Rechercher une redirection"
124
+
125
+ #: safe-redirect-manager.php:500
126
+ msgid "No redirect rules found."
127
+ msgstr "Aucune règle de redirection trouvée."
128
+
129
+ #: safe-redirect-manager.php:501
130
+ msgid "No redirect rules found in trash."
131
+ msgstr "Aucune règle de redirection trouvée dans la corbeille."
132
+
133
+ #: safe-redirect-manager.php:544
134
+ msgid "Redirect Settings"
135
+ msgstr "Paramètres de redirection"
136
+
137
+ #: safe-redirect-manager.php:565
138
+ msgid "Redirect From:"
139
+ msgstr "Redirection à partir de:"
140
+
141
+ #: safe-redirect-manager.php:567
142
+ msgid ""
143
+ "This path should be relative to the root of this WordPress installation (or "
144
+ "the sub-site, if you are running a multi-site). Appending a (*) wildcard "
145
+ "character will match all requests with the base."
146
+ msgstr ""
147
+ "Le chemin doit être relatif à la racine de votre installation WordPress (ou "
148
+ "du sous-site, si vous utilisez une installation multi-site). Ajouter le "
149
+ "caractère de remplacement (*) permet d'accepter toutes les requêtes à partir "
150
+ "de la base."
151
+
152
+ #: safe-redirect-manager.php:571
153
+ msgid "Redirect To:"
154
+ msgstr "Redirection vers:"
155
+
156
+ #: safe-redirect-manager.php:573
157
+ msgid ""
158
+ "This can be a URL or a path relative to the root of your website (not your "
159
+ "WordPress installation). Ending with a (*) wildcard character will append "
160
+ "the request match to the redirect."
161
+ msgstr ""
162
+ "Ceci peut être une URL ou un chemin relatif à la racine du site web (et non "
163
+ "pas votre installation WordPress). Terminer avec le caractère de "
164
+ "remplacement (*) vous permettra d'ajouter la requête à la redirection."
165
+
166
+ #: safe-redirect-manager.php:577
167
+ msgid "HTTP Status Code:"
168
+ msgstr "Code de statut HTTP:"
169
+
170
+ #: safe-redirect-manager.php:583
171
+ msgid "If you don't know what this is, leave it as is."
172
+ msgstr "Si vous ne savez pas ce que c'est, ne changez pas la valeur."
173
+
174
+ #. Plugin URI of the plugin/theme
175
+ #. Author URI of the plugin/theme
176
+ msgid "http://www.10up.com"
177
+ msgstr "http://www.10up.com"
178
+
179
+ #. Description of the plugin/theme
180
+ msgid "Easily and safely manage HTTP redirects."
181
+ msgstr "Gérez simplement et sans risque les redirections HTTP."
182
+
183
+ #. Author of the plugin/theme
184
+ msgid "Taylor Lovett (10up LLC), VentureBeat"
185
+ msgstr "Taylor Lovett (10up LLC), VentureBeat"
{lang → languages}/safe-redirect-manager-sk_SK.mo RENAMED
File without changes
{lang → languages}/safe-redirect-manager-sk_SK.po RENAMED
File without changes
languages/safe-redirect-manager.pot ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (C) 2012 Safe Redirect Manager
2
+ # This file is distributed under the same license as the Safe Redirect Manager package.
3
+ msgid ""
4
+ msgstr ""
5
+ "Project-Id-Version: Safe Redirect Manager 1.4-working\n"
6
+ "Report-Msgid-Bugs-To: http://wordpress.org/tag/safe-redirect-manager\n"
7
+ "POT-Creation-Date: 2012-10-09 23:02:23+00:00\n"
8
+ "MIME-Version: 1.0\n"
9
+ "Content-Type: text/plain; charset=UTF-8\n"
10
+ "Content-Transfer-Encoding: 8bit\n"
11
+ "PO-Revision-Date: 2012-MO-DA HO:MI+ZONE\n"
12
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13
+ "Language-Team: LANGUAGE <LL@li.org>\n"
14
+
15
+ #: safe-redirect-manager.php:259
16
+ msgid ""
17
+ "Safe Redirect Manager Warning: Possible redirect loops and/or chains have "
18
+ "been created."
19
+ msgstr ""
20
+
21
+ #: safe-redirect-manager.php:266
22
+ msgid ""
23
+ "Safe Redirect Manager Error: You have reached the maximum allowable number "
24
+ "of redirects"
25
+ msgstr ""
26
+
27
+ #: safe-redirect-manager.php:367 safe-redirect-manager.php:370
28
+ msgid "Redirect rule updated."
29
+ msgstr ""
30
+
31
+ #: safe-redirect-manager.php:368
32
+ msgid "Custom field updated."
33
+ msgstr ""
34
+
35
+ #: safe-redirect-manager.php:369
36
+ msgid "Custom field deleted."
37
+ msgstr ""
38
+
39
+ #. translators: %s: date and time of the revision
40
+ #: safe-redirect-manager.php:372
41
+ msgid "Redirect rule restored to revision from %s"
42
+ msgstr ""
43
+
44
+ #: safe-redirect-manager.php:373
45
+ msgid "Redirect rule published."
46
+ msgstr ""
47
+
48
+ #: safe-redirect-manager.php:374
49
+ msgid "Redirect rule saved."
50
+ msgstr ""
51
+
52
+ #: safe-redirect-manager.php:375
53
+ msgid "Redirect rule submitted."
54
+ msgstr ""
55
+
56
+ #: safe-redirect-manager.php:376
57
+ msgid "Redirect rule scheduled for: <strong>%1$s</strong>."
58
+ msgstr ""
59
+
60
+ #. translators: Publish box date format, see http:php.net/date
61
+ #: safe-redirect-manager.php:378
62
+ msgid "M j, Y @ G:i"
63
+ msgstr ""
64
+
65
+ #: safe-redirect-manager.php:379
66
+ msgid "Redirect rule draft updated."
67
+ msgstr ""
68
+
69
+ #: safe-redirect-manager.php:431
70
+ msgid "Redirect To"
71
+ msgstr ""
72
+
73
+ #: safe-redirect-manager.php:432
74
+ msgid "HTTP Status Code"
75
+ msgstr ""
76
+
77
+ #: safe-redirect-manager.php:435
78
+ msgid "Redirect From"
79
+ msgstr ""
80
+
81
+ #: safe-redirect-manager.php:439
82
+ msgid "Date"
83
+ msgstr ""
84
+
85
+ #: safe-redirect-manager.php:491
86
+ msgctxt "post type general name"
87
+ msgid "Safe Redirect Manager"
88
+ msgstr ""
89
+
90
+ #: safe-redirect-manager.php:492
91
+ msgctxt "post type singular name"
92
+ msgid "Redirect"
93
+ msgstr ""
94
+
95
+ #. #-#-#-#-# plugin.pot (Safe Redirect Manager 1.4-working) #-#-#-#-#
96
+ #. Plugin Name of the plugin/theme
97
+ #: safe-redirect-manager.php:494 safe-redirect-manager.php:497
98
+ #: safe-redirect-manager.php:503
99
+ msgid "Safe Redirect Manager"
100
+ msgstr ""
101
+
102
+ #: safe-redirect-manager.php:495
103
+ msgid "Edit Redirect Rule"
104
+ msgstr ""
105
+
106
+ #: safe-redirect-manager.php:496
107
+ msgid "New Redirect Rule"
108
+ msgstr ""
109
+
110
+ #: safe-redirect-manager.php:498
111
+ msgid "View Redirect Rule"
112
+ msgstr ""
113
+
114
+ #: safe-redirect-manager.php:499
115
+ msgid "Search Redirects"
116
+ msgstr ""
117
+
118
+ #: safe-redirect-manager.php:500
119
+ msgid "No redirect rules found."
120
+ msgstr ""
121
+
122
+ #: safe-redirect-manager.php:501
123
+ msgid "No redirect rules found in trash."
124
+ msgstr ""
125
+
126
+ #: safe-redirect-manager.php:544
127
+ msgid "Redirect Settings"
128
+ msgstr ""
129
+
130
+ #: safe-redirect-manager.php:565
131
+ msgid "Redirect From:"
132
+ msgstr ""
133
+
134
+ #: safe-redirect-manager.php:567
135
+ msgid ""
136
+ "This path should be relative to the root of this WordPress installation (or "
137
+ "the sub-site, if you are running a multi-site). Appending a (*) wildcard "
138
+ "character will match all requests with the base."
139
+ msgstr ""
140
+
141
+ #: safe-redirect-manager.php:571
142
+ msgid "Redirect To:"
143
+ msgstr ""
144
+
145
+ #: safe-redirect-manager.php:573
146
+ msgid ""
147
+ "This can be a URL or a path relative to the root of your website (not your "
148
+ "WordPress installation). Ending with a (*) wildcard character will append "
149
+ "the request match to the redirect."
150
+ msgstr ""
151
+
152
+ #: safe-redirect-manager.php:577
153
+ msgid "HTTP Status Code:"
154
+ msgstr ""
155
+
156
+ #: safe-redirect-manager.php:583
157
+ msgid "If you don't know what this is, leave it as is."
158
+ msgstr ""
159
+
160
+ #. #-#-#-#-# plugin.pot (Safe Redirect Manager 1.4-working) #-#-#-#-#
161
+ #. Plugin URI of the plugin/theme
162
+ #. #-#-#-#-# plugin.pot (Safe Redirect Manager 1.4-working) #-#-#-#-#
163
+ #. Author URI of the plugin/theme
164
+ msgid "http://www.10up.com"
165
+ msgstr ""
166
+
167
+ #. Description of the plugin/theme
168
+ msgid "Easily and safely manage HTTP redirects."
169
+ msgstr ""
170
+
171
+ #. Author of the plugin/theme
172
+ msgid "Taylor Lovett (10up LLC), VentureBeat"
173
+ msgstr ""
readme.txt CHANGED
@@ -1,37 +1,31 @@
1
  === Safe Redirect Manager ===
2
  Contributors: tlovett1, tollmanz, taylorde, 10up, jakemgold, danielbachhuber, VentureBeat
3
- Tags: http redirects, redirect manager, url redirection, safe http redirection, multisite redirects, redirects
4
  Requires at least: 3.1
5
- Tested up to: 5.0
6
  Stable tag: trunk
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
10
  == Description ==
11
 
12
- Safe Redirect Manager is a HTTP redirect manager for WordPress. An easy-to-use UI allows you to redirect locations to new URL's with the HTTP status codes of your choosing. This plugin works great with multisite.
13
 
14
- [Fork the plugin on GitHub.](https://github.com/10up/safe-redirect-manager)
15
 
16
  == Installation ==
17
 
18
  Extract the zip file and just drop the contents in the wp-content/plugins/ directory of your WordPress installation and then activate the Plugin from Plugins page.
19
 
 
 
 
 
 
20
  == Changelog ==
21
 
22
- = 1.9 =
23
- * Add redirect notes feature.
24
- * Fix PHP 7.2 errors
25
- * Instantiate classes in main file instead of individual files for improved testability.
26
- * Add filters for request path and redirect path
27
- * Add filter to only apply redirects on 404
28
-
29
- = 1.8 =
30
- * Improved escaping
31
- * Custom redirect capability
32
- * Code refactor
33
- * Fix root redirect in sub directory bug
34
- * Fix broken html
35
 
36
  = 1.7.8 (Dec. 16, 2015) =
37
  * Fix SQL injection bug and no search terms warning
1
  === Safe Redirect Manager ===
2
  Contributors: tlovett1, tollmanz, taylorde, 10up, jakemgold, danielbachhuber, VentureBeat
3
+ Tags: http redirects, redirect manager, url redirection, safe http redirection, multisite redirects
4
  Requires at least: 3.1
5
+ Tested up to: 4.5
6
  Stable tag: trunk
7
 
8
  Safely and easily manage your website's HTTP redirects.
9
 
10
  == Description ==
11
 
12
+ Safe Redirect Manager is a HTTP redirect manager for WordPress. An easy-to-use UI allows you to redirect locations to new URL's with the HTTP status codes of your choosing. The plugin uses the wp_safe_redirect function which only allows redirects to whitelisted hosts for security purposes. The plugin automatically handles whitelisting hosts for you. This plugin works great with Multisite.
13
 
14
+ [Fork the plugin on GitHub.](https://github.com/tlovett1/Safe-Redirect-Manager)
15
 
16
  == Installation ==
17
 
18
  Extract the zip file and just drop the contents in the wp-content/plugins/ directory of your WordPress installation and then activate the Plugin from Plugins page.
19
 
20
+ == Screenshots ==
21
+
22
+ 1. This view shows all your redirects. You can filter them by date or even search through them.
23
+ 2. This is the edit redirect page. Specify a "from" path, "to" path/URL, and a status code. You can schedule redirects for later dates just like posts.
24
+
25
  == Changelog ==
26
 
27
+ = 1.7.9 (Nov. 25, 2018) =
28
+ * Fix SQL injection bug in search functionality
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  = 1.7.8 (Dec. 16, 2015) =
31
  * Fix SQL injection bug and no search terms warning
safe-redirect-manager.php CHANGED
@@ -1,36 +1,1056 @@
1
  <?php
2
- /**
3
- * Plugin Name: Safe Redirect Manager
4
- * Plugin URI: https://10up.com
5
- * Description: Easily and safely manage HTTP redirects.
6
- * Author: 10up
7
- * Version: 1.9
8
- * Text Domain: safe-redirect-manager
9
- * Domain Path: /lang/
10
- * Author URI: https://10up.com
11
- * License: GPLv2 or later
12
- * License URI: http://www.gnu.org/licenses/gpl-2.0.html
13
- */
14
-
15
- /**
16
- * Localize plugin
17
- *
18
- * @since 1.8
19
- */
20
- function srm_load_textdomain() {
21
- load_plugin_textdomain( 'safe-redirect-manager', false, dirname( __FILE__ ) . '/lang' );
22
- }
23
- add_action( 'plugins_loaded', 'srm_load_textdomain' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- require_once( dirname( __FILE__ ) . '/inc/functions.php' );
26
- require_once( dirname( __FILE__ ) . '/inc/classes/class-srm-post-type.php' );
27
- require_once( dirname( __FILE__ ) . '/inc/classes/class-srm-redirect.php' );
 
28
 
 
 
 
 
 
 
29
 
30
- if ( defined( 'WP_CLI' ) && WP_CLI ) {
31
- require_once( dirname( __FILE__ ) . '/inc/classes/class-srm-wp-cli.php' );
32
- WP_CLI::add_command( 'safe-redirect-manager', 'SRM_WP_CLI' );
33
  }
34
 
35
- SRM_Post_Type::factory();
36
- SRM_Redirect::factory();
1
  <?php
2
+ /*
3
+ 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)
7
+ Version: 1.7.9
8
+ Author URI: http://www.10up.com
9
+
10
+ GNU General Public License, Free Software Foundation <http://creativecommons.org/licenses/GPL/2.0/>
11
+
12
+ This program is free software; you can redistribute it and/or modify
13
+ it under the terms of the GNU General Public License as published by
14
+ the Free Software Foundation; either version 2 of the License, or
15
+ (at your option) any later version.
16
+
17
+ This program is distributed in the hope that it will be useful,
18
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
19
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20
+ GNU General Public License for more details.
21
+
22
+ You should have received a copy of the GNU General Public License
23
+ along with this program; if not, write to the Free Software
24
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25
+
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_search_term;
34
+
35
+ public $redirect_post_type = 'redirect_rule';
36
+ private $redirect_nonce_name = 'srm_redirect_nonce';
37
+ private $redirect_nonce_action = 'srm-save-redirect-meta';
38
+
39
+ public $meta_key_redirect_from = '_redirect_rule_from';
40
+ public $meta_key_redirect_to = '_redirect_rule_to';
41
+ public $meta_key_redirect_status_code = '_redirect_rule_status_code';
42
+ public $meta_key_enable_redirect_from_regex = '_redirect_rule_from_regex';
43
+
44
+ public $cache_key_redirects = '_srm_redirects';
45
+
46
+ public $valid_status_codes = array( 301, 302, 303, 307, 403, 404 );
47
+
48
+ public $status_code_labels = array(
49
+ 301 => 'Moved Permanently',
50
+ 302 => 'Found',
51
+ 303 => 'See Other',
52
+ 307 => 'Temporary Redirect',
53
+ 403 => 'Forbidden',
54
+ 404 => 'Not Found',
55
+ );
56
+
57
+ private $whitelist_hosts = array();
58
+
59
+ public $default_max_redirects = 150;
60
+
61
+ /**
62
+ * Sets up redirect manager
63
+ *
64
+ * @since 1.0
65
+ * @uses add_action, add_filter
66
+ * @return object
67
+ */
68
+ public function __construct() {
69
+ add_action( 'init', array( $this, 'action_init_load_textdomain' ), 9 );
70
+ add_action( 'init', array( $this, 'action_init' ) );
71
+ add_action( 'init', array( $this, 'action_register_post_types' ) );
72
+ add_action( 'parse_request', array( $this, 'action_parse_request' ), 0 );
73
+ add_action( 'save_post', array( $this, 'action_save_post' ) );
74
+ add_filter( 'manage_' . $this->redirect_post_type . '_posts_columns' , array( $this, 'filter_redirect_columns' ) );
75
+ add_action( 'manage_' . $this->redirect_post_type . '_posts_custom_column' , array( $this, 'action_custom_redirect_columns' ), 10, 2 );
76
+ add_action( 'transition_post_status', array( $this, 'action_transition_post_status' ), 10, 3 );
77
+ add_filter( 'post_updated_messages', array( $this, 'filter_redirect_updated_messages' ) );
78
+ add_action( 'admin_notices', array( $this, 'action_redirect_chain_alert' ) );
79
+ add_filter( 'the_title', array( $this, 'filter_admin_title' ), 100, 2 );
80
+ add_filter( 'bulk_actions-edit-' . $this->redirect_post_type, array( $this, 'filter_bulk_actions' ) );
81
+ add_action( 'admin_print_styles-edit.php', array( $this, 'action_print_logo_css' ), 10, 1 );
82
+ add_action( 'admin_print_styles-post.php', array( $this, 'action_print_logo_css' ), 10, 1 );
83
+ add_action( 'admin_print_styles-post-new.php', array( $this, 'action_print_logo_css' ), 10, 1 );
84
+ add_filter( 'post_type_link', array( $this, 'filter_post_type_link' ), 10, 2 );
85
+ add_action( 'admin_init', array( $this, 'init_search_filters' ) );
86
+ }
87
+
88
+ /**
89
+ * Setup search filters
90
+ */
91
+ public function init_search_filters() {
92
+ $redirect_capability = $this->get_redirect_capability();
93
+
94
+ if ( ! is_admin() ) {
95
+ return;
96
+ }
97
+
98
+ if ( ! current_user_can( $redirect_capability ) ) {
99
+ return;
100
+ }
101
+
102
+ add_action( 'pre_get_posts', array( $this, 'disable_core_search' ) );
103
+ add_filter( 'posts_clauses', array( $this, 'filter_search_clauses' ), 10, 2 );
104
+ }
105
+
106
+ /**
107
+ * We don't need core's fancy search functionality since we provide our own.
108
+ *
109
+ * @param \WP_Query $query WP Query object
110
+ */
111
+ public function disable_core_search( $query ) {
112
+ if ( $query->is_search() && 'redirect_rule' === $query->get( 'post_type' ) ) {
113
+ // Store a reference to the search term for later use.
114
+ $this->redirect_search_term = $query->get( 's' );
115
+ // Don't let core build it's search clauses since we override them.
116
+ $query->set( 's', '' );
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Build custom JOIN + WHERE clauses to do a more direct search through meta.
122
+ *
123
+ * @param array $clauses Array of SQL clauses
124
+ * @param WP_Query $query WP_Query object
125
+ * @return array
126
+ */
127
+ public function filter_search_clauses( $clauses, $query ) {
128
+ global $wpdb;
129
+
130
+ if ( $this->redirect_search_term ) {
131
+ $search_term = $this->redirect_search_term;
132
+ $search_term_like = '%' . $wpdb->esc_like( $search_term ) . '%';
133
+ $query->set( 's', $this->redirect_search_term );
134
+ unset( $this->redirect_search_term );
135
+ $clauses['distinct'] = 'DISTINCT';
136
+ $clauses['join'] .= " LEFT JOIN $wpdb->postmeta AS pm ON ($wpdb->posts.ID = pm.post_id) ";
137
+ $clauses['where'] = $wpdb->prepare(
138
+ "AND (
139
+ (
140
+ pm.meta_value LIKE %s
141
+ AND pm.meta_key = '_redirect_rule_from'
142
+ ) OR (
143
+ pm.meta_value LIKE %s
144
+ AND pm.meta_key = '_redirect_rule_to'
145
+ )
146
+ )
147
+ AND $wpdb->posts.post_type = 'redirect_rule'
148
+ AND $wpdb->posts.post_status IN ( 'publish', 'future', 'draft', 'pending' )
149
+ ",
150
+ $search_term_like,
151
+ $search_term_like
152
+ );
153
+ }
154
+
155
+ return $clauses;
156
+ }
157
+
158
+
159
+ /**
160
+ * Localize plugin
161
+ *
162
+ * @since 1.7
163
+ * @uses load_plugin_textdomain
164
+ * @return void
165
+ */
166
+ public function action_init_load_textdomain() {
167
+ load_plugin_textdomain( 'safe-redirect-manager', false, basename( dirname( __FILE__ ) ) . '/languages' );
168
+ }
169
+
170
+ /**
171
+ * Swap tools logo for plugin logo
172
+ *
173
+ * @since 1.1
174
+ * @uses plugins_url
175
+ * @return void
176
+ */
177
+ public function action_print_logo_css() {
178
+ if ( $this->is_plugin_page() ) {
179
+ ?>
180
+ <style type="text/css">
181
+ #visibility, .view-switch, .posts .inline-edit-col-left .inline-edit-group {
182
+ display: none;
183
+ }
184
+ #srm<?php echo $this->meta_key_redirect_from; ?> {
185
+ width: 60%;
186
+ }
187
+ </style>
188
+ <?php
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Limit the bulk actions available in the Manage Redirects view
194
+ *
195
+ * @since 1.0
196
+ * @return array
197
+ */
198
+ public function filter_bulk_actions( $actions ) {
199
+
200
+ // No bulk editing at this time
201
+ unset( $actions['edit'] );
202
+
203
+ return $actions;
204
+ }
205
+
206
+ /**
207
+ * Creates a redirect post, this function will be useful for import/exports scripts
208
+ *
209
+ * @param string $redirect_from
210
+ * @param string $redirect_to
211
+ * @param int $status_code
212
+ * @param bool $enable_regex
213
+ * @param string $post_status
214
+ * @since 1.3
215
+ * @uses wp_insert_post, update_post_meta
216
+ * @return int|WP_Error
217
+ */
218
+ public function create_redirect( $redirect_from, $redirect_to, $status_code = 302, $enable_regex = false, $post_status = 'publish' ) {
219
+ global $wpdb;
220
+
221
+ $sanitized_redirect_from = $this->sanitize_redirect_from( $redirect_from );
222
+ $sanitized_redirect_to = $this->sanitize_redirect_to( $redirect_to );
223
+ $sanitized_status_code = absint( $status_code );
224
+ $sanitized_enable_regex = (bool)$enable_regex;
225
+ $sanitized_post_status = sanitize_key( $post_status );
226
+
227
+ // check and make sure no parameters are empty or invalid after sanitation
228
+ if ( empty( $sanitized_redirect_from ) || empty( $sanitized_redirect_to ) )
229
+ return new WP_Error( 'invalid-argument', __( 'Redirect from and/or redirect to arguments are invalid.', 'safe-redirect-manager' ) );
230
+
231
+ if ( ! in_array( $sanitized_status_code, $this->valid_status_codes ) )
232
+ return new WP_Error( 'invalid-argument', __( 'Invalid status code.', 'safe-redirect-manager' ) );
233
+
234
+ // Check to ensure this redirect doesn't already exist
235
+ 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 ) ) )
236
+ return new WP_Error( 'duplicate-redirect', sprintf( __( 'Redirect already exists for %s', 'safe-redirect-manager' ), $sanitized_redirect_from ) );
237
+
238
+ // create the post
239
+ $post_args = array(
240
+ 'post_type' => $this->redirect_post_type,
241
+ 'post_status' => $sanitized_post_status,
242
+ 'post_author' => 1
243
+ );
244
+
245
+ $post_id = wp_insert_post( $post_args );
246
+
247
+ if ( 0 >= $post_id )
248
+ return new WP_Error( 'error-creating', __( 'An error occurred creating the redirect.', 'safe-redirect-manager' ) );
249
+
250
+ // update the posts meta info
251
+ update_post_meta( $post_id, $this->meta_key_redirect_from, $sanitized_redirect_from );
252
+ update_post_meta( $post_id, $this->meta_key_redirect_to, $sanitized_redirect_to );
253
+ update_post_meta( $post_id, $this->meta_key_redirect_status_code, $sanitized_status_code );
254
+ update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $sanitized_enable_regex );
255
+
256
+ // We need to update the cache after creating this redirect
257
+ $this->update_redirect_cache();
258
+
259
+ return $post_id;
260
+ }
261
+
262
+ /**
263
+ * Whether or not this is an admin page specific to the plugin
264
+ *
265
+ * @since 1.1
266
+ * @uses get_post_type
267
+ * @return bool
268
+ */
269
+ private function is_plugin_page() {
270
+ return (bool) ( get_post_type() == $this->redirect_post_type || ( isset( $_GET['post_type'] ) && $this->redirect_post_type == $_GET['post_type'] ) );
271
+ }
272
+
273
+ /**
274
+ * Echoes admin message if redirect chains exist
275
+ *
276
+ * @since 1.0
277
+ * @uses apply_filters
278
+ * @return void
279
+ */
280
+ public function action_redirect_chain_alert() {
281
+ global $hook_suffix;
282
+ if ( $this->is_plugin_page() ) {
283
+
284
+ /**
285
+ * check_for_possible_redirect_loops() runs in best case Theta(n^2) so if you have 100 redirects, this method
286
+ * will be running slow. Let's disable it by default.
287
+ */
288
+ if ( apply_filters( 'srm_check_for_possible_redirect_loops', false ) ) {
289
+ if ( $this->check_for_possible_redirect_loops() ) {
290
+ ?>
291
+ <div class="updated">
292
+ <p><?php _e( 'Safe Redirect Manager Warning: Possible redirect loops and/or chains have been created.', 'safe-redirect-manager' ); ?></p>
293
+ </div>
294
+ <?php
295
+ }
296
+ } if ( $this->max_redirects_reached() ) {
297
+ ?>
298
+ <?php if ( 'post-new.php' == $hook_suffix ) : ?><style type="text/css">#post { display: none; }</style><?php endif; ?>
299
+ <div class="error">
300
+ <p><?php _e( 'Safe Redirect Manager Error: You have reached the maximum allowable number of redirects', 'safe-redirect-manager' ); ?></p>
301
+ </div>
302
+ <?php
303
+ }
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Returns true if max redirects have been reached
309
+ *
310
+ * @since 1.0
311
+ * @uses apply_filters, get_transient
312
+ * @return bool
313
+ */
314
+ public function max_redirects_reached() {
315
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
316
+ $redirects = $this->update_redirect_cache();
317
+ }
318
+
319
+ return ( count( $redirects ) >= $this->default_max_redirects );
320
+ }
321
+
322
+ /**
323
+ * Check for potential redirect loops or chains
324
+ *
325
+ * @since 1.0
326
+ * @uses home_url, get_transient
327
+ * @return boolean
328
+ */
329
+ public function check_for_possible_redirect_loops() {
330
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
331
+ $redirects = $this->update_redirect_cache();
332
+ }
333
+
334
+ $current_url = parse_url( home_url() );
335
+ $this_host = ( is_array( $current_url ) && ! empty( $current_url['host'] ) ) ? $current_url['host'] : '';
336
+
337
+ foreach ( $redirects as $redirect ) {
338
+ $redirect_from = $redirect['redirect_from'];
339
+
340
+ // check redirect from against all redirect to's
341
+ foreach ( $redirects as $compare_redirect ) {
342
+ $redirect_to = $compare_redirect['redirect_to'];
343
+
344
+ $redirect_url = parse_url( $redirect_to );
345
+ $redirect_host = ( is_array( $redirect_url ) && ! empty( $redirect_url['host'] ) ) ? $redirect_url['host'] : '';
346
+
347
+ // check if we are redirecting locally
348
+ if ( empty( $redirect_host ) || $redirect_host == $this_host ) {
349
+ $redirect_from_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', home_url() . $redirect_from );
350
+ $redirect_to_url = $redirect_to;
351
+ if ( ! preg_match( '/https?:\/\//i', $redirect_to_url ) )
352
+ $redirect_to_url = $this_host . $redirect_to_url;
353
+ else
354
+ $redirect_to_url = preg_replace( '/(http:\/\/|https:\/\/|www\.)/i', '', $redirect_to_url );
355
+
356
+ // possible loop/chain found
357
+ if ( $redirect_to_url == $redirect_from_url )
358
+ return true;
359
+ }
360
+ }
361
+ }
362
+
363
+ return false;
364
+ }
365
+
366
+ /**
367
+ * Filters title out for redirect from in post manager
368
+ *
369
+ * @since 1.0
370
+ * @param string $title
371
+ * @param int $post_id
372
+ * @uses is_admin, get_post_meta
373
+ * @return string
374
+ */
375
+ public function filter_admin_title( $title, $post_id = 0 ) {
376
+ if ( ! is_admin() )
377
+ return $title;
378
+
379
+ $redirect = get_post( $post_id );
380
+ if ( empty( $redirect ) )
381
+ return $title;
382
+
383
+ if ( $redirect->post_type != $this->redirect_post_type )
384
+ return $title;
385
+
386
+ $redirect_from = get_post_meta( $post_id, $this->meta_key_redirect_from, true );
387
+ if ( ! empty( $redirect_from ) )
388
+ return $redirect_from;
389
+
390
+ return $title;
391
+ }
392
+
393
+ /**
394
+ * Customizes updated messages for redirects
395
+ *
396
+ * @since 1.0
397
+ * @param array $messages
398
+ * @uses esc_url, get_permalink, add_query_var, wp_post_revision_title
399
+ * @return array
400
+ */
401
+ public function filter_redirect_updated_messages( $messages ) {
402
+ global $post, $post_ID;
403
+
404
+ $messages[$this->redirect_post_type] = array(
405
+ 0 => '', // Unused. Messages start at index 1.
406
+ 1 => sprintf( __( 'Redirect rule updated.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
407
+ 2 => __( 'Custom field updated.', 'safe-redirect-manager' ),
408
+ 3 => __( 'Custom field deleted.', 'safe-redirect-manager' ),
409
+ 4 => __( 'Redirect rule updated.', 'safe-redirect-manager' ),
410
+ /* translators: %s: date and time of the revision */
411
+ 5 => isset( $_GET['revision'] ) ? sprintf( __('Redirect rule restored to revision from %s', 'safe-redirect-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,
412
+ 6 => sprintf( __( 'Redirect rule published.', 'safe-redirect-manager' ), esc_url( get_permalink( $post_ID ) ) ),
413
+ 7 => __( 'Redirect rule saved.', 'safe-redirect-manager' ),
414
+ 8 => sprintf( __( 'Redirect rule submitted.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
415
+ 9 => sprintf( __( 'Redirect rule scheduled for: <strong>%1$s</strong>.', 'safe-redirect-manager' ),
416
+ // translators: Publish box date format, see http://php.net/date
417
+ date_i18n( __( 'M j, Y @ G:i' ), strtotime( $post->post_date ) ), esc_url( get_permalink($post_ID) ) ),
418
+ 10 => sprintf( __( 'Redirect rule draft updated.', 'safe-redirect-manager' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),
419
+ );
420
+
421
+ return $messages;
422
+ }
423
+
424
+ /**
425
+ * Clear redirect cache if appropriate post type is transitioned
426
+ *
427
+ * @since 1.0
428
+ * @param string $new_status
429
+ * @param string $old_status
430
+ * @param object $post
431
+ * @uses delete_transient
432
+ * @return void
433
+ */
434
+ public function action_transition_post_status( $new_status, $old_status, $post ) {
435
+ if ( ! is_object( $post ) )
436
+ return;
437
+
438
+ // recreate redirect cache
439
+ if ( $this->redirect_post_type == $post->post_type ) {
440
+ delete_transient( $this->cache_key_redirects );
441
+ $this->update_redirect_cache();
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Displays custom columns on redirect manager screen
447
+ *
448
+ * @since 1.0
449
+ * @param string $column
450
+ * @param int $post_id
451
+ * @uses get_post_meta, esc_html, admin_url
452
+ * @return void
453
+ */
454
+ public function action_custom_redirect_columns( $column, $post_id ) {
455
+ if ( 'srm' . $this->meta_key_redirect_to == $column ) {
456
+ echo esc_html( get_post_meta( $post_id, $this->meta_key_redirect_to, true ) );
457
+ } elseif ( 'srm' . $this->meta_key_redirect_status_code == $column ) {
458
+ echo absint( get_post_meta( $post_id, $this->meta_key_redirect_status_code, true ) );
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Add new columns to manage redirect screen
464
+ *
465
+ * @since 1.0
466
+ * @param array $columns
467
+ * @return array
468
+ */
469
+ public function filter_redirect_columns( $columns ) {
470
+ $columns['srm' . $this->meta_key_redirect_to] = __( 'Redirect To', 'safe-redirect-manager' );
471
+ $columns['srm'. $this->meta_key_redirect_status_code] = __( 'HTTP Status Code', 'safe-redirect-manager' );
472
+
473
+ // Change the title column
474
+ $columns['title'] = __( 'Redirect From', 'safe-redirect-manager' );
475
+
476
+ // Move date column to the back
477
+ unset( $columns['date'] );
478
+ $columns['date'] = __( 'Date', 'safe-redirect-manager' );
479
+
480
+ return $columns;
481
+ }
482
+
483
+ /**
484
+ * Saves meta info for redirect rules
485
+ *
486
+ * @since 1.0
487
+ * @param int $post_id
488
+ * @uses current_user_can, get_post_type, wp_verify_nonce, update_post_meta, delete_post_meta
489
+ * @return void
490
+ */
491
+ public function action_save_post( $post_id ) {
492
+ if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ! current_user_can( 'edit_post', $post_id ) || 'revision' == get_post_type( $post_id ) )
493
+ return;
494
+
495
+ // Update post meta for redirect rules
496
+ if ( ! empty( $_POST[$this->redirect_nonce_name] ) && wp_verify_nonce( $_POST[$this->redirect_nonce_name], $this->redirect_nonce_action ) ) {
497
+
498
+ if ( ! empty( $_POST['srm' . $this->meta_key_enable_redirect_from_regex] ) ) {
499
+ $allow_regex = (bool) $_POST['srm' . $this->meta_key_enable_redirect_from_regex];
500
+ update_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex, $allow_regex );
501
+ } else {
502
+ $allow_regex = false;
503
+ delete_post_meta( $post_id, $this->meta_key_enable_redirect_from_regex );
504
+ }
505
+
506
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_from] ) ) {
507
+ update_post_meta( $post_id, $this->meta_key_redirect_from, $this->sanitize_redirect_from( $_POST['srm' . $this->meta_key_redirect_from], $allow_regex ) );
508
+ } else {
509
+ delete_post_meta( $post_id, $this->meta_key_redirect_from );
510
+ }
511
+
512
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_to] ) ) {
513
+ update_post_meta( $post_id, $this->meta_key_redirect_to, $this->sanitize_redirect_to( $_POST['srm' . $this->meta_key_redirect_to] ) );
514
+ } else {
515
+ delete_post_meta( $post_id, $this->meta_key_redirect_to );
516
+ }
517
+
518
+ if ( ! empty( $_POST['srm' . $this->meta_key_redirect_status_code] ) ) {
519
+ update_post_meta( $post_id, $this->meta_key_redirect_status_code, absint( $_POST['srm' . $this->meta_key_redirect_status_code] ) );
520
+ } else {
521
+ delete_post_meta( $post_id, $this->meta_key_redirect_status_code );
522
+ }
523
+
524
+ /**
525
+ * This fixes an important bug where the redirect cache was not up-to-date. Previously the cache was only being
526
+ * updated on transition_post_status which gets called BEFORE save post. But since save_post is where all the important
527
+ * redirect info is saved, updating the cache before it is not sufficient.
528
+ */
529
+ $this->update_redirect_cache();
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Registers post types for plugin
535
+ *
536
+ * @since 1.0
537
+ * @uses register_post_type, _x, plugins_url, apply_filters
538
+ * @return void
539
+ */
540
+ public function action_register_post_types() {
541
+ $redirect_labels = array(
542
+ 'name' => _x( 'Safe Redirect Manager', 'post type general name', 'safe-redirect-manager' ),
543
+ 'singular_name' => _x( 'Redirect', 'post type singular name', 'safe-redirect-manager' ),
544
+ 'add_new' => _x( 'Create Redirect Rule', $this->redirect_post_type, 'safe-redirect-manager' ),
545
+ 'add_new_item' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
546
+ 'edit_item' => __( 'Edit Redirect Rule', 'safe-redirect-manager' ),
547
+ 'new_item' => __( 'New Redirect Rule', 'safe-redirect-manager' ),
548
+ 'all_items' => __( 'Safe Redirect Manager', 'safe-redirect-manager' ),
549
+ 'view_item' => __( 'View Redirect Rule', 'safe-redirect-manager' ),
550
+ 'search_items' => __( 'Search Redirects', 'safe-redirect-manager' ),
551
+ 'not_found' => __( 'No redirect rules found.', 'safe-redirect-manager' ),
552
+ 'not_found_in_trash' => __( 'No redirect rules found in trash.', 'safe-redirect-manager' ),
553
+ 'parent_item_colon' => '',
554
+ 'menu_name' => __( 'Safe Redirect Manager', 'safe-redirect-manager' )
555
+ );
556
+ $redirect_capability = 'manage_options';
557
+ $redirect_capability = apply_filters( 'srm_restrict_to_capability', $redirect_capability );
558
+ $capabilities = array(
559
+ 'edit_post' => $redirect_capability,
560
+ 'read_post' => $redirect_capability,
561
+ 'delete_post' => $redirect_capability,
562
+ 'delete_posts' => $redirect_capability,
563
+ 'edit_posts' => $redirect_capability,
564
+ 'edit_others_posts' => $redirect_capability,
565
+ 'publish_posts' => $redirect_capability,
566
+ 'read_private_posts' => $redirect_capability
567
+ );
568
+
569
+ $redirect_args = array(
570
+ 'labels' => $redirect_labels,
571
+ 'public' => false,
572
+ 'publicly_queryable' => true,
573
+ 'show_ui' => true,
574
+ 'show_in_menu' => 'tools.php',
575
+ 'query_var' => false,
576
+ 'rewrite' => false,
577
+ 'capability_type' => 'post',
578
+ 'capabilities' => $capabilities,
579
+ 'has_archive' => false,
580
+ 'hierarchical' => false,
581
+ 'register_meta_box_cb' => array( $this, 'action_redirect_rule_metabox' ),
582
+ 'menu_position' => 80,
583
+ 'supports' => array( '' )
584
+ );
585
+ register_post_type( $this->redirect_post_type, $redirect_args );
586
+ }
587
+
588
+ /**
589
+ * Registers meta boxes for redirect rule post type
590
+ *
591
+ * @since 1.0
592
+ * @uses add_meta_box
593
+ * @return void
594
+ */
595
+ public function action_redirect_rule_metabox() {
596
+ add_meta_box( 'redirect_settings', __( 'Redirect Settings', 'safe-redirect-manager' ), array( $this, 'redirect_rule_metabox' ), $this->redirect_post_type, 'normal', 'core' );
597
+ }
598
+
599
+ /**
600
+ * Echoes HTML for redirect rule meta box
601
+ *
602
+ * @since 1.0
603
+ * @param object $post
604
+ * @uses wp_nonce_field, get_post_meta, esc_attr, selected
605
+ * @return void
606
+ */
607
+ public function redirect_rule_metabox( $post ) {
608
+ wp_nonce_field( $this->redirect_nonce_action, $this->redirect_nonce_name );
609
+
610
+ $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
611
+ $redirect_to = get_post_meta( $post->ID, $this->meta_key_redirect_to, true );
612
+ $status_code = get_post_meta( $post->ID, $this->meta_key_redirect_status_code, true );
613
+ $enable_regex = get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true );
614
+ if ( empty( $status_code ) )
615
+ $status_code = apply_filters( 'srm_default_direct_status', 302 );
616
+ ?>
617
+ <p>
618
+ <label for="srm<?php echo $this->meta_key_redirect_from; ?>"><?php _e( 'Redirect From:', 'safe-redirect-manager' ); ?></label><br />
619
+ <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 ); ?>" />
620
+ <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" />
621
+ <label for="srm<?php echo $this->meta_key_enable_redirect_from_regex; ?>"><?php _e( 'Enable Regular Expressions (advanced)', 'safe-redirect-manager' ); ?></label><br />
622
+ <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>
623
+ </p>
624
+
625
+ <p>
626
+ <label for="srm<?php echo $this->meta_key_redirect_to; ?>"><?php _e( 'Redirect To:', 'safe-redirect-manager' ); ?></label><br />
627
+ <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 />
628
+ <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>
629
+ </p>
630
+
631
+ <p>
632
+ <label for="srm<?php echo $this->meta_key_redirect_status_code; ?>"><?php _e( 'HTTP Status Code:', 'safe-redirect-manager' ); ?></label>
633
+ <select name="srm<?php echo $this->meta_key_redirect_status_code; ?>" id="srm<?php echo $this->meta_key_redirect_status_code; ?>">
634
+ <?php foreach ( $this->valid_status_codes as $code ) : ?>
635
+ <option value="<?php echo esc_attr( $code ); ?>" <?php selected( $status_code, $code ); ?>><?php echo esc_html( $code . ' ' . $this->status_code_labels[$code] ); ?></option>
636
+ <?php endforeach; ?>
637
+ </select>
638
+ <em><?php _e( "If you don't know what this is, leave it as is.", 'safe-redirect-manager' ); ?></em>
639
+ </p>
640
+ <?php
641
+ }
642
+
643
+ /**
644
+ * Localize plugin
645
+ *
646
+ * @since 1.0
647
+ * @uses load_plugin_textdomain, plugin_basename
648
+ * @return void
649
+ */
650
+ public function action_init() {
651
+ load_plugin_textdomain( 'safe-redirect-manager', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
652
+
653
+ $this->default_max_redirects = apply_filters( 'srm_max_redirects', $this->default_max_redirects );
654
+ }
655
+
656
+ /**
657
+ * Apply whitelisted hosts to allowed_redirect_hosts filter
658
+ *
659
+ * @since 1.0
660
+ * @param array $content
661
+ * @return array
662
+ */
663
+ public function filter_allowed_redirect_hosts( $content ) {
664
+
665
+ foreach ( $this->whitelist_hosts as $host ) {
666
+ $without_www = preg_replace( '/^www\./i', '', $host );
667
+ $with_www = 'www.' . $without_www;
668
+
669
+ if ( ! in_array( $without_www, $content ) ) $content[] = $without_www;
670
+ if ( ! in_array( $with_www, $content ) ) $content[] = $with_www;
671
+ }
672
+
673
+ return $content;
674
+ }
675
+
676
+ /**
677
+ * Get required capability for managing redirects
678
+ *
679
+ * @return string
680
+ */
681
+ protected function get_redirect_capability() {
682
+ $redirect_capability = 'srm_manage_redirects';
683
+ $roles = array( 'administrator' );
684
+ foreach ( $roles as $role ) {
685
+ $role = get_role( $role );
686
+ if ( empty( $role ) || $role->has_cap( $redirect_capability ) ) {
687
+ continue;
688
+ }
689
+ $role->add_cap( $redirect_capability );
690
+ }
691
+ return apply_filters( 'srm_restrict_to_capability', $redirect_capability );
692
+ }
693
+
694
+ /**
695
+ * Get redirects from the database
696
+ *
697
+ * @since 1.6
698
+ * @param array $args Any arguments to filter by
699
+ * @return array $redirects An array of redirects
700
+ */
701
+ public function get_redirects( $args = array() ) {
702
+
703
+ $redirects = array();
704
+
705
+ if ( $this->default_max_redirects > 50 )
706
+ $posts_per_page = 50;
707
+ else
708
+ $posts_per_page = $this->default_max_redirects;
709
+
710
+ $i = 1;
711
+ do {
712
+
713
+ $defaults = array(
714
+ 'posts_per_page' => $posts_per_page,
715
+ 'post_status' => 'publish',
716
+ 'paged' => $i,
717
+ );
718
+
719
+ $query_args = array_merge( $defaults, $args );
720
+
721
+ // Some arguments that don't need to be configurable
722
+ $query_args['post_type'] = $this->redirect_post_type;
723
+ $query_args['no_found_rows'] = false;
724
+ $query_args['update_term_cache'] = false;
725
+
726
+ $redirect_query = new WP_Query( $query_args );
727
+
728
+ foreach( $redirect_query->posts as $redirect ) {
729
+ $redirects[] = array(
730
+ 'ID' => $redirect->ID,
731
+ 'post_status' => $redirect->post_status,
732
+ 'redirect_from' => get_post_meta( $redirect->ID, $this->meta_key_redirect_from, true ),
733
+ 'redirect_to' => get_post_meta( $redirect->ID, $this->meta_key_redirect_to, true ),
734
+ 'status_code' => (int)get_post_meta( $redirect->ID, $this->meta_key_redirect_status_code, true ),
735
+ 'enable_regex' => (bool)get_post_meta( $redirect->ID, $this->meta_key_enable_redirect_from_regex, true ),
736
+ );
737
+ }
738
+
739
+ if ( count( $redirects ) == $this->default_max_redirects
740
+ || count( $redirect_query->posts ) < $posts_per_page )
741
+ $build = false;
742
+ else
743
+ $build = true;
744
+
745
+ $i++;
746
+
747
+ } while ( $build );
748
+
749
+ return $redirects;
750
+ }
751
+
752
+ /**
753
+ * Force update on the redirect cache and return cache
754
+ *
755
+ * @since 1.0
756
+ * @uses set_transient, get_post_meta, the_post, have_posts, get_the_ID
757
+ * @return array
758
+ */
759
+ public function update_redirect_cache() {
760
+
761
+ $redirect_cache = $this->get_redirects();
762
+
763
+ set_transient( $this->cache_key_redirects, $redirect_cache );
764
+
765
+ return $redirect_cache;
766
+ }
767
+
768
+ /**
769
+ * Check current url against redirects
770
+ *
771
+ * @since 1.0
772
+ * @uses esc_url_raw, wp_safe_redirect, untrailingslashit, get_transient, add_filter
773
+ * @return void
774
+ */
775
+ public function action_parse_request() {
776
+
777
+ // Don't redirect from wp-admin
778
+ if ( is_admin() )
779
+ return;
780
+
781
+ // get redirects from cache or recreate it
782
+ if ( false === ( $redirects = get_transient( $this->cache_key_redirects ) ) ) {
783
+ $redirects = $this->update_redirect_cache();
784
+ }
785
+
786
+ // If we have no redirects, there is no need to continue
787
+ if ( empty( $redirects ) )
788
+ return;
789
+
790
+ // get requested path and add a / before it
791
+ $requested_path = esc_url_raw( $_SERVER['REQUEST_URI'] );
792
+ $requested_path = stripslashes( $requested_path );
793
+
794
+ $requested_path = untrailingslashit( $requested_path );
795
+ if ( empty( $requested_path ) ){
796
+ $requested_path = '/';
797
+ }
798
+
799
+ /**
800
+ * If WordPress resides in a directory that is not the public root, we have to chop
801
+ * the pre-WP path off the requested path.
802
+ */
803
+ $parsed_home_url = parse_url( home_url() );
804
+ if ( isset( $parsed_home_url['path'] ) && '/' != $parsed_home_url['path'] ) {
805
+ $requested_path = preg_replace( '@' . $parsed_home_url['path'] . '@i', '', $requested_path, 1 );
806
+ }
807
+
808
+ // Allow redirects to be filtered
809
+ $redirects = apply_filters( 'srm_registered_redirects', $redirects, $requested_path );
810
+
811
+ // Allow for case insensitive redirects
812
+ $case_insensitive = apply_filters( 'srm_case_insensitive_redirects', true );
813
+ if ( $case_insensitive ) {
814
+ $regex_flag = 'i';
815
+ // normalized path is used for matching but not for replace
816
+ $normalized_requested_path = strtolower( $requested_path );
817
+ } else {
818
+ $regex_flag = '';
819
+ $normalized_requested_path = $requested_path;
820
+ }
821
+
822
+ foreach ( (array)$redirects as $redirect ) {
823
+
824
+ $redirect_from = untrailingslashit( $redirect['redirect_from'] );
825
+ if ( empty( $redirect_from ) )
826
+ $redirect_from = '/'; // this only happens in the case where there is a redirect on the root
827
+
828
+ $redirect_to = $redirect['redirect_to'];
829
+ $status_code = $redirect['status_code'];
830
+ $enable_regex = ( isset( $redirect['enable_regex'] ) ) ? $redirect['enable_regex'] : false;
831
+
832
+ // check if the redirection destination is valid, otherwise just skip it
833
+ if ( empty( $redirect_to ) )
834
+ continue;
835
+
836
+ // check if requested path is the same as the redirect from path
837
+ if ( $enable_regex ) {
838
+ $matched_path = preg_match( '@' . $redirect_from . '@' . $regex_flag, $requested_path );
839
+ } else {
840
+ if ( $case_insensitive ) {
841
+ $redirect_from = strtolower( $redirect_from );
842
+ }
843
+
844
+ $matched_path = ( $normalized_requested_path == $redirect_from );
845
+
846
+ // check if the redirect_from ends in a wildcard
847
+ if ( !$matched_path && (strrpos( $redirect_from, '*' ) === strlen( $redirect_from ) - 1) ) {
848
+ $wildcard_base = substr( $redirect_from, 0, strlen( $redirect_from ) - 1 );
849
+
850
+ // mark as match if requested path matches the base of the redirect from
851
+ $matched_path = (substr( $normalized_requested_path, 0, strlen( $wildcard_base ) ) == $wildcard_base);
852
+ if ( (strrpos( $redirect_to, '*' ) == strlen( $redirect_to ) - 1 ) ) {
853
+ $redirect_to = rtrim( $redirect_to, '*' ) . ltrim( substr( $requested_path, strlen( $wildcard_base ) ), '/' );
854
+ }
855
+ }
856
+ }
857
+
858
+ if ( $matched_path ) {
859
+ // whitelist redirect to host if necessary
860
+ $parsed_redirect = parse_url( $redirect_to );
861
+ if ( is_array( $parsed_redirect ) && ! empty( $parsed_redirect['host'] ) ) {
862
+ $this->whitelist_hosts[] = $parsed_redirect['host'];
863
+ add_filter( 'allowed_redirect_hosts' , array( $this, 'filter_allowed_redirect_hosts' ) );
864
+ }
865
+
866
+ // Allow for regex replacement in $redirect_to
867
+ if ( $enable_regex ) {
868
+ $redirect_to = preg_replace( '@' . $redirect_from . '@' . $regex_flag, $redirect_to, $requested_path );
869
+ }
870
+
871
+ $sanitized_redirect_to = esc_url_raw( $redirect_to );
872
+
873
+ do_action( 'srm_do_redirect', $requested_path, $sanitized_redirect_to, $status_code );
874
+
875
+ if ( defined( 'PHPUNIT_SRM_TESTSUITE' ) && PHPUNIT_SRM_TESTSUITE ) {
876
+ // Don't actually redirect if we are testing
877
+ return;
878
+ }
879
+
880
+ header( 'X-Safe-Redirect-Manager: true' );
881
+
882
+ // if we have a valid status code, then redirect with it
883
+ if ( in_array( $status_code, $this->valid_status_codes ) )
884
+ wp_safe_redirect( $sanitized_redirect_to, $status_code );
885
+ else
886
+ wp_safe_redirect( $sanitized_redirect_to );
887
+ exit;
888
+ }
889
+ }
890
+ }
891
+
892
+ /**
893
+ * Sanitize redirect to path
894
+ *
895
+ * The only difference between this function and just calling esc_url_raw is
896
+ * esc_url_raw( 'test' ) == 'http://test', whereas sanitize_redirect_path( 'test' ) == '/test'
897
+ *
898
+ * @since 1.0
899
+ * @param string $path
900
+ * @uses esc_url_raw
901
+ * @return string
902
+ */
903
+ public function sanitize_redirect_to( $path ) {
904
+ $path = trim( $path );
905
+
906
+ if ( preg_match( '/^www\./i', $path ) )
907
+ $path = 'http://' . $path;
908
+
909
+ if ( ! preg_match( '/^https?:\/\//i', $path ) )
910
+ if ( strpos( $path, '/' ) !== 0 )
911
+ $path = '/' . $path;
912
+
913
+ return esc_url_raw( $path );
914
+ }
915
+
916
+ /**
917
+ * Sanitize redirect from path
918
+ *
919
+ * @since 1.0
920
+ * @param string $path
921
+ * @param boolean $allow_regex
922
+ * @uses esc_url_raw
923
+ * @return string
924
+ */
925
+ public function sanitize_redirect_from( $path, $allow_regex = false ) {
926
+
927
+ $path = trim( $path );
928
+
929
+ if ( empty( $path ) )
930
+ return '';
931
+
932
+ // dont accept paths starting with a .
933
+ if ( ! $allow_regex && strpos( $path, '.' ) === 0 )
934
+ return '';
935
+
936
+ // turn path in to absolute
937
+ if ( preg_match( '/https?:\/\//i', $path ) )
938
+ $path = preg_replace( '/^(http:\/\/|https:\/\/)(www\.)?[^\/?]+\/?(.*)/i', '/$3', $path );
939
+ elseif ( ! $allow_regex && strpos( $path, '/' ) !== 0 )
940
+ $path = '/' . $path;
941
+
942
+ // the @ symbol will break our regex engine
943
+ $path = str_replace( '@', '', $path );
944
+
945
+ return $path;
946
+ }
947
+
948
+ /**
949
+ * Return a permalink for a redirect post, which is the "redirect from"
950
+ * URL for that redirect.
951
+ *
952
+ * @since 1.7
953
+ * @param string $permalink The permalink
954
+ * @param object $post A Post object
955
+ * @uses home_url, get_post_meta
956
+ * @return string The permalink
957
+ */
958
+ public function filter_post_type_link( $permalink, $post ) {
959
+ if ( $this->redirect_post_type != $post->post_type )
960
+ return $permalink;
961
+
962
+ // We can't do anything to provide a permalink
963
+ // for regex enabled redirects.
964
+ if ( get_post_meta( $post->ID, $this->meta_key_enable_redirect_from_regex, true ) )
965
+ return $permalink;
966
+
967
+ // We can't do anything if there is a wildcard in the redirect from
968
+ $redirect_from = get_post_meta( $post->ID, $this->meta_key_redirect_from, true );
969
+ if ( false !== strpos( $redirect_from, '*' ) )
970
+ return $permalink;
971
+
972
+ // Use default permalink if no $redirect_from exists - this prevents duplicate GUIDs
973
+ if ( empty( $redirect_from ) ) {
974
+ return $permalink;
975
+ }
976
+
977
+ return home_url( $redirect_from );
978
+ }
979
+
980
+ /**
981
+ * Imports redirects from CSV file or stream.
982
+ *
983
+ * @since 1.7.6
984
+ *
985
+ * @access public
986
+ * @param string|resource $file File path, file pointer or stream to read redirects from.
987
+ * @param array $args The array of arguments. Includes column mapping and CSV settings.
988
+ * @return array Returns importing statistic on success, otherwise FALSE.
989
+ */
990
+ public function import_file( $file, $args ) {
991
+ $handle = $file;
992
+ $close_handle = false;
993
+ $doing_wp_cli = defined( 'WP_CLI' ) && WP_CLI;
994
+
995
+ // filter arguments
996
+ $args = apply_filters( 'srm_import_file_args', $args );
997
+
998
+ // enable line endings auto detection
999
+ @ini_set( 'auto_detect_line_endings', true );
1000
+
1001
+ // open file pointer if $file is not a resource
1002
+ if ( ! is_resource( $file ) ) {
1003
+ $handle = fopen( $file, 'rb' );
1004
+ if ( ! $handle ) {
1005
+ $doing_wp_cli && WP_CLI::error( sprintf( "Error retrieving %s file", basename( $file ) ) );
1006
+ return false;
1007
+ }
1008
+
1009
+ $close_handle = true;
1010
+ }
1011
+
1012
+ // process all rows of the file
1013
+ $created = $skipped = 0;
1014
+ $headers = fgetcsv( $handle );
1015
+ while( ( $row = fgetcsv( $handle ) ) ) {
1016
+ // validate
1017
+ $rule = array_combine( $headers, $row );
1018
+ if ( empty( $rule[$args['source']] ) || empty( $rule[$args['target']] ) ) {
1019
+ $doing_wp_cli && WP_CLI::warning( "Skipping - redirection rule is formatted improperly." );
1020
+ $skipped++;
1021
+ continue;
1022
+ }
1023
+
1024
+ // sanitize
1025
+ $redirect_from = $this->sanitize_redirect_from( $rule[$args['source']] );
1026
+ $redirect_to = $this->sanitize_redirect_to( $rule[$args['target']] );
1027
+ $status_code = ! empty( $rule[$args['code']] ) ? $rule[$args['code']] : 302;
1028
+ $regex = ! empty( $rule[$args['regex']] ) ? filter_var( $rule[$args['regex']], FILTER_VALIDATE_BOOLEAN ) : false;
1029
+
1030
+ // import
1031
+ $id = $this->create_redirect( $redirect_from, $redirect_to, $status_code, $regex );
1032
+ if ( is_wp_error( $id ) ) {
1033
+ $doing_wp_cli && WP_CLI::warning( $id );
1034
+ $skipped++;
1035
+ } else {
1036
+ $doing_wp_cli && WP_CLI::line( "Success - Created redirect from '{$redirect_from}' to '{$redirect_to}'" );
1037
+ $created++;
1038
+ }
1039
+ }
1040
 
1041
+ // close an open file pointer if we've opened it
1042
+ if ( $close_handle ) {
1043
+ fclose( $handle );
1044
+ }
1045
 
1046
+ // return result statistic
1047
+ return array(
1048
+ 'created' => $created,
1049
+ 'skipped' => $skipped,
1050
+ );
1051
+ }
1052
 
 
 
 
1053
  }
1054
 
1055
+ global $safe_redirect_manager;
1056
+ $safe_redirect_manager = new SRM_Safe_Redirect_Manager();