PluginProbe
Redirection / 4.1
Redirection v4.1
5.10.0 5.9.0 5.8.1 5.8.0 3.7.2 3.7.3 4.0 4.0.1 4.1 4.1.1 4.2 4.2.1 4.2.2 4.2.3 4.3 4.3.1 4.3.2 4.3.3 4.4 4.4.1 4.4.2 4.5 4.5.1 4.6.2 4.7.1 All 130 releases
redirection / redirection-admin.php

redirection-admin.php in Redirection 4.1, at redirection-admin.php

542 lines 17.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 include_once dirname( __FILE__ ) . '/models/group.php';
4 include_once dirname( __FILE__ ) . '/models/monitor.php';
5 include_once dirname( __FILE__ ) . '/models/file-io.php';
6 include_once dirname( __FILE__ ) . '/database/database.php';
7
8 define( 'RED_DEFAULT_PER_PAGE', 25 );
9 define( 'RED_MAX_PER_PAGE', 200 );
10
11 class Redirection_Admin {
12 private static $instance = null;
13 private $monitor;
14 private $fixit_failed = false;
15
16 static function init() {
17 if ( is_null( self::$instance ) ) {
18 self::$instance = new Redirection_Admin();
19 }
20
21 return self::$instance;
22 }
23
24 function __construct() {
25 add_action( 'admin_menu', [ $this, 'admin_menu' ] );
26 add_action( 'admin_notices', [ $this, 'update_nag' ] );
27 // add_action( 'network_admin_notices', [ $this, 'update_nag' ] );
28 add_action( 'plugin_action_links_' . basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ), [ $this, 'plugin_settings' ], 10, 4 );
29 add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 4 );
30 add_filter( 'redirection_save_options', [ $this, 'flush_schedule' ] );
31 add_filter( 'set-screen-option', [ $this, 'set_per_page' ], 10, 3 );
32 add_action( 'redirection_redirect_updated', [ $this, 'set_default_group' ], 10, 2 );
33
34 if ( defined( 'REDIRECTION_FLYING_SOLO' ) && REDIRECTION_FLYING_SOLO ) {
35 add_filter( 'script_loader_src', [ $this, 'flying_solo' ], 10, 2 );
36 }
37
38 register_deactivation_hook( REDIRECTION_FILE, [ 'Redirection_Admin', 'plugin_deactivated' ] );
39 register_uninstall_hook( REDIRECTION_FILE, [ 'Redirection_Admin', 'plugin_uninstall' ] );
40
41 $this->monitor = new Red_Monitor( red_get_options() );
42 $this->run_hacks();
43 }
44
45 // These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites
46 public static function plugin_activated() {
47 Red_Database::apply_to_sites( function() {
48 Red_Flusher::clear();
49 red_set_options();
50 } );
51 }
52
53 // These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites
54 public static function plugin_deactivated() {
55 Red_Database::apply_to_sites( function() {
56 Red_Flusher::clear();
57 } );
58 }
59
60 // These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites
61 public static function plugin_uninstall() {
62 $database = Red_Database::get_latest_database();
63
64 Red_Database::apply_to_sites( function() use ( $database ) {
65 $database->remove();
66 } );
67 }
68
69 public function update_nag() {
70 $status = new Red_Database_Status();
71
72 $message = false;
73 if ( $status->needs_installing() ) {
74 /* translators: 1: URL to plugin page */
75 $message = sprintf( __( 'Please complete your <a href="%s">Redirection setup</a> to activate the plugin.' ), 'tools.php?page=' . basename( REDIRECTION_FILE ) );
76 } elseif ( $status->needs_updating() ) {
77 /* translators: 1: URL to plugin page, 2: current version, 3: target version */
78 $message = sprintf( __( 'Redirection\'s database needs to be updated - <a href="%1$1s">click to update</a>.' ), 'tools.php?page=' . basename( REDIRECTION_FILE ) );
79 }
80
81 if ( ! $message || strpos( Redirection_Request::get_request_url(), 'page=redirection.php' ) !== false ) {
82 return;
83 }
84
85 // Contains HTML
86 echo '<div class="update-nag">' . $message . '</div>';
87 }
88
89 // So it finally came to this... some plugins include their JS in all pages, whether they are needed or not. If there is an error
90 // then this can prevent Redirection running and it's a little sensitive about that. We use the nuclear option here to disable
91 // all other JS while viewing Redirection
92 public function flying_solo( $src, $handle ) {
93 if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], 'page=redirection.php' ) !== false ) {
94 if ( substr( $src, 0, 4 ) === 'http' && $handle !== 'redirection' && strpos( $src, 'plugins' ) !== false ) {
95 if ( $this->ignore_this_plugin( $src ) ) {
96 return false;
97 }
98 }
99 }
100
101 return $src;
102 }
103
104 private function ignore_this_plugin( $src ) {
105 $ignore = array(
106 'mootools',
107 'wp-seo-',
108 'authenticate',
109 'wordpress-seo',
110 'yikes',
111 );
112
113 foreach ( $ignore as $text ) {
114 if ( strpos( $src, $text ) !== false ) {
115 return true;
116 }
117 }
118
119 return false;
120 }
121
122 public function flush_schedule( $options ) {
123 Red_Flusher::schedule();
124 return $options;
125 }
126
127 function set_per_page( $status, $option, $value ) {
128 if ( $option === 'redirection_log_per_page' ) {
129 return max( 1, min( intval( $value, 10 ), RED_MAX_PER_PAGE ) );
130 }
131
132 return $status;
133 }
134
135 function plugin_settings( $links ) {
136 $status = new Red_Database_Status();
137 if ( $status->needs_updating() ) {
138 array_unshift( $links, '<a style="color: red" href="tools.php?page=' . basename( REDIRECTION_FILE ) . '&amp;sub=support">' . __( 'Upgrade Database', 'redirection' ) . '</a>' );
139 }
140
141 array_unshift( $links, '<a href="tools.php?page=' . basename( REDIRECTION_FILE ) . '&amp;sub=options">' . __( 'Settings', 'redirection' ) . '</a>' );
142 return $links;
143 }
144
145 function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) {
146 if ( $plugin_file === basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ) ) {
147 $plugin_data['Description'] .= '<p>' . __( 'Please upgrade your database', 'redirection' ) . '</p>';
148 }
149
150 return $plugin_meta;
151 }
152
153 function redirection_head() {
154 global $wp_version;
155
156 $this->check_rest_api();
157
158 if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['_wpnonce'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'wp_rest' ) ) {
159 if ( $_REQUEST['action'] === 'fixit' ) {
160 $this->run_fixit();
161 } elseif ( $_REQUEST['action'] === 'rest_api' ) {
162 $this->set_rest_api( intval( $_REQUEST['rest_api'], 10 ) );
163 }
164 }
165
166 $build = REDIRECTION_VERSION . '-' . REDIRECTION_BUILD;
167 $preload = $this->get_preload_data();
168 $options = red_get_options();
169 $versions = array(
170 'Plugin: ' . REDIRECTION_VERSION,
171 'WordPress: ' . $wp_version . ' (' . ( is_multisite() ? 'multi' : 'single' ) . ')',
172 'PHP: ' . phpversion(),
173 'Browser: ' . Redirection_Request::get_user_agent(),
174 'JavaScript: ' . plugin_dir_url( REDIRECTION_FILE ) . 'redirection.js',
175 'REST API: ' . red_get_rest_api(),
176 );
177
178 $this->inject();
179
180 if ( in_array( $this->get_menu_page(), array( 'redirects', 'log', '404s', 'groups' ) ) ) {
181 add_screen_option( 'per_page', array(
182 /* translators: maximum number of log entries */
183 'label' => sprintf( __( 'Log entries (%d max)', 'redirection' ), RED_MAX_PER_PAGE ),
184 'default' => RED_DEFAULT_PER_PAGE,
185 'option' => 'redirection_log_per_page',
186 ) );
187 }
188
189 if ( defined( 'REDIRECTION_DEV_MODE' ) && REDIRECTION_DEV_MODE ) {
190 wp_enqueue_script( 'redirection', 'http://localhost:3312/redirection.js', array(), $build, true );
191 } else {
192 wp_enqueue_script( 'redirection', plugin_dir_url( REDIRECTION_FILE ) . 'redirection.js', array(), $build, true );
193 }
194
195 wp_enqueue_style( 'redirection', plugin_dir_url( REDIRECTION_FILE ) . 'redirection.css', array(), $build );
196
197 $status = new Red_Database_Status();
198 $status->check_tables_exist();
199
200 wp_localize_script( 'redirection', 'Redirectioni10n', array(
201 'WP_API_root' => esc_url_raw( red_get_rest_api() ),
202 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
203 'pluginBaseUrl' => plugins_url( '', REDIRECTION_FILE ),
204 'pluginRoot' => admin_url( 'tools.php?page=redirection.php' ),
205 'per_page' => $this->get_per_page(),
206 'locale' => $this->get_i18n_data(),
207 'localeSlug' => get_locale(),
208 'settings' => $options,
209 'preload' => $preload,
210 'versions' => implode( "\n", $versions ),
211 'version' => REDIRECTION_VERSION,
212 'database' => $status->get_json(),
213 ) );
214
215 $this->add_help_tab();
216 }
217
218 // Some plugins misbehave, so this attempts to 'fix' them so Redirection can get on with it's work
219 private function run_hacks() {
220 add_filter( 'ip-geo-block-admin', array( $this, 'ip_geo_block' ) );
221 }
222
223 /**
224 * This works around the IP Geo Block plugin being very aggressive and breaking Redirection
225 */
226 public function ip_geo_block( $validate ) {
227 $url = Redirection_Request::get_request_url();
228 $override = array(
229 'tools.php?page=redirection.php',
230 'action=red_proxy&rest_path=redirection',
231 );
232
233 foreach ( $override as $path ) {
234 if ( strpos( $url, $path ) !== false ) {
235 return array(
236 'result' => 'passed',
237 'auth' => false,
238 'asn' => false,
239 'code' => false,
240 'ip' => false,
241 );
242 }
243 }
244
245 return $validate;
246 }
247
248 public function check_rest_api() {
249 $options = red_get_options();
250
251 if ( $options['rest_api'] === false || ( defined( 'REDIRECTION_FORCE_UPDATE' ) && REDIRECTION_FORCE_UPDATE ) ) {
252 include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
253
254 $fixer = new Red_Fixer();
255 $status = $fixer->get_rest_status();
256
257 if ( $status['status'] === 'problem' ) {
258 $fixer->fix_rest();
259 } elseif ( $options['rest_api'] === false ) {
260 red_set_options( array( 'rest_api' => 0 ) );
261 }
262 }
263 }
264
265 private function run_fixit() {
266 if ( $this->user_has_access() ) {
267 include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
268
269 $fixer = new Red_Fixer();
270 $result = $fixer->fix( $fixer->get_status() );
271
272 if ( is_wp_error( $result ) ) {
273 $this->fixit_failed = $result;
274 }
275 }
276 }
277
278 private function set_rest_api( $api ) {
279 if ( $api >= 0 && $api <= REDIRECTION_API_POST ) {
280 red_set_options( array( 'rest_api' => intval( $api, 10 ) ) );
281 }
282 }
283
284 private function get_preload_data() {
285 if ( $this->get_menu_page() === 'support' ) {
286 $api = new Redirection_Api_Plugin( REDIRECTION_API_NAMESPACE );
287
288 return array(
289 'pluginStatus' => $api->route_status( new WP_REST_Request() ),
290 );
291 }
292
293 return array();
294 }
295
296 private function add_help_tab() {
297 /* translators: URL */
298 $content = sprintf( __( 'You can find full documentation about using Redirection on the <a href="%s" target="_blank">redirection.me</a> support site.', 'redirection' ), 'https://redirection.me/support/?utm_source=redirection&utm_medium=plugin&utm_campaign=context-help' );
299 $title = __( 'Redirection Support', 'redirection' );
300
301 $current_screen = get_current_screen();
302 $current_screen->add_help_tab( array(
303 'id' => 'redirection',
304 'title' => 'Redirection',
305 'content' => "<h2>$title</h2><p>$content</p>",
306 ) );
307 }
308
309 private function get_per_page() {
310 $per_page = intval( get_user_meta( get_current_user_id(), 'redirection_log_per_page', true ), 10 );
311
312 return $per_page > 0 ? max( 5, min( $per_page, RED_MAX_PER_PAGE ) ) : RED_DEFAULT_PER_PAGE;
313 }
314
315 private function get_i18n_data() {
316 $locale = get_locale();
317
318 // WP 4.7
319 if ( function_exists( 'get_user_locale' ) ) {
320 $locale = get_user_locale();
321 }
322
323 $i18n_json = dirname( REDIRECTION_FILE ) . '/locale/json/redirection-' . $locale . '.json';
324
325 if ( is_file( $i18n_json ) && is_readable( $i18n_json ) ) {
326 $locale_data = @file_get_contents( $i18n_json );
327
328 if ( $locale_data ) {
329 return json_decode( $locale_data );
330 }
331 }
332
333 // Return empty if we have nothing to return so it doesn't fail when parsed in JS
334 return array();
335 }
336
337 public function admin_menu() {
338 $hook = add_management_page( 'Redirection', 'Redirection', $this->get_access_role(), basename( REDIRECTION_FILE ), [ &$this, 'admin_screen' ] );
339 add_action( 'load-' . $hook, [ $this, 'redirection_head' ] );
340 }
341
342 public function get_access_role() {
343 return apply_filters( 'redirection_role', 'manage_options' );
344 }
345
346 private function check_minimum_wp() {
347 $wp_version = get_bloginfo( 'version' );
348
349 if ( version_compare( $wp_version, REDIRECTION_MIN_WP, '<' ) ) {
350 return false;
351 }
352
353 return true;
354 }
355
356 public function set_default_group( $id, $redirect ) {
357 red_set_options( array( 'last_group_id' => $redirect->get_group_id() ) );
358 }
359
360 public function admin_screen() {
361 if ( ! $this->user_has_access() ) {
362 die( 'You do not have sufficient permissions to access this page.' );
363 }
364
365 if ( $this->check_minimum_wp() === false ) {
366 return $this->show_minimum_wordpress();
367 }
368
369 if ( $this->fixit_failed ) {
370 $this->show_fixit_failed();
371 }
372
373 Red_Flusher::schedule();
374
375 $this->show_main();
376 }
377
378 private function show_fixit_failed() {
379 ?>
380 <div class="notice notice-error">
381 <h1><?php echo esc_html( $this->fixit_failed->get_error_message() ); ?></h1>
382 <p><?php echo esc_html( $this->fixit_failed->get_error_data() ); ?></p>
383 </div>
384 <?php
385 }
386
387 private function show_minimum_wordpress() {
388 /* translators: 1: Expected WordPress version, 2: Actual WordPress version */
389 $wp_requirement = sprintf( __( 'Redirection requires WordPress v%1$1s, you are using v%2$2s - please update your WordPress', 'redirection' ), REDIRECTION_MIN_WP, $wp_version );
390 ?>
391 <div class="react-error">
392 <h1><?php esc_html_e( 'Unable to load Redirection', 'redirection' ); ?></h1>
393 <p><?php echo esc_html( $wp_requirement ); ?></p>
394 </div>
395 <?php
396 }
397
398 private function show_load_fail() {
399 ?>
400 <div class="react-error" style="display: none">
401 <h1><?php esc_html_e( 'Unable to load Redirection ☹️', 'redirection' ); ?> v<?php echo esc_html( REDIRECTION_VERSION ); ?></h1>
402 <p><?php esc_html_e( "This may be caused by another plugin - look at your browser's error console for more details.", 'redirection' ); ?></p>
403 <p><?php esc_html_e( 'If you are using a page caching plugin or service (CloudFlare, OVH, etc) then you can also try clearing that cache.', 'redirection' ); ?></p>
404 <p><?php _e( 'Also check if your browser is able to load <code>redirection.js</code>:', 'redirection' ); ?></p>
405 <p><code><?php echo esc_html( plugin_dir_url( REDIRECTION_FILE ) . 'redirection.js?ver=' . urlencode( REDIRECTION_VERSION ) . '-' . urlencode( REDIRECTION_BUILD ) ); ?></code></p>
406 <p><?php esc_html_e( 'Please note that Redirection requires the WordPress REST API to be enabled. If you have disabled this then you won\'t be able to use Redirection', 'redirection' ); ?></p>
407 <p><?php _e( 'Please see the <a href="https://redirection.me/support/problems/">list of common problems</a>.', 'redirection' ); ?></p>
408 <p><?php esc_html_e( 'If you think Redirection is at fault then create an issue.', 'redirection' ); ?></p>
409 <p class="versions"><?php _e( '<code>Redirectioni10n</code> is not defined. This usually means another plugin is blocking Redirection from loading. Please disable all plugins and try again.', 'redirection' ); ?></p>
410 <p>
411 <a class="button-primary" target="_blank" href="https://github.com/johngodley/redirection/issues/new?title=Problem%20starting%20Redirection%20<?php echo esc_attr( REDIRECTION_VERSION ); ?>">
412 <?php esc_html_e( 'Create Issue', 'redirection' ); ?>
413 </a>
414 </p>
415 </div>
416 <?php
417 }
418
419 private function show_main() {
420 ?>
421 <div id="react-modal"></div>
422 <div id="react-ui">
423 <div class="react-loading">
424 <h1><?php esc_html_e( 'Loading, please wait...', 'redirection' ); ?></h1>
425
426 <span class="react-loading-spinner"></span>
427 </div>
428 <noscript><?php esc_html_e( 'Please enable JavaScript', 'redirection' ); ?></noscript>
429
430 <?php $this->show_load_fail(); ?>
431 </div>
432
433 <script>
434 var prevError = window.onerror;
435 var errors = [];
436 var timeout = 0;
437 var timer = setInterval( function() {
438 if ( isRedirectionLoaded() ) {
439 resetAll();
440 } else if ( errors.length > 0 || timeout++ === 5 ) {
441 showError();
442 }
443 }, 5000 );
444
445 function isRedirectionLoaded() {
446 return typeof redirection !== 'undefined';
447 }
448
449 function showError() {
450 var errorText = "";
451
452 if ( errors.length > 0 ) {
453 errorText = "```\n" + errors.join( ',' ) + "\n```\n\n";
454 }
455
456 resetAll();
457 document.querySelector( '.react-loading' ).style.display = 'none';
458 document.querySelector( '.react-error' ).style.display = 'block';
459
460 if ( typeof Redirectioni10n !== 'undefined' ) {
461 document.querySelector( '.versions' ).innerHTML = Redirectioni10n.versions.replace( /\n/g, '<br />' );
462 document.querySelector( '.react-error .button-primary' ).href += '&body=' + encodeURIComponent( errorText ) + encodeURIComponent( Redirectioni10n.versions );
463 }
464 }
465
466 function resetAll() {
467 clearInterval( timer );
468 window.onerror = prevError;
469 }
470
471 window.onerror = function( error, url, line ) {
472 console.error( error );
473 errors.push( error + ' ' + url + ' ' + line );
474 };
475 </script>
476 <?php
477 }
478
479 private function user_has_access() {
480 return current_user_can( $this->get_access_role() );
481 }
482
483 private function inject() {
484 if ( isset( $_GET['page'] ) && $this->get_menu_page() !== 'redirects' && $_GET['page'] === 'redirection.php' ) {
485 $this->try_export_logs();
486 $this->try_export_redirects();
487 $this->try_export_rss();
488 }
489 }
490
491 private function get_menu_page() {
492 if ( isset( $_GET['sub'] ) && in_array( $_GET['sub'], array( 'group', '404s', 'log', 'io', 'options', 'support', true ) ) ) {
493 return $_GET['sub'];
494 }
495
496 return 'redirects';
497 }
498
499 public function try_export_rss() {
500 if ( isset( $_GET['token'] ) && $this->get_menu_page() === 'rss' ) {
501 $options = red_get_options();
502
503 if ( $_GET['token'] === $options['token'] && ! empty( $options['token'] ) ) {
504 $items = Red_Item::get_all_for_module( intval( $_GET['module'] ) );
505
506 $exporter = Red_FileIO::create( 'rss' );
507 $exporter->force_download();
508 echo $exporter->get_data( $items, array() );
509 die();
510 }
511 }
512 }
513
514 private function try_export_logs() {
515 if ( $this->user_has_access() && isset( $_POST['export-csv'] ) && check_admin_referer( 'wp_rest' ) ) {
516 if ( $this->get_menu_page() === 'log' ) {
517 RE_Log::export_to_csv();
518 } else {
519 RE_404::export_to_csv();
520 }
521
522 die();
523 }
524 }
525
526 private function try_export_redirects() {
527 if ( $this->user_has_access() && $_GET['sub'] === 'io' && isset( $_GET['exporter'] ) && isset( $_GET['export'] ) && check_admin_referer( 'wp_rest' ) ) {
528 $export = Red_FileIO::export( $_GET['export'], $_GET['exporter'] );
529
530 if ( $export !== false ) {
531 $export['exporter']->force_download();
532 echo $export['data'];
533 die();
534 }
535 }
536 }
537 }
538
539 register_activation_hook( REDIRECTION_FILE, array( 'Redirection_Admin', 'plugin_activated' ) );
540
541 add_action( 'init', array( 'Redirection_Admin', 'init' ) );
542