| @@ -1,38 +1,191 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | +/** | |
| 4 | + * WordPress redirect module. | |
| 5 | + * | |
| 6 | + * Provides PHP controlled redirects and monitoring and is the core of the front-end redirection. | |
| 7 | + */ | |
| 3 | 8 | class WordPress_Module extends Red_Module { |
| 9 | + /** | |
| 10 | + * @var integer | |
| 11 | + */ | |
| 4 | 12 | const MODULE_ID = 1; |
| 5 | 13 | |
| 14 | + /** | |
| 15 | + * Can we log? | |
| 16 | + * | |
| 17 | + * @var boolean | |
| 18 | + */ | |
| 19 | + private $can_log = true; | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * The target redirect URL | |
| 23 | + * | |
| 24 | + * @var string|false | |
| 25 | + */ | |
| 26 | + private $redirect_url = false; | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * The target redirect code | |
| 30 | + * | |
| 31 | + * @var integer | |
| 32 | + */ | |
| 33 | + private $redirect_code = 0; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * Copy of redirects that match the requested URL | |
| 37 | + * | |
| 38 | + * @var Red_Item[] | |
| 39 | + */ | |
| 40 | + private $redirects = []; | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * Matched redirect | |
| 44 | + * | |
| 45 | + * @var Red_Item|false | |
| 46 | + */ | |
| 6 | 47 | private $matched = false; |
| 7 | - private $can_log = true; | |
| 8 | 48 | |
| 49 | + /** | |
| 50 | + * Return the module ID | |
| 51 | + * | |
| 52 | + * @return integer | |
| 53 | + */ | |
| 9 | 54 | public function get_id() { |
| 10 | 55 | return self::MODULE_ID; |
| 11 | 56 | } |
| 12 | 57 | |
| 58 | + /** | |
| 59 | + * Return the module name | |
| 60 | + * | |
| 61 | + * @return string | |
| 62 | + */ | |
| 13 | 63 | public function get_name() { |
| 14 | 64 | return 'WordPress'; |
| 15 | 65 | } |
| 16 | 66 | |
| 67 | + /** | |
| 68 | + * Start the module. Hooks any filters and actions | |
| 69 | + * | |
| 70 | + * @return void | |
| 71 | + */ | |
| 17 | 72 | public function start() { |
| 73 | + // Only run redirect rules if we're not disabled | |
| 74 | + if ( ! red_is_disabled() ) { | |
| 75 | + // Canonical site settings - https, www, relocate, and aliases | |
| 76 | + add_action( 'init', [ $this, 'canonical_domain' ] ); | |
| 77 | + | |
| 78 | + // The main redirect loop | |
| 79 | + add_action( 'init', [ $this, 'init' ] ); | |
| 80 | + | |
| 81 | + // Send site HTTP headers as well as 410 error codes | |
| 82 | + add_action( 'send_headers', [ $this, 'send_headers' ] ); | |
| 83 | + | |
| 84 | + // Redirect HTTP headers and server-specific overrides | |
| 85 | + add_filter( 'wp_redirect', [ $this, 'wp_redirect' ], 1, 2 ); | |
| 86 | + | |
| 87 | + // Allow permalinks to be redirected | |
| 88 | + add_filter( 'pre_handle_404', [ $this, 'pre_handle_404' ], 10, 2 ); | |
| 89 | + | |
| 90 | + // Cache support | |
| 91 | + add_action( 'redirection_matched', [ $this, 'cache_redirects' ], 10, 3 ); | |
| 92 | + add_action( 'redirection_last', [ $this, 'cache_unmatched_redirects' ], 10, 3 ); | |
| 93 | + } | |
| 94 | + | |
| 18 | 95 | // Setup the various filters and actions that allow Redirection to happen |
| 19 | - add_action( 'init', array( $this, 'init' ) ); | |
| 20 | - add_action( 'init', array( $this, 'force_https' ) ); | |
| 21 | - add_action( 'send_headers', array( $this, 'send_headers' ) ); | |
| 22 | - add_filter( 'wp_redirect', array( $this, 'wp_redirect' ), 1, 2 ); | |
| 23 | - add_action( 'redirection_visit', array( $this, 'redirection_visit' ), 10, 3 ); | |
| 24 | - add_action( 'redirection_do_nothing', array( $this, 'redirection_do_nothing' ) ); | |
| 25 | - add_filter( 'redirect_canonical', array( $this, 'redirect_canonical' ), 10, 2 ); | |
| 26 | - add_action( 'template_redirect', array( $this, 'template_redirect' ) ); | |
| 96 | + add_action( 'redirection_visit', [ $this, 'redirection_visit' ], 10, 3 ); | |
| 97 | + add_action( 'redirection_do_nothing', [ $this, 'redirection_do_nothing' ] ); | |
| 27 | 98 | |
| 28 | - // Remove WordPress 2.3 redirection | |
| 29 | - remove_action( 'template_redirect', 'wp_old_slug_redirect' ); | |
| 99 | + // Prevent WordPress overriding a canonical redirect | |
| 100 | + add_filter( 'redirect_canonical', [ $this, 'redirect_canonical' ], 10, 2 ); | |
| 101 | + | |
| 102 | + // Log 404s and perform 'URL and WordPress page type' redirects | |
| 103 | + add_action( 'template_redirect', [ $this, 'template_redirect' ] ); | |
| 104 | + | |
| 105 | + // Back-compat for < database 4.2 | |
| 106 | + add_filter( 'redirection_404_data', [ $this, 'log_back_compat' ] ); | |
| 107 | + add_filter( 'redirection_log_data', [ $this, 'log_back_compat' ] ); | |
| 108 | + | |
| 109 | + // Record the redirect agent | |
| 110 | + add_filter( 'x_redirect_by', [ $this, 'record_redirect_by' ], 90 ); | |
| 30 | 111 | } |
| 31 | 112 | |
| 32 | 113 | /** |
| 114 | + * Called after no redirect is matched. This allows us to cache a negative result/ | |
| 115 | + * | |
| 116 | + * @param String $url URL. | |
| 117 | + * @param WordPress_Module $wp This. | |
| 118 | + * @param array $redirects Array of redirects. | |
| 119 | + * @return void | |
| 120 | + */ | |
| 121 | + public function cache_unmatched_redirects( $url, $wp, $redirects ) { | |
| 122 | + if ( $this->matched ) { | |
| 123 | + return; | |
| 124 | + } | |
| 125 | + | |
| 126 | + $this->cache_redirects( $url, $this->matched, $redirects ); | |
| 127 | + } | |
| 128 | + | |
| 129 | + /** | |
| 130 | + * Called when a redirect is matched. This allows us to cache a positive result. | |
| 131 | + * | |
| 132 | + * @param String $url URL. | |
| 133 | + * @param Red_Item|false $matched_redirect Matched redirect. | |
| 134 | + * @param array $redirects Array of redirects. | |
| 135 | + * @return void | |
| 136 | + */ | |
| 137 | + public function cache_redirects( $url, $matched_redirect, $redirects ) { | |
| 138 | + $cache = Redirect_Cache::init(); | |
| 139 | + $cache->set( $url, $matched_redirect, $redirects ); | |
| 140 | + } | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * If we have a 404 then check for any permalink migrations | |
| 144 | + * | |
| 145 | + * @param boolean $result Return result. | |
| 146 | + * @param WP_Query $query WP_Query object. | |
| 147 | + * @return boolean | |
| 148 | + */ | |
| 149 | + public function pre_handle_404( $result, WP_Query $query ) { | |
| 150 | + $options = red_get_options(); | |
| 151 | + | |
| 152 | + if ( is_404() && count( $options['permalinks'] ) > 0 ) { | |
| 153 | + include_once dirname( dirname( __FILE__ ) ) . '/models/permalinks.php'; | |
| 154 | + | |
| 155 | + $permalinks = new Red_Permalinks( $options['permalinks'] ); | |
| 156 | + $permalinks->migrate( $query ); | |
| 157 | + } | |
| 158 | + | |
| 159 | + return $result; | |
| 160 | + } | |
| 161 | + | |
| 162 | + /** | |
| 163 | + * Back-compatability for Redirection databases older than 4.2. Prevents errors from storing data that has no DB column | |
| 164 | + * | |
| 165 | + * @param array $insert Data to log. | |
| 166 | + * @return array | |
| 167 | + */ | |
| 168 | + public function log_back_compat( $insert ) { | |
| 169 | + // Remove columns not supported in older versions | |
| 170 | + $status = new Red_Database_Status(); | |
| 171 | + | |
| 172 | + if ( ! $status->does_support( '4.2' ) ) { | |
| 173 | + foreach ( [ 'request_data', 'request_method', 'http_code', 'domain', 'redirect_by' ] as $ignore ) { | |
| 174 | + unset( $insert[ $ignore ] ); | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 178 | + return $insert; | |
| 179 | + } | |
| 180 | + | |
| 181 | + /** | |
| 33 | 182 | * This ensures that a matched URL is not overriddden by WordPress, if the URL happens to be a WordPress URL of some kind |
| 34 | 183 | * For example: /?author=1 will be redirected to /author/name unless this returns false |
| 184 | + * | |
| 185 | + * @param String $redirect_url The redirected URL. | |
| 186 | + * @param String $requested_url The requested URL. | |
| 187 | + * @return String|false | |
| 35 | 188 | */ |
| 36 | 189 | public function redirect_canonical( $redirect_url, $requested_url ) { |
| 37 | 190 | if ( $this->matched ) { |
| 38 | 191 | return false; |
| @@ -40,82 +193,157 @@ | ||
| 40 | 193 | |
| 41 | 194 | return $redirect_url; |
| 42 | 195 | } |
| 43 | 196 | |
| 197 | + /** | |
| 198 | + * WordPress 'template_redirect' hook. Used to check for 404s | |
| 199 | + * | |
| 200 | + * @return void | |
| 201 | + */ | |
| 44 | 202 | public function template_redirect() { |
| 45 | 203 | if ( ! is_404() || $this->matched ) { |
| 46 | 204 | return; |
| 47 | 205 | } |
| 48 | 206 | |
| 49 | - if ( $this->match_404_type() ) { | |
| 50 | - // Don't log an intentionally redirected 404 | |
| 207 | + // We are on a 404. Check if we have a 'URL and page type' match in any of the matched redirects. | |
| 208 | + if ( $this->is_url_and_page_type() ) { | |
| 209 | + // Don't log an intentionally redirected 404 as part of the 'url and page type' | |
| 51 | 210 | return; |
| 52 | 211 | } |
| 53 | 212 | |
| 54 | 213 | $options = red_get_options(); |
| 55 | 214 | |
| 56 | - if ( isset( $options['expire_404'] ) && $options['expire_404'] >= 0 && apply_filters( 'redirection_log_404', $this->can_log ) ) { | |
| 57 | - RE_404::create( Redirection_Request::get_request_url(), Redirection_Request::get_user_agent(), Redirection_Request::get_ip(), Redirection_Request::get_referrer() ); | |
| 215 | + if ( isset( $options['expire_404'] ) && $options['expire_404'] >= 0 && $this->can_log() ) { | |
| 216 | + $details = [ | |
| 217 | + 'agent' => Redirection_Request::get_user_agent(), | |
| 218 | + 'referrer' => Redirection_Request::get_referrer(), | |
| 219 | + 'request_method' => Redirection_Request::get_request_method(), | |
| 220 | + 'http_code' => 404, | |
| 221 | + ]; | |
| 222 | + | |
| 223 | + if ( $options['log_header'] ) { | |
| 224 | + $details['request_data'] = [ | |
| 225 | + 'headers' => Redirection_Request::get_request_headers(), | |
| 226 | + ]; | |
| 227 | + } | |
| 228 | + | |
| 229 | + Red_404_Log::create( Redirection_Request::get_server(), Redirection_Request::get_request_url(), Redirection_Request::get_ip(), $details ); | |
| 58 | 230 | } |
| 59 | 231 | } |
| 60 | 232 | |
| 61 | - private function match_404_type() { | |
| 62 | - if ( ! property_exists( $this, 'redirects' ) || count( $this->redirects ) === 0 ) { | |
| 63 | - return false; | |
| 64 | - } | |
| 233 | + /** | |
| 234 | + * Return `true` if any of the matched redirects is a 'url and page type', `false` otherwise | |
| 235 | + * | |
| 236 | + * @return boolean | |
| 237 | + */ | |
| 238 | + private function is_url_and_page_type() { | |
| 239 | + $page_types = array_values( array_filter( $this->redirects, function( Red_Item $redirect ) { | |
| 240 | + return $redirect->match && $redirect->match->get_type() === 'page'; | |
| 241 | + } ) ); | |
| 65 | 242 | |
| 66 | - $page_type = array_values( array_filter( $this->redirects, array( $this, 'only_404' ) ) ); | |
| 243 | + if ( count( $page_types ) > 0 ) { | |
| 244 | + $request = new Red_Url_Request( Redirection_Request::get_request_url() ); | |
| 245 | + $action = $page_types[0]->get_match( $request->get_decoded_url(), $request->get_original_url() ); | |
| 246 | + if ( $action ) { | |
| 247 | + $action->run(); | |
| 248 | + } | |
| 67 | 249 | |
| 68 | - if ( count( $page_type ) > 0 ) { | |
| 69 | - $url = apply_filters( 'redirection_url_source', Redirection_Request::get_request_url() ); | |
| 70 | - $first = $page_type[0]; | |
| 71 | - return $first->matches( $url ); | |
| 250 | + return true; | |
| 72 | 251 | } |
| 73 | 252 | |
| 74 | 253 | return false; |
| 75 | 254 | } |
| 76 | 255 | |
| 77 | - private function only_404( $redirect ) { | |
| 78 | - return $redirect->match->get_type() === 'page'; | |
| 79 | - } | |
| 80 | - | |
| 81 | - // Return true to stop further processing of the 'do nothing' | |
| 256 | + /** | |
| 257 | + * Called by a 'do nothing' action. Return true to stop further processing of the 'do nothing' | |
| 258 | + * | |
| 259 | + * @return boolean | |
| 260 | + */ | |
| 82 | 261 | public function redirection_do_nothing() { |
| 83 | 262 | $this->can_log = false; |
| 84 | 263 | return true; |
| 85 | 264 | } |
| 86 | 265 | |
| 266 | + /** | |
| 267 | + * Action fired when a redirect is performed, and used to log the data | |
| 268 | + * | |
| 269 | + * @param Red_Item $redirect The redirect. | |
| 270 | + * @param String $url The source URL. | |
| 271 | + * @param String $target The target URL. | |
| 272 | + * @return void | |
| 273 | + */ | |
| 87 | 274 | public function redirection_visit( $redirect, $url, $target ) { |
| 88 | 275 | $redirect->visit( $url, $target ); |
| 89 | 276 | } |
| 90 | 277 | |
| 91 | - public function force_https() { | |
| 278 | + /** | |
| 279 | + * Get canonical target | |
| 280 | + * | |
| 281 | + * @return string|false | |
| 282 | + */ | |
| 283 | + public function get_canonical_target() { | |
| 92 | 284 | $options = red_get_options(); |
| 285 | + $canonical = new Redirection_Canonical( $options['https'], $options['preferred_domain'], $options['aliases'], get_home_url() ); | |
| 93 | 286 | |
| 94 | - if ( $options['https'] && ! is_ssl() ) { | |
| 95 | - $target = rtrim( Redirection_Request::get_server_name(), '/' ) . esc_url_raw( Redirection_Request::get_request_url() ); | |
| 96 | - wp_safe_redirect( 'https://' . $target, 301 ); | |
| 287 | + // Relocate domain? | |
| 288 | + if ( $options['relocate'] ) { | |
| 289 | + return $canonical->relocate_request( $options['relocate'], Redirection_Request::get_server_name(), Redirection_Request::get_request_url() ); | |
| 290 | + } | |
| 291 | + | |
| 292 | + // Force HTTPS or www | |
| 293 | + return $canonical->get_redirect( Redirection_Request::get_request_server_name(), Redirection_Request::get_request_url() ); | |
| 294 | + } | |
| 295 | + | |
| 296 | + /** | |
| 297 | + * Checks for canonical domain requests | |
| 298 | + * | |
| 299 | + * @return void | |
| 300 | + */ | |
| 301 | + public function canonical_domain() { | |
| 302 | + $target = $this->get_canonical_target(); | |
| 303 | + | |
| 304 | + if ( $target ) { | |
| 305 | + // phpcs:ignore | |
| 306 | + wp_redirect( $target, 301, 'redirection' ); | |
| 97 | 307 | die(); |
| 98 | 308 | } |
| 99 | 309 | } |
| 100 | 310 | |
| 311 | + /** | |
| 312 | + * Redirection 'main loop'. Checks the currently requested URL against the database and perform a redirect, if necessary. | |
| 313 | + * | |
| 314 | + * @return void | |
| 315 | + */ | |
| 101 | 316 | public function init() { |
| 102 | - $url = apply_filters( 'redirection_url_source', Redirection_Request::get_request_url() ); | |
| 317 | + if ( $this->matched ) { | |
| 318 | + return; | |
| 319 | + } | |
| 103 | 320 | |
| 321 | + $request = new Red_Url_Request( Redirection_Request::get_request_url() ); | |
| 322 | + | |
| 104 | 323 | // Make sure we don't try and redirect something essential |
| 105 | - if ( $url && ! $this->protected_url( $url ) && $this->matched === false ) { | |
| 106 | - do_action( 'redirection_first', $url, $this ); | |
| 324 | + if ( $request->is_valid() && ! $request->is_protected_url() ) { | |
| 325 | + do_action( 'redirection_first', $request->get_decoded_url(), $this ); | |
| 107 | 326 | |
| 108 | - $redirects = Red_Item::get_for_url( $url ); | |
| 327 | + // Get all redirects that match the URL | |
| 328 | + $redirects = Red_Item::get_for_url( $request->get_decoded_url() ); | |
| 109 | 329 | |
| 330 | + // Redirects will be ordered by position. Run through the list until one fires | |
| 110 | 331 | foreach ( (array) $redirects as $item ) { |
| 111 | - if ( $item->matches( $url ) ) { | |
| 332 | + $action = $item->get_match( $request->get_decoded_url(), $request->get_original_url() ); | |
| 333 | + | |
| 334 | + if ( $action ) { | |
| 112 | 335 | $this->matched = $item; |
| 336 | + | |
| 337 | + do_action( 'redirection_matched', $request->get_decoded_url(), $item, $redirects ); | |
| 338 | + | |
| 339 | + $action->run(); | |
| 113 | 340 | break; |
| 114 | 341 | } |
| 115 | 342 | } |
| 116 | 343 | |
| 117 | - do_action( 'redirection_last', $url, $this ); | |
| 344 | + // We will only get here if there is no match (check $this->matched) or the action does not result in redirecting away | |
| 345 | + do_action( 'redirection_last', $request->get_decoded_url(), $this, $redirects ); | |
| 118 | 346 | |
| 119 | 347 | if ( ! $this->matched ) { |
| 120 | 348 | // Keep them for later |
| 121 | 349 | $this->redirects = $redirects; |
| @@ -123,24 +351,14 @@ | ||
| 123 | 351 | } |
| 124 | 352 | } |
| 125 | 353 | |
| 126 | 354 | /** |
| 127 | - * Protect certain URLs from being redirected. Note we don't need to protect wp-admin, as this code doesn't run there | |
| 355 | + * Fix for incorrect headers sent when using FastCGI/IIS | |
| 356 | + * | |
| 357 | + * @param String $status HTTP status line. | |
| 358 | + * @return String | |
| 128 | 359 | */ |
| 129 | - private function protected_url( $url ) { | |
| 130 | - $rest = wp_parse_url( red_get_rest_api() ); | |
| 131 | - $rest_api = $rest['path'] . ( isset( $rest['query'] ) ? '?' . $rest['query'] : '' ); | |
| 132 | - | |
| 133 | - if ( substr( $url, 0, strlen( $rest_api ) ) === $rest_api ) { | |
| 134 | - // Never redirect the REST API | |
| 135 | - return true; | |
| 136 | - } | |
| 137 | - | |
| 138 | - return false; | |
| 139 | - } | |
| 140 | - | |
| 141 | 360 | public function status_header( $status ) { |
| 142 | - // Fix for incorrect headers sent when using FastCGI/IIS | |
| 143 | 361 | if ( substr( php_sapi_name(), 0, 3 ) === 'cgi' ) { |
| 144 | 362 | return str_replace( 'HTTP/1.1', 'Status:', $status ); |
| 145 | 363 | } |
| 146 | 364 | |
| @@ -146,28 +364,58 @@ | ||
| 146 | 364 | |
| 147 | 365 | return $status; |
| 148 | 366 | } |
| 149 | 367 | |
| 368 | + /** | |
| 369 | + * Add any custom HTTP headers to the response. | |
| 370 | + * | |
| 371 | + * @param array $obj Some object. | |
| 372 | + * @return void | |
| 373 | + */ | |
| 150 | 374 | public function send_headers( $obj ) { |
| 151 | - if ( ! empty( $this->matched ) && $this->matched->match->action_code === '410' ) { | |
| 152 | - add_filter( 'status_header', array( $this, 'set_header_410' ) ); | |
| 375 | + if ( ! empty( $this->matched ) && $this->matched->action && $this->matched->action->get_code() === 410 ) { | |
| 376 | + add_filter( 'status_header', [ $this, 'set_header_410' ] ); | |
| 153 | 377 | } |
| 378 | + | |
| 379 | + // Add any custom headers | |
| 380 | + $options = red_get_options(); | |
| 381 | + $headers = new Red_Http_Headers( $options['headers'] ); | |
| 382 | + $headers->run( $headers->get_site_headers() ); | |
| 154 | 383 | } |
| 155 | 384 | |
| 385 | + /** | |
| 386 | + * Add support for a 410 response. | |
| 387 | + * | |
| 388 | + * @return String | |
| 389 | + */ | |
| 156 | 390 | public function set_header_410() { |
| 157 | 391 | return 'HTTP/1.1 410 Gone'; |
| 158 | 392 | } |
| 159 | 393 | |
| 160 | - public function wp_redirect( $url, $status ) { | |
| 161 | - global $wp_version, $is_IIS; | |
| 394 | + /** | |
| 395 | + * IIS fix. Don't know if this is still needed | |
| 396 | + * | |
| 397 | + * @param String $url URL. | |
| 398 | + * @return void | |
| 399 | + */ | |
| 400 | + private function iis_fix( $url ) { | |
| 401 | + global $is_IIS; | |
| 162 | 402 | |
| 163 | 403 | if ( $is_IIS ) { |
| 164 | 404 | header( "Refresh: 0;url=$url" ); |
| 165 | - return $url; | |
| 166 | 405 | } |
| 406 | + } | |
| 167 | 407 | |
| 408 | + /** | |
| 409 | + * Don't know if this is still needed | |
| 410 | + * | |
| 411 | + * @param String $url URL. | |
| 412 | + * @param integer $status HTTP status code. | |
| 413 | + * @return void | |
| 414 | + */ | |
| 415 | + private function cgi_fix( $url, $status ) { | |
| 168 | 416 | if ( $status === 301 && php_sapi_name() === 'cgi-fcgi' ) { |
| 169 | - $servers_to_check = array( 'lighttpd', 'nginx' ); | |
| 417 | + $servers_to_check = [ 'lighttpd', 'nginx' ]; | |
| 170 | 418 | |
| 171 | 419 | foreach ( $servers_to_check as $name ) { |
| 172 | 420 | if ( isset( $_SERVER['SERVER_SOFTWARE'] ) && stripos( $_SERVER['SERVER_SOFTWARE'], $name ) !== false ) { |
| 173 | 421 | status_header( $status ); |
| @@ -175,9 +423,91 @@ | ||
| 175 | 423 | exit( 0 ); |
| 176 | 424 | } |
| 177 | 425 | } |
| 178 | 426 | } |
| 427 | + } | |
| 179 | 428 | |
| 429 | + /** | |
| 430 | + * Get a 'source' for a redirect by digging through the backtrace. | |
| 431 | + * | |
| 432 | + * @return string[] | |
| 433 | + */ | |
| 434 | + private function get_redirect_source() { | |
| 435 | + $ignore = [ | |
| 436 | + 'WP_Hook', | |
| 437 | + 'template-loader.php', | |
| 438 | + 'wp-blog-header.php', | |
| 439 | + ]; | |
| 440 | + | |
| 441 | + // phpcs:ignore | |
| 442 | + $source = wp_debug_backtrace_summary( null, 5, false ); | |
| 443 | + | |
| 444 | + return array_filter( $source, function( $item ) use ( $ignore ) { | |
| 445 | + foreach ( $ignore as $ignore_item ) { | |
| 446 | + if ( strpos( $item, $ignore_item ) !== false ) { | |
| 447 | + return false; | |
| 448 | + } | |
| 449 | + } | |
| 450 | + | |
| 451 | + return true; | |
| 452 | + } ); | |
| 453 | + } | |
| 454 | + | |
| 455 | + /** | |
| 456 | + * Record a redirect. | |
| 457 | + * | |
| 458 | + * @param String $agent Redirect agent. | |
| 459 | + * @return string | |
| 460 | + */ | |
| 461 | + public function record_redirect_by( $agent ) { | |
| 462 | + // Have we already redirected with Redirection? | |
| 463 | + if ( $this->matched || $agent === 'redirection' ) { | |
| 464 | + return $agent; | |
| 465 | + } | |
| 466 | + | |
| 467 | + $options = red_get_options(); | |
| 468 | + | |
| 469 | + if ( ! $options['log_external'] ) { | |
| 470 | + return $agent; | |
| 471 | + } | |
| 472 | + | |
| 473 | + $details = [ | |
| 474 | + 'target' => $this->redirect_url, | |
| 475 | + 'agent' => Redirection_Request::get_user_agent(), | |
| 476 | + 'referrer' => Redirection_Request::get_referrer(), | |
| 477 | + 'request_method' => Redirection_Request::get_request_method(), | |
| 478 | + 'redirect_by' => $agent ? $agent : 'wordpress', | |
| 479 | + 'http_code' => $this->redirect_code, | |
| 480 | + 'request_data' => [ | |
| 481 | + 'source' => array_values( $this->get_redirect_source() ), | |
| 482 | + ], | |
| 483 | + ]; | |
| 484 | + | |
| 485 | + if ( $options['log_header'] ) { | |
| 486 | + $details['request_data']['headers'] = Redirection_Request::get_request_headers(); | |
| 487 | + } | |
| 488 | + | |
| 489 | + Red_Redirect_Log::create( Redirection_Request::get_server(), Redirection_Request::get_request_url(), Redirection_Request::get_ip(), $details ); | |
| 490 | + | |
| 491 | + return $agent; | |
| 492 | + } | |
| 493 | + | |
| 494 | + /** | |
| 495 | + * Perform any pre-redirect processing, such as logging and header fixing. | |
| 496 | + * | |
| 497 | + * @param String $url Target URL. | |
| 498 | + * @param integer $status HTTP status. | |
| 499 | + * @return string | |
| 500 | + */ | |
| 501 | + public function wp_redirect( $url, $status = 302 ) { | |
| 502 | + global $wp_version; | |
| 503 | + | |
| 504 | + $this->redirect_url = $url; | |
| 505 | + $this->redirect_code = $status; | |
| 506 | + | |
| 507 | + $this->iis_fix( $url ); | |
| 508 | + $this->cgi_fix( $url, $status ); | |
| 509 | + | |
| 180 | 510 | if ( intval( $status, 10 ) === 307 ) { |
| 181 | 511 | status_header( $status ); |
| 182 | 512 | nocache_headers(); |
| 183 | 513 | return $url; |
| @@ -183,8 +513,10 @@ | ||
| 183 | 513 | return $url; |
| 184 | 514 | } |
| 185 | 515 | |
| 186 | 516 | $options = red_get_options(); |
| 517 | + $headers = new Red_Http_Headers( $options['headers'] ); | |
| 518 | + $headers->run( $headers->get_redirect_headers() ); | |
| 187 | 519 | |
| 188 | 520 | // Do we need to set the cache header? |
| 189 | 521 | if ( ! headers_sent() && isset( $options['redirect_cache'] ) && $options['redirect_cache'] !== 0 && intval( $status, 10 ) === 301 ) { |
| 190 | 522 | if ( $options['redirect_cache'] === -1 ) { |
| @@ -200,18 +532,24 @@ | ||
| 200 | 532 | status_header( $status ); |
| 201 | 533 | return $url; |
| 202 | 534 | } |
| 203 | 535 | |
| 204 | - public function update( array $data ) { | |
| 205 | - return false; | |
| 536 | + /** | |
| 537 | + * Reset the module. Used for unit tests | |
| 538 | + * | |
| 539 | + * @param Red_Item|false $matched Set the `matched` var. | |
| 540 | + * @return void | |
| 541 | + */ | |
| 542 | + public function reset( $matched = false ) { | |
| 543 | + $this->can_log = true; | |
| 544 | + $this->matched = $matched; | |
| 206 | 545 | } |
| 207 | 546 | |
| 208 | - protected function load( $options ) { | |
| 209 | - } | |
| 210 | - | |
| 211 | - protected function flush_module() { | |
| 212 | - } | |
| 213 | - | |
| 214 | - public function reset() { | |
| 215 | - $this->can_log = true; | |
| 547 | + /** | |
| 548 | + * Can we log a redirect? | |
| 549 | + * | |
| 550 | + * @return boolean | |
| 551 | + */ | |
| 552 | + public function can_log() { | |
| 553 | + return apply_filters( 'redirection_log_404', $this->can_log ); | |
| 216 | 554 | } |
| 217 | 555 | } |