f = $deps->functions !== null ? $deps->functions : abj_service('functions'); $this->redirectsRepo = $deps->redirectsRepo !== null ? $deps->redirectsRepo : abj_service('redirects_repository'); $this->logsRepo = $deps->logsRepo !== null ? $deps->logsRepo : abj_service('logs_repository'); $this->logger = $deps->logging !== null ? $deps->logging : abj_service('logging'); $this->optionsRepository = $deps->optionsRepository !== null ? $deps->optionsRepository : abj_service('options_repository'); $this->previousRequestCookieTracker = $deps->previousRequestCookieTracker !== null ? $deps->previousRequestCookieTracker : abj_service('previous_request_cookie_tracker'); } /** * @param string $requestedURL * @param string $reason * @param bool $useUserSpecified404 * @param array|null $optionsOverride * @return void */ function sendTo404Page(string $requestedURL, string $reason = '', bool $useUserSpecified404 = true, $optionsOverride = null): void { $options = (is_array($optionsOverride) ? $optionsOverride : $this->optionsRepository->getOptions()); $behavior = isset($options['dest404_behavior']) ? $options['dest404_behavior'] : ''; if ($behavior === 'suggest') { $systemPage = ABJ_404_Solution_SystemPage::getInstance(); if (!$systemPage->systemPageExists()) { $systemPage->handleSystemPageDeleted(); $options = $this->optionsRepository->getOptions(true); } } $dest404pageRaw = isset($options['dest404page']) ? $options['dest404page'] : null; $dest404page = is_string($dest404pageRaw) ? $dest404pageRaw : (ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED); if ($useUserSpecified404 && $this->thereIsAUserSpecified404Page($dest404page)) { $permalink = ABJ_404_Solution_PermalinkResolver::permalinkInfoToArray($dest404page, 0, null, $options); if (!in_array($permalink['status'], array('publish', 'published'))) { $msg = __("The user specified 404 page wasn't found. " . "Please update the user-specified 404 page on the Options page.", '404-solution'); $this->logger->infoMessage($msg); } else { $redirect = $this->redirectsRepo->getExistingRedirectForURL($requestedURL); $pType = is_scalar($permalink['type']) ? (string)$permalink['type'] : ''; $pId = is_scalar($permalink['id']) ? (string)$permalink['id'] : ''; $pLink = is_scalar($permalink['link']) ? (string)$permalink['link'] : ''; $defRedir = is_scalar($options['default_redirect']) ? (string)$options['default_redirect'] : '301'; if (!isset($redirect['id']) || $redirect['id'] == 0) { $this->redirectsRepo->setupRedirect(ABJ_404_Solution_RedirectSpec::create( $requestedURL, (string)ABJ404_STATUS_CAPTURED, $pType, $pId, $defRedir, 0 )); } $this->logsRepo->logRedirectHit(ABJ_404_Solution_RedirectHitLogEntry::create($requestedURL, $pLink, 'user specified 404 page. ' . $reason)); setcookie(ABJ404_PP . '_STATUS_404', 'true', abj_clock()->now() + 20, "/"); $this->forceRedirect(esc_url($pLink), (int)$defRedir); exit; } } if (@$options['capture_404'] == '1') { $redirect = $this->redirectsRepo->getExistingRedirectForURL($requestedURL); $defRedir2 = is_scalar($options['default_redirect']) ? (string)$options['default_redirect'] : '301'; if (!isset($redirect['id']) || $redirect['id'] == 0) { $this->redirectsRepo->setupRedirect(ABJ_404_Solution_RedirectSpec::create( $requestedURL, (string)ABJ404_STATUS_CAPTURED, (string)ABJ404_TYPE_404_DISPLAYED, (string)ABJ404_TYPE_404_DISPLAYED, $defRedir2, 0 )); } } else { $optionsJson = json_encode($options); $this->logger->debugMessage("No permalink found to redirect to. capture_404 is off. Requested URL: " . $requestedURL . " | Redirect: (none)" . " | is_single(): " . is_single() . " | " . "is_page(): " . is_page() . " | is_feed(): " . is_feed() . " | is_trackback(): " . is_trackback() . " | is_preview(): " . is_preview() . " | options: " . wp_kses_post(is_string($optionsJson) ? $optionsJson : '')); } } /** @param string|null $dest404page @return bool */ function thereIsAUserSpecified404Page($dest404page): bool { if ($dest404page == null) { return false; } $check1 = ($dest404page !== (ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED)); $check2 = ($dest404page !== (string)ABJ404_TYPE_404_DISPLAYED); return $check1 && $check2; } /** @return string */ function getCommentPartAndQueryPartOfRequest() { $requestUri = isset($_SERVER['REQUEST_URI']) && is_string($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; if ($requestUri !== '' && strpos($requestUri, '?') === false && strpos($requestUri, '/comment-page-') === false) { return ''; } $userRequest = ABJ_404_Solution_UserRequest::getInstance(); if ($userRequest === null) { return ''; } $queryString = $userRequest->getQueryString(); $queryParts = abj_service('query_string_helper')->removePageIDFromQueryString(is_string($queryString) ? $queryString : ''); $queryParts = ($queryParts == '') ? '' : '?' . $queryParts; $commentPart = $userRequest->getCommentPagePart(); return (is_string($commentPart) ? $commentPart : '') . $queryParts; } /** * @param string $location * @param int $status * @param int|string $type only 0 for sending to a 404 page * @param string $requestedURL * @param bool $isCustom404 * @return bool true if the user is sent to the default 404 page. */ function forceRedirect(string $location, int $status = 302, $type = -1, string $requestedURL = '', bool $isCustom404 = false): bool { if ($status === 410) { $this->sendGoneStatusResponse( 410, 'gone410.html', esc_html__('This content has been permanently removed.', '404-solution'), esc_html__('The page you requested no longer exists and has not been moved to a new location.', '404-solution') ); } if ($status === 451) { $this->sendGoneStatusResponse( 451, 'gone451.html', esc_html__('451 Unavailable For Legal Reasons', '404-solution'), esc_html__('This content is unavailable due to a legal demand.', '404-solution') ); } if ($status === 0 && $location !== '') { $this->sendMetaRefreshResponse($location); } $pluginLogic = abj_service('plugin_logic'); if (is_object($pluginLogic) && method_exists($pluginLogic, 'pageOrdering')) { $finalDestination = $pluginLogic->pageOrdering() ->buildFinalRedirectDestination($location, $requestedURL, $isCustom404); } else { $finalDestination = (string)$location . $this->getCommentPartAndQueryPartOfRequest(); } $loopSafeDestination = $this->avoidInfiniteRedirect($finalDestination, $location); if ($loopSafeDestination === false) { return false; } $finalDestination = $loopSafeDestination; if ($type == ABJ404_TYPE_404_DISPLAYED) { $this->sendTo404Page($requestedURL, '', false); return true; } if (is_object($this->previousRequestCookieTracker)) { $this->previousRequestCookieTracker->setCookieWithPreviousRequest(); } if (!headers_sent()) { return $this->sendHeaderRedirect($finalDestination, $status); } return $this->sendJavaScriptRedirectFallback($finalDestination); } private function sendGoneStatusResponse(int $status, string $templateFile, string $heading, string $message): void { status_header($status); $templatePath = dirname(__DIR__) . '/html/' . $templateFile; if (file_exists($templatePath)) { $siteName = function_exists('get_bloginfo') ? get_bloginfo('name') : ''; $siteUrl = function_exists('home_url') ? home_url('/') : '/'; $templateContent = file_get_contents($templatePath); if (is_string($templateContent)) { echo str_replace( array('{site_name}', '{site_url}', '{heading}', '{message}', '{back_home}'), array( esc_html($siteName), esc_url($siteUrl), $heading, $message, esc_html__('Back to home page', '404-solution'), ), $templateContent ); } } exit; } private function sendMetaRefreshResponse(string $location): void { status_header(200); $templatePath = dirname(__DIR__) . '/html/metaRefresh.html'; if (file_exists($templatePath)) { $templateContent = file_get_contents($templatePath); if (is_string($templateContent)) { echo str_replace( array('{url}', '{delay}', '{title}', '{message}'), array( esc_url($location), '0', esc_html__('Redirecting...', '404-solution'), esc_html__('You are being redirected. Click the link if not redirected automatically.', '404-solution'), ), $templateContent ); } } exit; } /** * @return string|false */ private function avoidInfiniteRedirect(string $finalDestination, string $location) { $previousRequest = is_object($this->previousRequestCookieTracker) ? $this->previousRequestCookieTracker->readCookieWithPreviousRqeuestShort() : ''; if (empty($previousRequest)) { return $finalDestination; } $finalDestNoHome = $this->redirectPathOnly($finalDestination); $locationNoHome = $this->redirectPathOnly($location); if ($previousRequest == $finalDestNoHome && $previousRequest != $locationNoHome) { $this->logger->infoMessage("Maybe avoided infite redirects to/from: " . $previousRequest); return $location; } if ($previousRequest == $finalDestination) { $this->logger->infoMessage("Avoided infite redirects to/from: " . $previousRequest); return false; } return $finalDestination; } private function redirectPathOnly(string $url): string { $schemePos = $this->f->strpos($url, '://'); $withoutHost = ($schemePos !== false) ? $this->f->substr($url, $schemePos + 3) : $url; $slashPos = $this->f->strpos($withoutHost, '/'); return ($slashPos !== false) ? $this->f->substr($withoutHost, $slashPos) : '/'; } private function sendHeaderRedirect(string $finalDestination, int $status): bool { if (function_exists('abj404_benchmark_emit_headers')) { try { abj404_benchmark_emit_headers(); } catch (Throwable $e) { $this->warnBenchmarkHeaderFailure($e); } } $allowedRedirectHostFilter = $this->addAllowedRedirectHostFilter($finalDestination); wp_safe_redirect($finalDestination, $status, ABJ404_NAME); if ($allowedRedirectHostFilter !== null) { remove_filter('allowed_redirect_hosts', $allowedRedirectHostFilter, 10); } if (!apply_filters('abj404_should_exit', true, array('source' => 'forceRedirect_header'))) { return false; } exit; } private function addAllowedRedirectHostFilter(string $finalDestination): ?callable { $destHost = parse_url($finalDestination, PHP_URL_HOST); if (!is_string($destHost) || $destHost === '') { return null; } $allowedRedirectHostFilter = static function($hosts) use ($destHost) { $normalizedHosts = array(); if (is_array($hosts)) { foreach ($hosts as $host) { if (is_scalar($host) || $host === null || (is_object($host) && method_exists($host, '__toString'))) { $normalizedHosts[] = (string)$host; } } } $normalizedHosts[] = strtolower($destHost); return array_values(array_unique($normalizedHosts)); }; add_filter('allowed_redirect_hosts', $allowedRedirectHostFilter, 10, 1); return $allowedRedirectHostFilter; } private function sendJavaScriptRedirectFallback(string $finalDestination): bool { if (function_exists('abj404_benchmark_emit_headers')) { abj404_benchmark_emit_headers(); } $template = ABJ_404_Solution_FileSystemService::readFileContents( dirname(__DIR__) . '/html/notFoundRedirectFallback.html', false ); echo $this->f->str_replace( array('{destination_json}', '{destination_url}', '{destination_label}'), array((string)wp_json_encode($finalDestination), esc_url($finalDestination), esc_html($finalDestination)), $template ); if (!apply_filters('abj404_should_exit', true, array('source' => 'forceRedirect_js'))) { return false; } exit; } private function warnBenchmarkHeaderFailure(Throwable $e): void { $message = 'abj404_benchmark_emit_headers failed: ' . $e->getMessage(); if (is_object($this->logger) && method_exists($this->logger, 'warn')) { $this->logger->warn($message); return; } abj404_logPhpFallback('service-resolution-fallback', $message); } }