| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | require_once __DIR__ . '/models/monitor.php'; |
|---|
| 4 | require_once __DIR__ . '/models/file-io.php'; |
|---|
| 5 | require_once __DIR__ . '/database/database.php'; |
|---|
| 6 | require_once __DIR__ . '/redirection-capabilities.php'; |
|---|
| 7 | |
|---|
| 8 | class Redirection_Admin { |
|---|
| 9 | /** |
|---|
| 10 | * @var Redirection_Admin|null |
|---|
| 11 | */ |
|---|
| 12 | private static $instance = null; |
|---|
| 13 | |
|---|
| 14 | /** |
|---|
| 15 | * @var Red_Monitor|null |
|---|
| 16 | * @phpstan-ignore property.onlyWritten |
|---|
| 17 | */ |
|---|
| 18 | private $monitor; |
|---|
| 19 | |
|---|
| 20 | /** |
|---|
| 21 | * @var WP_Error|false |
|---|
| 22 | */ |
|---|
| 23 | private $fixit_failed = false; |
|---|
| 24 | |
|---|
| 25 | /** |
|---|
| 26 | * @return Redirection_Admin|null |
|---|
| 27 | */ |
|---|
| 28 | public static function init() { |
|---|
| 29 | if ( is_null( self::$instance ) ) { |
|---|
| 30 | self::$instance = new Redirection_Admin(); |
|---|
| 31 | } |
|---|
| 32 | |
|---|
| 33 | return self::$instance; |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | public function __construct() { |
|---|
| 37 | // TODO: remove this once we have a stable installed version |
|---|
| 38 | if ( ! class_exists( 'Red_Options' ) ) { |
|---|
| 39 | if ( function_exists( 'opcache_reset' ) ) { |
|---|
| 40 | opcache_reset(); |
|---|
| 41 | } |
|---|
| 42 | |
|---|
| 43 | add_action( 'admin_notices', [ $this, 'show_incomplete_installation_notice' ] ); |
|---|
| 44 | return; |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | add_action( 'admin_menu', [ $this, 'admin_menu' ] ); |
|---|
| 48 | add_action( 'admin_notices', [ $this, 'update_nag' ] ); |
|---|
| 49 | add_filter( 'plugin_action_links_' . basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ), [ $this, 'plugin_settings' ] ); |
|---|
| 50 | add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 4 ); |
|---|
| 51 | add_filter( 'redirection_save_options', [ $this, 'flush_schedule' ] ); |
|---|
| 52 | add_filter( 'set-screen-option', [ $this, 'set_per_page' ], 10, 3 ); |
|---|
| 53 | add_filter( |
|---|
| 54 | 'set_screen_option_redirection_log_per_page', |
|---|
| 55 | function ( $ignore, $option, $value ) { |
|---|
| 56 | return $value; |
|---|
| 57 | }, |
|---|
| 58 | 10, |
|---|
| 59 | 3 |
|---|
| 60 | ); |
|---|
| 61 | add_action( 'redirection_redirect_updated', [ $this, 'set_default_group' ], 10, 2 ); |
|---|
| 62 | add_action( 'redirection_redirect_updated', [ $this, 'clear_cache' ] ); |
|---|
| 63 | |
|---|
| 64 | if ( defined( 'REDIRECTION_FLYING_SOLO' ) && REDIRECTION_FLYING_SOLO ) { |
|---|
| 65 | add_filter( 'script_loader_src', [ $this, 'flying_solo' ], 10, 2 ); |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | register_deactivation_hook( REDIRECTION_FILE, [ 'Redirection_Admin', 'plugin_deactivated' ] ); |
|---|
| 69 | register_uninstall_hook( REDIRECTION_FILE, [ 'Redirection_Admin', 'plugin_uninstall' ] ); |
|---|
| 70 | |
|---|
| 71 | $this->monitor = new Red_Monitor( Red_Options::get() ); |
|---|
| 72 | $this->run_hacks(); |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | /** |
|---|
| 76 | * These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites |
|---|
| 77 | * @return void |
|---|
| 78 | */ |
|---|
| 79 | public static function plugin_activated() { |
|---|
| 80 | Red_Database::apply_to_sites( |
|---|
| 81 | function () { |
|---|
| 82 | Red_Flusher::clear(); |
|---|
| 83 | red_set_options(); |
|---|
| 84 | } |
|---|
| 85 | ); |
|---|
| 86 | } |
|---|
| 87 | |
|---|
| 88 | /** |
|---|
| 89 | * These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites |
|---|
| 90 | * @return void |
|---|
| 91 | */ |
|---|
| 92 | public static function plugin_deactivated() { |
|---|
| 93 | Red_Database::apply_to_sites( |
|---|
| 94 | function () { |
|---|
| 95 | Red_Flusher::clear(); |
|---|
| 96 | } |
|---|
| 97 | ); |
|---|
| 98 | } |
|---|
| 99 | |
|---|
| 100 | /** |
|---|
| 101 | * These are only called on the single standard site, or in the network admin of the multisite - they run across all available sites |
|---|
| 102 | * @return void |
|---|
| 103 | */ |
|---|
| 104 | public static function plugin_uninstall() { |
|---|
| 105 | $database = Red_Database::get_latest_database(); |
|---|
| 106 | |
|---|
| 107 | Red_Database::apply_to_sites( |
|---|
| 108 | function () use ( $database ) { |
|---|
| 109 | $database->remove(); |
|---|
| 110 | } |
|---|
| 111 | ); |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | /** |
|---|
| 115 | * Show the database upgrade nag |
|---|
| 116 | * |
|---|
| 117 | * @return void |
|---|
| 118 | */ |
|---|
| 119 | public function update_nag() { |
|---|
| 120 | $options = Red_Options::get(); |
|---|
| 121 | |
|---|
| 122 | // Is the site configured to upgrade automatically? |
|---|
| 123 | if ( $options['plugin_update'] === 'admin' ) { |
|---|
| 124 | $this->automatic_upgrade(); |
|---|
| 125 | return; |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | // Can the user perform a manual database upgrade? |
|---|
| 129 | if ( ! Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_OPTION_MANAGE ) ) { |
|---|
| 130 | return; |
|---|
| 131 | } |
|---|
| 132 | |
|---|
| 133 | // Default manual update, with nag |
|---|
| 134 | $status = new Red_Database_Status(); |
|---|
| 135 | |
|---|
| 136 | $message = false; |
|---|
| 137 | if ( $status->needs_installing() ) { |
|---|
| 138 | /* translators: 1: URL to plugin page */ |
|---|
| 139 | $message = sprintf( __( 'Please complete your <a href="%s">Redirection setup</a> to activate the plugin.', 'redirection' ), esc_url( $this->get_plugin_url() ) ); |
|---|
| 140 | } elseif ( $status->needs_updating() ) { |
|---|
| 141 | /* translators: 1: URL to plugin page, 2: current version, 3: target version */ |
|---|
| 142 | $message = sprintf( __( 'Redirection\'s database needs to be updated - <a href="%1$1s">click to update</a>.', 'redirection' ), esc_url( $this->get_plugin_url() ) ); |
|---|
| 143 | } |
|---|
| 144 | |
|---|
| 145 | if ( $message === false || strpos( Redirection_Request::get_request_url(), 'page=redirection.php' ) !== false ) { |
|---|
| 146 | return; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | // Known HTML and so isn't escaped |
|---|
| 150 | // phpcs:ignore |
|---|
| 151 | echo '<div class="update-nag notice notice-warning" style="width: 95%">' . $message . '</div>'; |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | /** |
|---|
| 155 | * Show incomplete installation error |
|---|
| 156 | * |
|---|
| 157 | * @return void |
|---|
| 158 | */ |
|---|
| 159 | public function show_incomplete_installation_notice() { |
|---|
| 160 | ?> |
|---|
| 161 | <div class="notice notice-error"> |
|---|
| 162 | <p> |
|---|
| 163 | <strong><?php esc_html_e( 'Redirection Error: Incomplete Installation Detected', 'redirection' ); ?></strong> |
|---|
| 164 | </p> |
|---|
| 165 | <p> |
|---|
| 166 | <?php esc_html_e( 'Redirection has detected that required files are missing or were not properly loaded. This likely means your cache needs clearing.', 'redirection' ); ?> |
|---|
| 167 | </p> |
|---|
| 168 | <p> |
|---|
| 169 | <?php esc_html_e( 'Please try clearing your cache and reloading the page.', 'redirection' ); ?> |
|---|
| 170 | </p> |
|---|
| 171 | </div> |
|---|
| 172 | <?php |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | /** |
|---|
| 176 | * Perform an automatic DB upgrade |
|---|
| 177 | * |
|---|
| 178 | * @return void |
|---|
| 179 | */ |
|---|
| 180 | private function automatic_upgrade() { |
|---|
| 181 | $loop = 0; |
|---|
| 182 | $status = new Red_Database_Status(); |
|---|
| 183 | $database = new Red_Database(); |
|---|
| 184 | |
|---|
| 185 | // Loop until the DB is upgraded, or until a max is exceeded (just in case) |
|---|
| 186 | while ( $loop < 20 ) { |
|---|
| 187 | if ( ! $status->needs_updating() ) { |
|---|
| 188 | break; |
|---|
| 189 | } |
|---|
| 190 | |
|---|
| 191 | $database->apply_upgrade( $status ); |
|---|
| 192 | |
|---|
| 193 | if ( $status->is_error() ) { |
|---|
| 194 | // If an error occurs then switch to 'prompt' mode and let the user deal with it. |
|---|
| 195 | red_set_options( [ 'plugin_update' => 'prompt' ] ); |
|---|
| 196 | return; |
|---|
| 197 | } |
|---|
| 198 | |
|---|
| 199 | $loop++; |
|---|
| 200 | } |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | |
|---|
| 204 | /** |
|---|
| 205 | * 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 |
|---|
| 206 | * then this can prevent Redirection running and it's a little sensitive about that. We use the nuclear option here to disable |
|---|
| 207 | * all other JS while viewing Redirection |
|---|
| 208 | * @param string $src |
|---|
| 209 | * @param string $handle |
|---|
| 210 | * @return string|false |
|---|
| 211 | */ |
|---|
| 212 | public function flying_solo( $src, $handle ) { |
|---|
| 213 | $request = Redirection_Request::get_request_url(); |
|---|
| 214 | |
|---|
| 215 | if ( strpos( $request, 'page=redirection.php' ) !== false ) { |
|---|
| 216 | if ( substr( $src, 0, 4 ) === 'http' && $handle !== 'redirection' && strpos( $src, 'plugins' ) !== false ) { |
|---|
| 217 | if ( $this->ignore_this_plugin( $src ) ) { |
|---|
| 218 | return false; |
|---|
| 219 | } |
|---|
| 220 | } |
|---|
| 221 | } |
|---|
| 222 | |
|---|
| 223 | return $src; |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | /** |
|---|
| 227 | * @param string $src |
|---|
| 228 | * @return bool |
|---|
| 229 | */ |
|---|
| 230 | private function ignore_this_plugin( $src ) { |
|---|
| 231 | $ignore = array( |
|---|
| 232 | 'mootools', |
|---|
| 233 | 'wp-seo-', |
|---|
| 234 | 'authenticate', |
|---|
| 235 | 'wordpress-seo', |
|---|
| 236 | 'yikes', |
|---|
| 237 | ); |
|---|
| 238 | |
|---|
| 239 | foreach ( $ignore as $text ) { |
|---|
| 240 | if ( strpos( $src, $text ) !== false ) { |
|---|
| 241 | return true; |
|---|
| 242 | } |
|---|
| 243 | } |
|---|
| 244 | |
|---|
| 245 | return false; |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | /** |
|---|
| 249 | * @param mixed $options |
|---|
| 250 | * @return mixed |
|---|
| 251 | */ |
|---|
| 252 | public function flush_schedule( $options ) { |
|---|
| 253 | Red_Flusher::schedule(); |
|---|
| 254 | return $options; |
|---|
| 255 | } |
|---|
| 256 | |
|---|
| 257 | /** |
|---|
| 258 | * @param mixed $status |
|---|
| 259 | * @param mixed $option |
|---|
| 260 | * @param mixed $value |
|---|
| 261 | * @return mixed |
|---|
| 262 | */ |
|---|
| 263 | public function set_per_page( $status, $option, $value ) { |
|---|
| 264 | if ( $option === 'redirection_log_per_page' ) { |
|---|
| 265 | $value = max( 1, min( intval( $value, 10 ), Red_Log::MAX_PER_PAGE ) ); |
|---|
| 266 | return $value; |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | return $status; |
|---|
| 270 | } |
|---|
| 271 | |
|---|
| 272 | /** |
|---|
| 273 | * @param array<int|string, string> $links |
|---|
| 274 | * @return array<int|string, string> |
|---|
| 275 | */ |
|---|
| 276 | public function plugin_settings( array $links ): array { |
|---|
| 277 | $status = new Red_Database_Status(); |
|---|
| 278 | if ( $status->needs_updating() ) { |
|---|
| 279 | array_unshift( $links, '<a style="color: red" href="' . esc_url( $this->get_plugin_url() ) . '&sub=support">' . __( 'Upgrade Database', 'redirection' ) . '</a>' ); |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | array_unshift( $links, '<a href="' . esc_url( $this->get_plugin_url() ) . '&sub=options">' . __( 'Settings', 'redirection' ) . '</a>' ); |
|---|
| 283 | return $links; |
|---|
| 284 | } |
|---|
| 285 | |
|---|
| 286 | /** |
|---|
| 287 | * @param mixed $plugin_meta |
|---|
| 288 | * @param mixed $plugin_file |
|---|
| 289 | * @param mixed $plugin_data |
|---|
| 290 | * @param mixed $status |
|---|
| 291 | * @return mixed |
|---|
| 292 | */ |
|---|
| 293 | public function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) { |
|---|
| 294 | if ( $plugin_file === basename( dirname( REDIRECTION_FILE ) ) . '/' . basename( REDIRECTION_FILE ) ) { |
|---|
| 295 | $plugin_data['Description'] .= '<p>' . __( 'Please upgrade your database', 'redirection' ) . '</p>'; |
|---|
| 296 | } |
|---|
| 297 | |
|---|
| 298 | return $plugin_meta; |
|---|
| 299 | } |
|---|
| 300 | |
|---|
| 301 | /** |
|---|
| 302 | * @return string |
|---|
| 303 | */ |
|---|
| 304 | private function get_plugin_url() { |
|---|
| 305 | return admin_url( 'tools.php?page=' . basename( REDIRECTION_FILE ) ); |
|---|
| 306 | } |
|---|
| 307 | |
|---|
| 308 | /** |
|---|
| 309 | * @return string |
|---|
| 310 | */ |
|---|
| 311 | private function get_first_available_page_url() { |
|---|
| 312 | $pages = Redirection_Capabilities::get_available_pages(); |
|---|
| 313 | |
|---|
| 314 | if ( count( $pages ) > 0 ) { |
|---|
| 315 | return $this->get_plugin_url() . ( $pages[0] === 'redirect' ? '' : '&sub=' . rawurlencode( $pages[0] ) ); |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | return admin_url(); |
|---|
| 319 | } |
|---|
| 320 | |
|---|
| 321 | /** |
|---|
| 322 | * @param mixed $name |
|---|
| 323 | * @return string|null |
|---|
| 324 | */ |
|---|
| 325 | private function get_query( $name ) { |
|---|
| 326 | if ( isset( $_GET[ $name ] ) ) { |
|---|
| 327 | return sanitize_text_field( $_GET[ $name ] ); |
|---|
| 328 | } |
|---|
| 329 | |
|---|
| 330 | return null; |
|---|
| 331 | } |
|---|
| 332 | |
|---|
| 333 | /** |
|---|
| 334 | * @return void |
|---|
| 335 | */ |
|---|
| 336 | public function redirection_head() { |
|---|
| 337 | global $wp_version; |
|---|
| 338 | |
|---|
| 339 | // Does user have access to this page? |
|---|
| 340 | if ( $this->get_current_page() === false ) { |
|---|
| 341 | // Redirect to root plugin page |
|---|
| 342 | wp_safe_redirect( $this->get_first_available_page_url() ); |
|---|
| 343 | die(); |
|---|
| 344 | } |
|---|
| 345 | |
|---|
| 346 | if ( isset( $_REQUEST['action'] ) && isset( $_REQUEST['_wpnonce'] ) && is_string( $_REQUEST['action'] ) && wp_verify_nonce( $_REQUEST['_wpnonce'], 'wp_rest' ) !== false ) { |
|---|
| 347 | $action = sanitize_text_field( $_REQUEST['action'] ); |
|---|
| 348 | |
|---|
| 349 | if ( $action === 'fixit' ) { |
|---|
| 350 | $this->run_fixit(); |
|---|
| 351 | } elseif ( $action === 'rest_api' && isset( $_REQUEST['rest_api'] ) && is_string( $_REQUEST['rest_api'] ) ) { |
|---|
| 352 | $this->set_rest_api( intval( $_REQUEST['rest_api'], 10 ) ); |
|---|
| 353 | } |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | $build = REDIRECTION_VERSION . '-' . REDIRECTION_BUILD; |
|---|
| 357 | $preload = $this->get_preload_data(); |
|---|
| 358 | $options = Red_Options::get(); |
|---|
| 359 | $versions = array( |
|---|
| 360 | 'Plugin: ' . REDIRECTION_VERSION . ' ' . REDIRECTION_DB_VERSION, |
|---|
| 361 | 'WordPress: ' . $wp_version . ' (' . ( is_multisite() ? 'multi' : 'single' ) . ')', |
|---|
| 362 | 'PHP: ' . phpversion(), |
|---|
| 363 | 'Browser: ' . Redirection_Request::get_user_agent(), |
|---|
| 364 | 'JavaScript: ' . plugin_dir_url( REDIRECTION_FILE ) . 'build/redirection.js?ver=' . $build, |
|---|
| 365 | 'REST API: ' . red_get_rest_api(), |
|---|
| 366 | ); |
|---|
| 367 | |
|---|
| 368 | $this->inject(); |
|---|
| 369 | |
|---|
| 370 | // Add contextual help to some pages |
|---|
| 371 | if ( in_array( $this->get_current_page(), [ 'redirect', 'log', '404s', 'groups' ], true ) ) { |
|---|
| 372 | add_screen_option( |
|---|
| 373 | 'per_page', |
|---|
| 374 | array( |
|---|
| 375 | /* translators: maximum number of log entries */ |
|---|
| 376 | 'label' => sprintf( __( 'Log entries (%d max)', 'redirection' ), Red_Log::MAX_PER_PAGE ), |
|---|
| 377 | 'default' => Red_Log::DEFAULT_PER_PAGE, |
|---|
| 378 | 'option' => 'redirection_log_per_page', |
|---|
| 379 | ) |
|---|
| 380 | ); |
|---|
| 381 | } |
|---|
| 382 | |
|---|
| 383 | $assets = include plugin_dir_path( REDIRECTION_FILE ) . 'build/redirection.asset.php'; |
|---|
| 384 | $dependencies = $assets['dependencies']; |
|---|
| 385 | $version = $assets['version']; |
|---|
| 386 | |
|---|
| 387 | wp_enqueue_script( 'redirection', plugin_dir_url( REDIRECTION_FILE ) . 'build/redirection.js', $dependencies, $version, true ); |
|---|
| 388 | wp_enqueue_style( 'redirection', plugin_dir_url( REDIRECTION_FILE ) . 'build/redirection.css', [], $version ); |
|---|
| 389 | |
|---|
| 390 | $is_new = false; |
|---|
| 391 | $major_version = implode( '.', array_slice( explode( '.', REDIRECTION_VERSION ), 0, 2 ) ); |
|---|
| 392 | |
|---|
| 393 | if ( $this->get_query( 'page' ) === 'redirection.php' && strpos( REDIRECTION_VERSION, '-beta' ) === false ) { |
|---|
| 394 | $is_new = version_compare( (string) $options['update_notice'], $major_version ) < 0; |
|---|
| 395 | } |
|---|
| 396 | |
|---|
| 397 | $status = new Red_Database_Status(); |
|---|
| 398 | $status->check_tables_exist(); |
|---|
| 399 | |
|---|
| 400 | // Fix some sites having a version set to +OK - not sure why |
|---|
| 401 | if ( $options['database'] === '+OK' ) { |
|---|
| 402 | red_set_options( [ 'database' => REDIRECTION_DB_VERSION ] ); |
|---|
| 403 | $status->stop_update(); |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | wp_localize_script( |
|---|
| 407 | 'redirection', |
|---|
| 408 | 'Redirectioni10n', |
|---|
| 409 | array( |
|---|
| 410 | 'api' => [ |
|---|
| 411 | 'WP_API_root' => esc_url_raw( red_get_rest_api() ), |
|---|
| 412 | 'WP_API_nonce' => wp_create_nonce( 'wp_rest' ), |
|---|
| 413 | 'site_health' => admin_url( 'site-health.php' ), |
|---|
| 414 | 'current' => $options['rest_api'], |
|---|
| 415 | 'routes' => [ |
|---|
| 416 | Red_Options::API_JSON => red_get_rest_api( Red_Options::API_JSON ), |
|---|
| 417 | Red_Options::API_JSON_INDEX => red_get_rest_api( Red_Options::API_JSON_INDEX ), |
|---|
| 418 | Red_Options::API_JSON_RELATIVE => red_get_rest_api( Red_Options::API_JSON_RELATIVE ), |
|---|
| 419 | ], |
|---|
| 420 | ], |
|---|
| 421 | 'pluginBaseUrl' => plugins_url( '', REDIRECTION_FILE ), |
|---|
| 422 | 'pluginRoot' => $this->get_plugin_url(), |
|---|
| 423 | 'per_page' => $this->get_per_page(), |
|---|
| 424 | 'locale' => implode( '-', array_slice( explode( '-', str_replace( '_', '-', get_locale() ) ), 0, 2 ) ), |
|---|
| 425 | 'settings' => $options, |
|---|
| 426 | 'preload' => $preload, |
|---|
| 427 | 'versions' => implode( "\n", $versions ), |
|---|
| 428 | 'version' => REDIRECTION_VERSION, |
|---|
| 429 | 'database' => $status->get_json(), |
|---|
| 430 | 'caps' => [ |
|---|
| 431 | 'pages' => Redirection_Capabilities::get_available_pages(), |
|---|
| 432 | 'capabilities' => Redirection_Capabilities::get_all_capabilities(), |
|---|
| 433 | ], |
|---|
| 434 | 'update_notice' => $is_new ? $major_version : false, |
|---|
| 435 | ) |
|---|
| 436 | ); |
|---|
| 437 | |
|---|
| 438 | wp_set_script_translations( 'redirection', 'redirection' ); |
|---|
| 439 | |
|---|
| 440 | $this->add_help_tab(); |
|---|
| 441 | } |
|---|
| 442 | |
|---|
| 443 | /** |
|---|
| 444 | * Some plugins misbehave, so this attempts to 'fix' them so Redirection can get on with it's work |
|---|
| 445 | * @return void |
|---|
| 446 | */ |
|---|
| 447 | private function run_hacks() { |
|---|
| 448 | add_filter( 'ip-geo-block-admin', array( $this, 'ip_geo_block' ) ); |
|---|
| 449 | } |
|---|
| 450 | |
|---|
| 451 | /** |
|---|
| 452 | * This works around the IP Geo Block plugin being very aggressive and breaking Redirection |
|---|
| 453 | * |
|---|
| 454 | * @param array<string, mixed> $validate |
|---|
| 455 | * @return array<string, mixed> |
|---|
| 456 | */ |
|---|
| 457 | public function ip_geo_block( array $validate ): array { |
|---|
| 458 | $url = Redirection_Request::get_request_url(); |
|---|
| 459 | $override = array( |
|---|
| 460 | 'tools.php?page=redirection.php', |
|---|
| 461 | 'action=red_proxy&rest_path=redirection', |
|---|
| 462 | ); |
|---|
| 463 | |
|---|
| 464 | foreach ( $override as $path ) { |
|---|
| 465 | if ( strpos( $url, $path ) !== false ) { |
|---|
| 466 | return array( |
|---|
| 467 | 'result' => 'passed', |
|---|
| 468 | 'auth' => false, |
|---|
| 469 | 'asn' => false, |
|---|
| 470 | 'code' => false, |
|---|
| 471 | 'ip' => false, |
|---|
| 472 | ); |
|---|
| 473 | } |
|---|
| 474 | } |
|---|
| 475 | |
|---|
| 476 | return $validate; |
|---|
| 477 | } |
|---|
| 478 | |
|---|
| 479 | /** |
|---|
| 480 | * @return void |
|---|
| 481 | */ |
|---|
| 482 | private function run_fixit() { |
|---|
| 483 | if ( Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_SUPPORT_MANAGE ) ) { |
|---|
| 484 | require_once dirname( REDIRECTION_FILE ) . '/models/fixer.php'; |
|---|
| 485 | |
|---|
| 486 | $fixer = new Red_Fixer(); |
|---|
| 487 | $result = $fixer->fix( $fixer->get_status() ); |
|---|
| 488 | |
|---|
| 489 | if ( is_wp_error( $result ) ) { |
|---|
| 490 | $this->fixit_failed = $result; |
|---|
| 491 | } |
|---|
| 492 | } |
|---|
| 493 | } |
|---|
| 494 | |
|---|
| 495 | /** |
|---|
| 496 | * @param mixed $api |
|---|
| 497 | * @return void |
|---|
| 498 | */ |
|---|
| 499 | private function set_rest_api( $api ) { |
|---|
| 500 | if ( $api >= 0 && $api <= Red_Options::API_JSON_RELATIVE ) { |
|---|
| 501 | red_set_options( array( 'rest_api' => intval( $api, 10 ) ) ); |
|---|
| 502 | } |
|---|
| 503 | } |
|---|
| 504 | |
|---|
| 505 | /** |
|---|
| 506 | * @return array{importers: list<array{id: string, name: string, total: int}>}|array{pluginStatus: array<string, mixed>}|array{} |
|---|
| 507 | */ |
|---|
| 508 | private function get_preload_data(): array { |
|---|
| 509 | $status = new Red_Database_Status(); |
|---|
| 510 | |
|---|
| 511 | if ( $status->needs_installing() ) { |
|---|
| 512 | include_once __DIR__ . '/models/importer.php'; |
|---|
| 513 | |
|---|
| 514 | return [ |
|---|
| 515 | 'importers' => Red_Plugin_Importer::get_plugins(), |
|---|
| 516 | ]; |
|---|
| 517 | } |
|---|
| 518 | |
|---|
| 519 | if ( $this->get_current_page() === 'support' ) { |
|---|
| 520 | require_once dirname( REDIRECTION_FILE ) . '/models/fixer.php'; |
|---|
| 521 | |
|---|
| 522 | $fixer = new Red_Fixer(); |
|---|
| 523 | |
|---|
| 524 | return array( |
|---|
| 525 | 'pluginStatus' => $fixer->get_json(), |
|---|
| 526 | ); |
|---|
| 527 | } |
|---|
| 528 | |
|---|
| 529 | return []; |
|---|
| 530 | } |
|---|
| 531 | |
|---|
| 532 | /** |
|---|
| 533 | * @return void |
|---|
| 534 | */ |
|---|
| 535 | private function add_help_tab() { |
|---|
| 536 | /* translators: URL */ |
|---|
| 537 | $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' ); |
|---|
| 538 | $title = __( 'Redirection Support', 'redirection' ); |
|---|
| 539 | |
|---|
| 540 | $current_screen = get_current_screen(); |
|---|
| 541 | if ( $current_screen === null ) { |
|---|
| 542 | return; |
|---|
| 543 | } |
|---|
| 544 | |
|---|
| 545 | $current_screen->add_help_tab( |
|---|
| 546 | array( |
|---|
| 547 | 'id' => 'redirection', |
|---|
| 548 | 'title' => 'Redirection', |
|---|
| 549 | 'content' => "<h2>$title</h2><p>$content</p>", |
|---|
| 550 | ) |
|---|
| 551 | ); |
|---|
| 552 | } |
|---|
| 553 | |
|---|
| 554 | /** |
|---|
| 555 | * @return int |
|---|
| 556 | */ |
|---|
| 557 | private function get_per_page() { |
|---|
| 558 | $per_page = intval( get_user_meta( get_current_user_id(), 'redirection_log_per_page', true ), 10 ); |
|---|
| 559 | |
|---|
| 560 | return $per_page > 0 ? max( 5, min( $per_page, Red_Log::MAX_PER_PAGE ) ) : Red_Log::DEFAULT_PER_PAGE; |
|---|
| 561 | } |
|---|
| 562 | |
|---|
| 563 | /** |
|---|
| 564 | * @return void |
|---|
| 565 | */ |
|---|
| 566 | public function admin_menu() { |
|---|
| 567 | $hook = add_management_page( 'Redirection', 'Redirection', Redirection_Capabilities::get_plugin_access(), basename( REDIRECTION_FILE ), [ $this, 'admin_screen' ] ); |
|---|
| 568 | add_action( 'load-' . $hook, [ $this, 'redirection_head' ] ); |
|---|
| 569 | } |
|---|
| 570 | |
|---|
| 571 | /** |
|---|
| 572 | * @return bool |
|---|
| 573 | */ |
|---|
| 574 | private function check_minimum_wp() { |
|---|
| 575 | $wp_version = get_bloginfo( 'version' ); |
|---|
| 576 | |
|---|
| 577 | if ( version_compare( $wp_version, REDIRECTION_MIN_WP, '<' ) ) { |
|---|
| 578 | return false; |
|---|
| 579 | } |
|---|
| 580 | |
|---|
| 581 | return true; |
|---|
| 582 | } |
|---|
| 583 | |
|---|
| 584 | /** |
|---|
| 585 | * Update the cache key when updating or creating a redirect |
|---|
| 586 | * |
|---|
| 587 | * @return void |
|---|
| 588 | */ |
|---|
| 589 | public function clear_cache() { |
|---|
| 590 | $settings = Red_Options::get(); |
|---|
| 591 | |
|---|
| 592 | if ( $settings['cache_key'] > 0 ) { |
|---|
| 593 | red_set_options( [ 'cache_key' => time() ] ); |
|---|
| 594 | } |
|---|
| 595 | } |
|---|
| 596 | |
|---|
| 597 | /** |
|---|
| 598 | * @param mixed $id |
|---|
| 599 | * @param mixed $redirect |
|---|
| 600 | * @return void |
|---|
| 601 | */ |
|---|
| 602 | public function set_default_group( $id, $redirect ) { |
|---|
| 603 | red_set_options( array( 'last_group_id' => $redirect->get_group_id() ) ); |
|---|
| 604 | } |
|---|
| 605 | |
|---|
| 606 | /** |
|---|
| 607 | * @return void |
|---|
| 608 | */ |
|---|
| 609 | public function admin_screen() { |
|---|
| 610 | if ( count( Redirection_Capabilities::get_all_capabilities() ) === 0 ) { |
|---|
| 611 | die( 'You do not have sufficient permissions to access this page.' ); |
|---|
| 612 | } |
|---|
| 613 | |
|---|
| 614 | if ( $this->check_minimum_wp() === false ) { |
|---|
| 615 | $this->show_minimum_wordpress(); |
|---|
| 616 | return; |
|---|
| 617 | } |
|---|
| 618 | |
|---|
| 619 | if ( $this->fixit_failed instanceof WP_Error ) { |
|---|
| 620 | $this->show_fixit_failed(); |
|---|
| 621 | } |
|---|
| 622 | |
|---|
| 623 | Red_Flusher::schedule(); |
|---|
| 624 | |
|---|
| 625 | $this->show_main(); |
|---|
| 626 | } |
|---|
| 627 | |
|---|
| 628 | /** |
|---|
| 629 | * @return void |
|---|
| 630 | */ |
|---|
| 631 | private function show_fixit_failed() { |
|---|
| 632 | if ( $this->fixit_failed === false ) { |
|---|
| 633 | return; |
|---|
| 634 | } |
|---|
| 635 | ?> |
|---|
| 636 | <div class="notice notice-error"> |
|---|
| 637 | <h1><?php echo esc_html( $this->fixit_failed->get_error_message() ); ?></h1> |
|---|
| 638 | <p><?php echo esc_html( $this->fixit_failed->get_error_data() ); ?></p> |
|---|
| 639 | </div> |
|---|
| 640 | <?php |
|---|
| 641 | } |
|---|
| 642 | |
|---|
| 643 | /** |
|---|
| 644 | * @return void |
|---|
| 645 | */ |
|---|
| 646 | private function show_minimum_wordpress() { |
|---|
| 647 | global $wp_version; |
|---|
| 648 | |
|---|
| 649 | /* translators: 1: Expected WordPress version, 2: Actual WordPress version */ |
|---|
| 650 | $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 ); |
|---|
| 651 | ?> |
|---|
| 652 | <div class="react-error"> |
|---|
| 653 | <h1><?php esc_html_e( 'Unable to load Redirection', 'redirection' ); ?></h1> |
|---|
| 654 | <p><?php echo esc_html( $wp_requirement ); ?></p> |
|---|
| 655 | </div> |
|---|
| 656 | <?php |
|---|
| 657 | } |
|---|
| 658 | |
|---|
| 659 | /** |
|---|
| 660 | * @return void |
|---|
| 661 | */ |
|---|
| 662 | private function show_load_fail() { |
|---|
| 663 | ?> |
|---|
| 664 | <div class="react-error" style="display: none"> |
|---|
| 665 | <h1><?php esc_html_e( 'Unable to load Redirection ☹️', 'redirection' ); ?> v<?php echo esc_html( REDIRECTION_VERSION ); ?></h1> |
|---|
| 666 | <p><?php esc_html_e( "This may be caused by another plugin - look at your browser's error console for more details.", 'redirection' ); ?></p> |
|---|
| 667 | <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> |
|---|
| 668 | <p> |
|---|
| 669 | <?php |
|---|
| 670 | echo wp_kses_post( |
|---|
| 671 | sprintf( |
|---|
| 672 | /* translators: %s: redirection script filename */ |
|---|
| 673 | __( 'Also check if your browser is able to load %s:', 'redirection' ), |
|---|
| 674 | '<code>' . esc_html( 'redirection.js' ) . '</code>' |
|---|
| 675 | ) |
|---|
| 676 | ); |
|---|
| 677 | ?> |
|---|
| 678 | </p> |
|---|
| 679 | <p><code><?php echo esc_html( plugin_dir_url( REDIRECTION_FILE ) . 'redirection.js?ver=' . rawurlencode( REDIRECTION_VERSION ) . '-' . rawurlencode( REDIRECTION_BUILD ) ); ?></code></p> |
|---|
| 680 | <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> |
|---|
| 681 | <p> |
|---|
| 682 | <?php |
|---|
| 683 | echo wp_kses_post( |
|---|
| 684 | sprintf( |
|---|
| 685 | /* translators: %s: URL to common problems documentation */ |
|---|
| 686 | __( 'Please see the <a href="%s">list of common problems</a>.', 'redirection' ), |
|---|
| 687 | esc_url( 'https://redirection.me/support/problems/' ) |
|---|
| 688 | ) |
|---|
| 689 | ); |
|---|
| 690 | ?> |
|---|
| 691 | </p> |
|---|
| 692 | <p><?php esc_html_e( 'If you think Redirection is at fault then create an issue.', 'redirection' ); ?></p> |
|---|
| 693 | <p class="versions"> |
|---|
| 694 | <?php |
|---|
| 695 | echo wp_kses_post( |
|---|
| 696 | __( '<code>Redirectioni10n</code> is not defined. This usually means another plugin is blocking Redirection from loading. Please disable all plugins and try again.', 'redirection' ) |
|---|
| 697 | ); |
|---|
| 698 | ?> |
|---|
| 699 | </p> |
|---|
| 700 | <p> |
|---|
| 701 | <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 ); ?>"> |
|---|
| 702 | <?php esc_html_e( 'Create Issue', 'redirection' ); ?> |
|---|
| 703 | </a> |
|---|
| 704 | </p> |
|---|
| 705 | </div> |
|---|
| 706 | <?php |
|---|
| 707 | } |
|---|
| 708 | |
|---|
| 709 | /** |
|---|
| 710 | * @return void |
|---|
| 711 | */ |
|---|
| 712 | private function show_main() { |
|---|
| 713 | ?> |
|---|
| 714 | <div id="react-modal"></div> |
|---|
| 715 | <div id="react-ui"> |
|---|
| 716 | <div class="react-loading"> |
|---|
| 717 | <h1><?php esc_html_e( 'Loading, please wait...', 'redirection' ); ?></h1> |
|---|
| 718 | |
|---|
| 719 | <span class="react-loading-spinner"></span> |
|---|
| 720 | </div> |
|---|
| 721 | <noscript><?php esc_html_e( 'Please enable JavaScript', 'redirection' ); ?></noscript> |
|---|
| 722 | |
|---|
| 723 | <?php $this->show_load_fail(); ?> |
|---|
| 724 | </div> |
|---|
| 725 | |
|---|
| 726 | <script> |
|---|
| 727 | var prevError = window.onerror; |
|---|
| 728 | var errors = []; |
|---|
| 729 | var timeout = 0; |
|---|
| 730 | var timer = setInterval( function() { |
|---|
| 731 | if ( isRedirectionLoaded() ) { |
|---|
| 732 | resetAll(); |
|---|
| 733 | } else if ( errors.length > 0 || timeout++ === 5 ) { |
|---|
| 734 | showError(); |
|---|
| 735 | } |
|---|
| 736 | }, 5000 ); |
|---|
| 737 | |
|---|
| 738 | function isRedirectionLoaded() { |
|---|
| 739 | return typeof redirection !== 'undefined'; |
|---|
| 740 | } |
|---|
| 741 | |
|---|
| 742 | function showError() { |
|---|
| 743 | var errorText = ""; |
|---|
| 744 | |
|---|
| 745 | if ( errors.length > 0 ) { |
|---|
| 746 | errorText = "```\n" + errors.join( ',' ) + "\n```\n\n"; |
|---|
| 747 | } |
|---|
| 748 | |
|---|
| 749 | resetAll(); |
|---|
| 750 | |
|---|
| 751 | if ( document.querySelector( '.react-loading' ) ) { |
|---|
| 752 | document.querySelector( '.react-loading' ).style.display = 'none'; |
|---|
| 753 | document.querySelector( '.react-error' ).style.display = 'block'; |
|---|
| 754 | |
|---|
| 755 | if ( typeof Redirectioni10n !== 'undefined' && Redirectioni10n ) { |
|---|
| 756 | document.querySelector( '.versions' ).innerHTML = Redirectioni10n.versions.replace( /\n/g, '<br />' ); |
|---|
| 757 | document.querySelector( '.react-error .button-primary' ).href += '&body=' + encodeURIComponent( errorText ) + encodeURIComponent( Redirectioni10n.versions ); |
|---|
| 758 | } |
|---|
| 759 | } else { |
|---|
| 760 | document.querySelector( '#react-ui' ).innerHTML = '<p>Sorry something went very wrong.</p>'; |
|---|
| 761 | } |
|---|
| 762 | } |
|---|
| 763 | |
|---|
| 764 | function resetAll() { |
|---|
| 765 | clearInterval( timer ); |
|---|
| 766 | window.onerror = prevError; |
|---|
| 767 | } |
|---|
| 768 | |
|---|
| 769 | window.onerror = function( error, url, line ) { |
|---|
| 770 | console.error( error ); |
|---|
| 771 | errors.push( error + ' ' + url + ' ' + line ); |
|---|
| 772 | }; |
|---|
| 773 | </script> |
|---|
| 774 | <?php |
|---|
| 775 | } |
|---|
| 776 | |
|---|
| 777 | /** |
|---|
| 778 | * Get the current plugin page. |
|---|
| 779 | * Uses $_GET['sub'] to determine the current page unless a page is supplied. |
|---|
| 780 | * |
|---|
| 781 | * @param string|false $page Current page. |
|---|
| 782 | * @return string|boolean Current page, or false. |
|---|
| 783 | */ |
|---|
| 784 | private function get_current_page( $page = false ) { |
|---|
| 785 | if ( $page === false ) { |
|---|
| 786 | $page = 'redirect'; |
|---|
| 787 | |
|---|
| 788 | if ( $this->get_query( 'sub' ) !== null ) { |
|---|
| 789 | $page = $this->get_query( 'sub' ); |
|---|
| 790 | } |
|---|
| 791 | } |
|---|
| 792 | |
|---|
| 793 | // Are we allowed to access this page? |
|---|
| 794 | if ( in_array( $page, Redirection_Capabilities::get_available_pages(), true ) ) { |
|---|
| 795 | return $page; |
|---|
| 796 | } |
|---|
| 797 | |
|---|
| 798 | return false; |
|---|
| 799 | } |
|---|
| 800 | |
|---|
| 801 | /** |
|---|
| 802 | * @return void |
|---|
| 803 | */ |
|---|
| 804 | private function inject() { |
|---|
| 805 | $page = $this->get_query( 'page' ); |
|---|
| 806 | $current_page = $this->get_current_page(); |
|---|
| 807 | |
|---|
| 808 | if ( $page !== null && $current_page !== 'redirect' && $page === 'redirection.php' ) { |
|---|
| 809 | $this->try_export_logs(); |
|---|
| 810 | $this->try_export_redirects(); |
|---|
| 811 | $this->try_export_rss(); |
|---|
| 812 | } |
|---|
| 813 | } |
|---|
| 814 | |
|---|
| 815 | /** |
|---|
| 816 | * @return void |
|---|
| 817 | */ |
|---|
| 818 | public function try_export_rss() { |
|---|
| 819 | $token = $this->get_query( 'token' ); |
|---|
| 820 | $sub = $this->get_query( 'sub' ); |
|---|
| 821 | |
|---|
| 822 | if ( $token !== null && $sub === 'rss' && Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_REDIRECT_MANAGE ) ) { |
|---|
| 823 | $options = Red_Options::get(); |
|---|
| 824 | |
|---|
| 825 | if ( $token === $options['token'] && $options['token'] !== '' ) { |
|---|
| 826 | $module = $this->get_query( 'module' ); |
|---|
| 827 | |
|---|
| 828 | $items = Red_Item::get_all_for_module( intval( $module, 10 ) ); |
|---|
| 829 | |
|---|
| 830 | $exporter = Red_FileIO::create( 'rss' ); |
|---|
| 831 | if ( $exporter !== false ) { |
|---|
| 832 | $exporter->force_download(); |
|---|
| 833 | |
|---|
| 834 | echo $exporter->get_data( $items, array() ); |
|---|
| 835 | die(); |
|---|
| 836 | } |
|---|
| 837 | } |
|---|
| 838 | } |
|---|
| 839 | } |
|---|
| 840 | |
|---|
| 841 | /** |
|---|
| 842 | * @return void |
|---|
| 843 | */ |
|---|
| 844 | private function try_export_logs() { |
|---|
| 845 | if ( Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE ) && isset( $_POST['export-csv'] ) && check_admin_referer( 'wp_rest' ) !== false ) { |
|---|
| 846 | if ( $this->get_current_page() === 'log' ) { |
|---|
| 847 | Red_Redirect_Log::export_to_csv(); |
|---|
| 848 | } elseif ( $this->get_current_page() === '404s' ) { |
|---|
| 849 | Red_404_Log::export_to_csv(); |
|---|
| 850 | } |
|---|
| 851 | |
|---|
| 852 | die(); |
|---|
| 853 | } |
|---|
| 854 | } |
|---|
| 855 | |
|---|
| 856 | /** |
|---|
| 857 | * @return void |
|---|
| 858 | */ |
|---|
| 859 | private function try_export_redirects() { |
|---|
| 860 | $sub = $this->get_query( 'sub' ); |
|---|
| 861 | if ( $sub !== 'io' ) { |
|---|
| 862 | return; |
|---|
| 863 | } |
|---|
| 864 | |
|---|
| 865 | $export = $this->get_query( 'export' ); |
|---|
| 866 | $exporter = $this->get_query( 'exporter' ); |
|---|
| 867 | |
|---|
| 868 | if ( Redirection_Capabilities::has_access( Redirection_Capabilities::CAP_IO_MANAGE ) && $export !== null && $exporter !== null && check_admin_referer( 'wp_rest' ) !== false ) { |
|---|
| 869 | $export = Red_FileIO::export( $export, $exporter ); |
|---|
| 870 | |
|---|
| 871 | if ( $export !== false ) { |
|---|
| 872 | $export['exporter']->force_download(); |
|---|
| 873 | |
|---|
| 874 | // This data is not displayed and will be downloaded to a file |
|---|
| 875 | echo str_replace( '&', '&', wp_kses( $export['data'], 'strip' ) ); |
|---|
| 876 | die(); |
|---|
| 877 | } |
|---|
| 878 | } |
|---|
| 879 | } |
|---|
| 880 | } |
|---|
| 881 | |
|---|
| 882 | register_activation_hook( REDIRECTION_FILE, array( 'Redirection_Admin', 'plugin_activated' ) ); |
|---|
| 883 | |
|---|
| 884 | // @phpstan-ignore return.void |
|---|
| 885 | add_action( 'init', array( 'Redirection_Admin', 'init' ) ); |
|---|
| 886 | |
|---|
| 887 | // This is causing a lot of problems with the REST API - disable qTranslate |
|---|
| 888 | add_filter( |
|---|
| 889 | 'qtranslate_language_detect_redirect', |
|---|
| 890 | function ( $lang, $url ) { |
|---|
| 891 | $url = Redirection_Request::get_request_url(); |
|---|
| 892 | |
|---|
| 893 | if ( strpos( $url, '/wp-json/' ) !== false || strpos( $url, '?rest_route' ) !== false ) { |
|---|
| 894 | return false; |
|---|
| 895 | } |
|---|
| 896 | |
|---|
| 897 | return $lang; |
|---|
| 898 | }, |
|---|
| 899 | 10, |
|---|
| 900 | 2 |
|---|
| 901 | ); |
|---|