PluginProbe
Redirection / 4.1.1
Redirection v4.1.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.1, at redirection-admin.php

544 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 include_once dirname( REDIRECTION_FILE ) . '/models/fixer.php';
287
288 $fixer = new Red_Fixer();
289
290 return array(
291 'pluginStatus' => $fixer->get_status(),
292 );
293 }
294
295 return array();
296 }
297
298 private function add_help_tab() {
299 /* translators: URL */
300 $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' );
301 $title = __( 'Redirection Support', 'redirection' );
302
303 $current_screen = get_current_screen();
304 $current_screen->add_help_tab( array(
305 'id' => 'redirection',
306 'title' => 'Redirection',
307 'content' => "<h2>$title</h2><p>$content</p>",
308 ) );
309 }
310
311 private function get_per_page() {
312 $per_page = intval( get_user_meta( get_current_user_id(), 'redirection_log_per_page', true ), 10 );
313
314 return $per_page > 0 ? max( 5, min( $per_page, RED_MAX_PER_PAGE ) ) : RED_DEFAULT_PER_PAGE;
315 }
316
317 private function get_i18n_data() {
318 $locale = get_locale();
319
320 // WP 4.7
321 if ( function_exists( 'get_user_locale' ) ) {
322 $locale = get_user_locale();
323 }
324
325 $i18n_json = dirname( REDIRECTION_FILE ) . '/locale/json/redirection-' . $locale . '.json';
326
327 if ( is_file( $i18n_json ) && is_readable( $i18n_json ) ) {
328 $locale_data = @file_get_contents( $i18n_json );
329
330 if ( $locale_data ) {
331 return json_decode( $locale_data );
332 }
333 }
334
335 // Return empty if we have nothing to return so it doesn't fail when parsed in JS
336 return array();
337 }
338
339 public function admin_menu() {
340 $hook = add_management_page( 'Redirection', 'Redirection', $this->get_access_role(), basename( REDIRECTION_FILE ), [ $this, 'admin_screen' ] );
341 add_action( 'load-' . $hook, [ $this, 'redirection_head' ] );
342 }
343
344 public function get_access_role() {
345 return apply_filters( 'redirection_role', 'manage_options' );
346 }
347
348 private function check_minimum_wp() {
349 $wp_version = get_bloginfo( 'version' );
350
351 if ( version_compare( $wp_version, REDIRECTION_MIN_WP, '<' ) ) {
352 return false;
353 }
354
355 return true;
356 }
357
358 public function set_default_group( $id, $redirect ) {
359 red_set_options( array( 'last_group_id' => $redirect->get_group_id() ) );
360 }
361
362 public function admin_screen() {
363 if ( ! $this->user_has_access() ) {
364 die( 'You do not have sufficient permissions to access this page.' );
365 }
366
367 if ( $this->check_minimum_wp() === false ) {
368 return $this->show_minimum_wordpress();
369 }
370
371 if ( $this->fixit_failed ) {
372 $this->show_fixit_failed();
373 }
374
375 Red_Flusher::schedule();
376
377 $this->show_main();
378 }
379
380 private function show_fixit_failed() {
381 ?>
382 <div class="notice notice-error">
383 <h1><?php echo esc_html( $this->fixit_failed->get_error_message() ); ?></h1>
384 <p><?php echo esc_html( $this->fixit_failed->get_error_data() ); ?></p>
385 </div>
386 <?php
387 }
388
389 private function show_minimum_wordpress() {
390 /* translators: 1: Expected WordPress version, 2: Actual WordPress version */
391 $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 );
392 ?>
393 <div class="react-error">
394 <h1><?php esc_html_e( 'Unable to load Redirection', 'redirection' ); ?></h1>
395 <p><?php echo esc_html( $wp_requirement ); ?></p>
396 </div>
397 <?php
398 }
399
400 private function show_load_fail() {
401 ?>
402 <div class="react-error" style="display: none">
403 <h1><?php esc_html_e( 'Unable to load Redirection ☹️', 'redirection' ); ?> v<?php echo esc_html( REDIRECTION_VERSION ); ?></h1>
404 <p><?php esc_html_e( "This may be caused by another plugin - look at your browser's error console for more details.", 'redirection' ); ?></p>
405 <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>
406 <p><?php _e( 'Also check if your browser is able to load <code>redirection.js</code>:', 'redirection' ); ?></p>
407 <p><code><?php echo esc_html( plugin_dir_url( REDIRECTION_FILE ) . 'redirection.js?ver=' . urlencode( REDIRECTION_VERSION ) . '-' . urlencode( REDIRECTION_BUILD ) ); ?></code></p>
408 <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>
409 <p><?php _e( 'Please see the <a href="https://redirection.me/support/problems/">list of common problems</a>.', 'redirection' ); ?></p>
410 <p><?php esc_html_e( 'If you think Redirection is at fault then create an issue.', 'redirection' ); ?></p>
411 <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>
412 <p>
413 <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 ); ?>">
414 <?php esc_html_e( 'Create Issue', 'redirection' ); ?>
415 </a>
416 </p>
417 </div>
418 <?php
419 }
420
421 private function show_main() {
422 ?>
423 <div id="react-modal"></div>
424 <div id="react-ui">
425 <div class="react-loading">
426 <h1><?php esc_html_e( 'Loading, please wait...', 'redirection' ); ?></h1>
427
428 <span class="react-loading-spinner"></span>
429 </div>
430 <noscript><?php esc_html_e( 'Please enable JavaScript', 'redirection' ); ?></noscript>
431
432 <?php $this->show_load_fail(); ?>
433 </div>
434
435 <script>
436 var prevError = window.onerror;
437 var errors = [];
438 var timeout = 0;
439 var timer = setInterval( function() {
440 if ( isRedirectionLoaded() ) {
441 resetAll();
442 } else if ( errors.length > 0 || timeout++ === 5 ) {
443 showError();
444 }
445 }, 5000 );
446
447 function isRedirectionLoaded() {
448 return typeof redirection !== 'undefined';
449 }
450
451 function showError() {
452 var errorText = "";
453
454 if ( errors.length > 0 ) {
455 errorText = "```\n" + errors.join( ',' ) + "\n```\n\n";
456 }
457
458 resetAll();
459 document.querySelector( '.react-loading' ).style.display = 'none';
460 document.querySelector( '.react-error' ).style.display = 'block';
461
462 if ( typeof Redirectioni10n !== 'undefined' ) {
463 document.querySelector( '.versions' ).innerHTML = Redirectioni10n.versions.replace( /\n/g, '<br />' );
464 document.querySelector( '.react-error .button-primary' ).href += '&body=' + encodeURIComponent( errorText ) + encodeURIComponent( Redirectioni10n.versions );
465 }
466 }
467
468 function resetAll() {
469 clearInterval( timer );
470 window.onerror = prevError;
471 }
472
473 window.onerror = function( error, url, line ) {
474 console.error( error );
475 errors.push( error + ' ' + url + ' ' + line );
476 };
477 </script>
478 <?php
479 }
480
481 private function user_has_access() {
482 return current_user_can( $this->get_access_role() );
483 }
484
485 private function inject() {
486 if ( isset( $_GET['page'] ) && $this->get_menu_page() !== 'redirects' && $_GET['page'] === 'redirection.php' ) {
487 $this->try_export_logs();
488 $this->try_export_redirects();
489 $this->try_export_rss();
490 }
491 }
492
493 private function get_menu_page() {
494 if ( isset( $_GET['sub'] ) && in_array( $_GET['sub'], array( 'group', '404s', 'log', 'io', 'options', 'support', true ) ) ) {
495 return $_GET['sub'];
496 }
497
498 return 'redirects';
499 }
500
501 public function try_export_rss() {
502 if ( isset( $_GET['token'] ) && $this->get_menu_page() === 'rss' ) {
503 $options = red_get_options();
504
505 if ( $_GET['token'] === $options['token'] && ! empty( $options['token'] ) ) {
506 $items = Red_Item::get_all_for_module( intval( $_GET['module'] ) );
507
508 $exporter = Red_FileIO::create( 'rss' );
509 $exporter->force_download();
510 echo $exporter->get_data( $items, array() );
511 die();
512 }
513 }
514 }
515
516 private function try_export_logs() {
517 if ( $this->user_has_access() && isset( $_POST['export-csv'] ) && check_admin_referer( 'wp_rest' ) ) {
518 if ( $this->get_menu_page() === 'log' ) {
519 RE_Log::export_to_csv();
520 } else {
521 RE_404::export_to_csv();
522 }
523
524 die();
525 }
526 }
527
528 private function try_export_redirects() {
529 if ( $this->user_has_access() && $_GET['sub'] === 'io' && isset( $_GET['exporter'] ) && isset( $_GET['export'] ) && check_admin_referer( 'wp_rest' ) ) {
530 $export = Red_FileIO::export( $_GET['export'], $_GET['exporter'] );
531
532 if ( $export !== false ) {
533 $export['exporter']->force_download();
534 echo $export['data'];
535 die();
536 }
537 }
538 }
539 }
540
541 register_activation_hook( REDIRECTION_FILE, array( 'Redirection_Admin', 'plugin_activated' ) );
542
543 add_action( 'init', array( 'Redirection_Admin', 'init' ) );
544