PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / services / NotFoundResponseService.php

NotFoundResponseService.php in 404 Solution 4.3.0, at includes/services/NotFoundResponseService.php

329 lines 15.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Dispatches unresolved frontend requests to the configured 404 response path.
9 */
10 class ABJ_404_Solution_NotFoundResponseService {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $f;
14
15 /** @var ABJ_404_Solution_RedirectsRepositoryInterface */
16 private $redirectsRepo;
17
18 /** @var ABJ_404_Solution_LogsRepositoryInterface */
19 private $logsRepo;
20
21 /** @var ABJ_404_Solution_Logging */
22 private $logger;
23
24 /** @var ABJ_404_Solution_PluginLogicOptionsResolver */
25 private $optionsRepository;
26
27 /** @var ABJ_404_Solution_PreviousRequestCookieTracker */
28 private $previousRequestCookieTracker;
29
30 /**
31 * @param ABJ_404_Solution_NotFoundResponseDependencies|null $deps
32 */
33 function __construct(?ABJ_404_Solution_NotFoundResponseDependencies $deps = null) {
34 $deps = $deps ?? new ABJ_404_Solution_NotFoundResponseDependencies();
35 $this->f = $deps->functions !== null ? $deps->functions : abj_service('functions');
36 $this->redirectsRepo = $deps->redirectsRepo !== null ? $deps->redirectsRepo : abj_service('redirects_repository');
37 $this->logsRepo = $deps->logsRepo !== null ? $deps->logsRepo : abj_service('logs_repository');
38 $this->logger = $deps->logging !== null ? $deps->logging : abj_service('logging');
39 $this->optionsRepository = $deps->optionsRepository !== null ? $deps->optionsRepository : abj_service('options_repository');
40 $this->previousRequestCookieTracker = $deps->previousRequestCookieTracker !== null
41 ? $deps->previousRequestCookieTracker
42 : abj_service('previous_request_cookie_tracker');
43 }
44
45 /**
46 * @param string $requestedURL
47 * @param string $reason
48 * @param bool $useUserSpecified404
49 * @param array<string, mixed>|null $optionsOverride
50 * @return void
51 */
52 function sendTo404Page(string $requestedURL, string $reason = '', bool $useUserSpecified404 = true, $optionsOverride = null): void {
53 $options = (is_array($optionsOverride) ? $optionsOverride : $this->optionsRepository->getOptions());
54
55 $behavior = isset($options['dest404_behavior']) ? $options['dest404_behavior'] : '';
56 if ($behavior === 'suggest') {
57 $systemPage = ABJ_404_Solution_SystemPage::getInstance();
58 if (!$systemPage->systemPageExists()) {
59 $systemPage->handleSystemPageDeleted();
60 $options = $this->optionsRepository->getOptions(true);
61 }
62 }
63
64 $dest404pageRaw = isset($options['dest404page']) ? $options['dest404page'] : null;
65 $dest404page = is_string($dest404pageRaw) ? $dest404pageRaw : (ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED);
66
67 if ($useUserSpecified404 && $this->thereIsAUserSpecified404Page($dest404page)) {
68 $permalink = ABJ_404_Solution_PermalinkResolver::permalinkInfoToArray($dest404page, 0, null, $options);
69
70 if (!in_array($permalink['status'], array('publish', 'published'))) {
71 $msg = __("The user specified 404 page wasn't found. " .
72 "Please update the user-specified 404 page on the Options page.",
73 '404-solution');
74 $this->logger->infoMessage($msg);
75
76 } else {
77 $redirect = $this->redirectsRepo->getExistingRedirectForURL($requestedURL);
78 $pType = is_scalar($permalink['type']) ? (string)$permalink['type'] : '';
79 $pId = is_scalar($permalink['id']) ? (string)$permalink['id'] : '';
80 $pLink = is_scalar($permalink['link']) ? (string)$permalink['link'] : '';
81 $defRedir = is_scalar($options['default_redirect']) ? (string)$options['default_redirect'] : '301';
82 if (!isset($redirect['id']) || $redirect['id'] == 0) {
83 $this->redirectsRepo->setupRedirect(ABJ_404_Solution_RedirectSpec::create(
84 $requestedURL, (string)ABJ404_STATUS_CAPTURED, $pType, $pId, $defRedir, 0
85 ));
86 }
87
88 $this->logsRepo->logRedirectHit(ABJ_404_Solution_RedirectHitLogEntry::create($requestedURL, $pLink, 'user specified 404 page. ' . $reason));
89
90 setcookie(ABJ404_PP . '_STATUS_404', 'true', abj_clock()->now() + 20, "/");
91
92 $this->forceRedirect(esc_url($pLink), (int)$defRedir);
93 exit;
94 }
95 }
96
97 if (@$options['capture_404'] == '1') {
98 $redirect = $this->redirectsRepo->getExistingRedirectForURL($requestedURL);
99 $defRedir2 = is_scalar($options['default_redirect']) ? (string)$options['default_redirect'] : '301';
100 if (!isset($redirect['id']) || $redirect['id'] == 0) {
101 $this->redirectsRepo->setupRedirect(ABJ_404_Solution_RedirectSpec::create(
102 $requestedURL, (string)ABJ404_STATUS_CAPTURED, (string)ABJ404_TYPE_404_DISPLAYED, (string)ABJ404_TYPE_404_DISPLAYED, $defRedir2, 0
103 ));
104 }
105 } else {
106 $optionsJson = json_encode($options);
107 $this->logger->debugMessage("No permalink found to redirect to. capture_404 is off. Requested URL: " . $requestedURL .
108 " | Redirect: (none)" . " | is_single(): " . is_single() . " | " .
109 "is_page(): " . is_page() . " | is_feed(): " . is_feed() . " | is_trackback(): " .
110 is_trackback() . " | is_preview(): " . is_preview() . " | options: " . wp_kses_post(is_string($optionsJson) ? $optionsJson : ''));
111 }
112 }
113
114 /** @param string|null $dest404page @return bool */
115 function thereIsAUserSpecified404Page($dest404page): bool {
116 if ($dest404page == null) {
117 return false;
118 }
119 $check1 = ($dest404page !== (ABJ404_TYPE_404_DISPLAYED . '|' . ABJ404_TYPE_404_DISPLAYED));
120 $check2 = ($dest404page !== (string)ABJ404_TYPE_404_DISPLAYED);
121 return $check1 && $check2;
122 }
123
124 /** @return string */
125 function getCommentPartAndQueryPartOfRequest() {
126 $requestUri = isset($_SERVER['REQUEST_URI']) && is_string($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
127 if ($requestUri !== '' &&
128 strpos($requestUri, '?') === false &&
129 strpos($requestUri, '/comment-page-') === false) {
130 return '';
131 }
132
133 $userRequest = ABJ_404_Solution_UserRequest::getInstance();
134 if ($userRequest === null) {
135 return '';
136 }
137 $queryString = $userRequest->getQueryString();
138 $queryParts = abj_service('query_string_helper')->removePageIDFromQueryString(is_string($queryString) ? $queryString : '');
139 $queryParts = ($queryParts == '') ? '' : '?' . $queryParts;
140 $commentPart = $userRequest->getCommentPagePart();
141 return (is_string($commentPart) ? $commentPart : '') . $queryParts;
142 }
143
144 /**
145 * @param string $location
146 * @param int $status
147 * @param int|string $type only 0 for sending to a 404 page
148 * @param string $requestedURL
149 * @param bool $isCustom404
150 * @return bool true if the user is sent to the default 404 page.
151 */
152 function forceRedirect(string $location, int $status = 302, $type = -1, string $requestedURL = '', bool $isCustom404 = false): bool {
153 if ($status === 410) {
154 status_header(410);
155 $templatePath = dirname(__DIR__) . '/html/gone410.html';
156 if (file_exists($templatePath)) {
157 $siteName = function_exists('get_bloginfo') ? get_bloginfo('name') : '';
158 $siteUrl = function_exists('home_url') ? home_url('/') : '/';
159 $templateContent = file_get_contents($templatePath);
160 if (is_string($templateContent)) {
161 $templateContent = str_replace(
162 array('{site_name}', '{site_url}', '{heading}', '{message}', '{back_home}'),
163 array(
164 esc_html($siteName),
165 esc_url($siteUrl),
166 esc_html__('This content has been permanently removed.', '404-solution'),
167 esc_html__('The page you requested no longer exists and has not been moved to a new location.', '404-solution'),
168 esc_html__('Back to home page', '404-solution'),
169 ),
170 $templateContent
171 );
172 echo $templateContent;
173 }
174 }
175 exit;
176 }
177
178 if ($status === 451) {
179 status_header(451);
180 $templatePath = dirname(__DIR__) . '/html/gone451.html';
181 if (file_exists($templatePath)) {
182 $siteName = function_exists('get_bloginfo') ? get_bloginfo('name') : '';
183 $siteUrl = function_exists('home_url') ? home_url('/') : '/';
184 $templateContent = file_get_contents($templatePath);
185 if (is_string($templateContent)) {
186 $templateContent = str_replace(
187 array('{site_name}', '{site_url}', '{heading}', '{message}', '{back_home}'),
188 array(
189 esc_html($siteName),
190 esc_url($siteUrl),
191 esc_html__('451 Unavailable For Legal Reasons', '404-solution'),
192 esc_html__('This content is unavailable due to a legal demand.', '404-solution'),
193 esc_html__('Back to home page', '404-solution'),
194 ),
195 $templateContent
196 );
197 echo $templateContent;
198 }
199 }
200 exit;
201 }
202
203 if ($status === 0 && $location !== '') {
204 status_header(200);
205 $templatePath = dirname(__DIR__) . '/html/metaRefresh.html';
206 if (file_exists($templatePath)) {
207 $templateContent = file_get_contents($templatePath);
208 if (is_string($templateContent)) {
209 $templateContent = str_replace(
210 array('{url}', '{delay}', '{title}', '{message}'),
211 array(
212 esc_url($location),
213 '0',
214 esc_html__('Redirecting...', '404-solution'),
215 esc_html__('You are being redirected. Click the link if not redirected automatically.', '404-solution'),
216 ),
217 $templateContent
218 );
219 echo $templateContent;
220 }
221 }
222 exit;
223 }
224
225 $pluginLogic = abj_service('plugin_logic');
226 if (is_object($pluginLogic) && method_exists($pluginLogic, 'pageOrdering')) {
227 $finalDestination = $pluginLogic->pageOrdering()
228 ->buildFinalRedirectDestination($location, $requestedURL, $isCustom404);
229 } else {
230 $finalDestination = (string)$location . $this->getCommentPartAndQueryPartOfRequest();
231 }
232
233 $previousRequest = is_object($this->previousRequestCookieTracker)
234 ? $this->previousRequestCookieTracker->readCookieWithPreviousRqeuestShort()
235 : '';
236 $schemePos = $this->f->strpos($finalDestination, '://');
237 $finalDestNoHome = ($schemePos !== false)
238 ? $this->f->substr($finalDestination, $schemePos + 3) : $finalDestination;
239 $slashPos = $this->f->strpos($finalDestNoHome, '/');
240 $finalDestNoHome = ($slashPos !== false)
241 ? $this->f->substr($finalDestNoHome, $slashPos) : '/';
242
243 $schemePos2 = $this->f->strpos($location, '://');
244 $locationNoHome = ($schemePos2 !== false)
245 ? $this->f->substr($location, $schemePos2 + 3) : $location;
246 $slashPos2 = $this->f->strpos($locationNoHome, '/');
247 $locationNoHome = ($slashPos2 !== false)
248 ? $this->f->substr($locationNoHome, $slashPos2) : '/';
249 if (!empty($previousRequest)) {
250 if ($previousRequest == $finalDestNoHome && $previousRequest != $locationNoHome) {
251 $this->logger->infoMessage("Maybe avoided infite redirects to/from: " . $previousRequest);
252 $finalDestination = $location;
253
254 } else if ($previousRequest == $finalDestination) {
255 $this->logger->infoMessage("Avoided infite redirects to/from: " . $previousRequest);
256 return false;
257 }
258 }
259
260 if ($type == ABJ404_TYPE_404_DISPLAYED) {
261 $this->sendTo404Page($requestedURL, '', false);
262 return true;
263 }
264
265 if (is_object($this->previousRequestCookieTracker)) {
266 $this->previousRequestCookieTracker->setCookieWithPreviousRequest();
267 }
268 if (!headers_sent()) {
269 if (function_exists('abj404_benchmark_emit_headers')) {
270 try {
271 abj404_benchmark_emit_headers();
272 } catch (Throwable $e) {
273 $this->warnBenchmarkHeaderFailure($e);
274 }
275 }
276 $useSafe = false;
277 if (function_exists('wp_safe_redirect')) {
278 $destHost = parse_url($finalDestination, PHP_URL_HOST);
279 if ($destHost === null || $destHost === false || $destHost === '') {
280 $useSafe = true;
281 } else {
282 $homeHost = parse_url(home_url(), PHP_URL_HOST);
283 if (is_string($homeHost) && $homeHost !== '' && strtolower($homeHost) === strtolower($destHost)) {
284 $useSafe = true;
285 }
286 }
287 }
288
289 if ($useSafe) {
290 wp_safe_redirect($finalDestination, $status, ABJ404_NAME);
291 } else {
292 wp_redirect($finalDestination, $status, ABJ404_NAME);
293 }
294 if (!apply_filters('abj404_should_exit', true, array('source' => 'forceRedirect_header'))) {
295 return false;
296 }
297 exit;
298 }
299
300 if (function_exists('abj404_benchmark_emit_headers')) {
301 abj404_benchmark_emit_headers();
302 }
303 $template = ABJ_404_Solution_FileSystemService::readFileContents(
304 dirname(__DIR__) . '/html/notFoundRedirectFallback.html',
305 false
306 );
307 echo $this->f->str_replace(
308 array('{destination_json}', '{destination_url}', '{destination_label}'),
309 array((string)wp_json_encode($finalDestination), esc_url($finalDestination), esc_html($finalDestination)),
310 $template
311 );
312 if (!apply_filters('abj404_should_exit', true, array('source' => 'forceRedirect_js'))) {
313 return false;
314 }
315 exit;
316 }
317
318 private function warnBenchmarkHeaderFailure(Throwable $e): void {
319 $message = 'abj404_benchmark_emit_headers failed: ' . $e->getMessage();
320 if (is_object($this->logger) && method_exists($this->logger, 'warn')) {
321 $this->logger->warn($message);
322 return;
323 }
324
325 abj404_logPhpFallback('service-resolution-fallback', $message);
326 }
327
328 }
329