PluginProbe
404 Solution / 4.1.19
404 Solution v4.1.19
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 / ViewTrait_Redirects.php

ViewTrait_Redirects.php in 404 Solution 4.1.19, at includes/ViewTrait_Redirects.php

523 lines 24.8 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 * Edit redirect page and destination option helpers.
9 */
10 trait ViewTrait_Redirects {
11
12 /** @return void */
13 function echoAdminEditRedirectPage() {
14
15 $options = $this->getOptionsWithDefaults();
16
17 // Compute source page early so we can use it in the back link
18 $source_page = $this->dao->getPostOrGetSanitize('source_page');
19 if ($source_page === '') {
20 $source_page = $this->dao->getPostOrGetSanitize('subpage');
21 }
22 if ($source_page === '' || $source_page == 'abj404_edit') {
23 $source_page = 'abj404_redirects';
24 }
25 $backUrl = '?page=' . ABJ404_PP . '&subpage=' . esc_attr($source_page);
26
27 // Modern page container
28 echo '<div class="abj404-edit-page">';
29 echo '<div class="abj404-edit-container">';
30
31 // Header row: title + back link
32 $isSimpleMode = $this->logic->getSettingsMode() === 'simple';
33 $isFromCaptured = ($source_page === 'abj404_captured');
34 echo '<div class="abj404-edit-page-header">';
35 if ($isSimpleMode && $isFromCaptured) {
36 echo '<h2>' . esc_html__('Create Redirect', '404-solution') . '</h2>';
37 echo '<a href="' . esc_url($backUrl) . '" class="abj404-back-link">&#8592; ' . esc_html__('Back to Captured 404s', '404-solution') . '</a>';
38 } else {
39 echo '<h2>' . esc_html__('Edit Redirect', '404-solution') . '</h2>';
40 echo '<a href="' . esc_url($backUrl) . '" class="abj404-back-link">&#8592; ' . esc_html__('Back to Redirects', '404-solution') . '</a>';
41 }
42 echo '</div>';
43
44 $link = wp_nonce_url("?page=" . ABJ404_PP . "&subpage=abj404_edit", "abj404editRedirect");
45
46 echo '<form method="POST" name="admin-edit-redirect" action="' . esc_attr($link) . '" onsubmit="return validateAddManualRedirectForm(event);">';
47 echo "<input type=\"hidden\" name=\"action\" value=\"editRedirect\">";
48
49 // Preserve source page for return navigation
50 echo "<input type=\"hidden\" name=\"source_page\" value=\"" . esc_attr($source_page) . "\">";
51
52 // Preserve table options so we can return to the exact same view
53 $filter = $this->dao->getPostOrGetSanitize('filter');
54 if ($filter !== '') {
55 echo "<input type=\"hidden\" name=\"source_filter\" value=\"" . esc_attr($filter) . "\">";
56 }
57 $orderby = $this->dao->getPostOrGetSanitize('orderby');
58 if ($orderby !== '') {
59 echo "<input type=\"hidden\" name=\"source_orderby\" value=\"" . esc_attr($orderby) . "\">";
60 }
61 $order = $this->dao->getPostOrGetSanitize('order');
62 if ($order !== '') {
63 echo "<input type=\"hidden\" name=\"source_order\" value=\"" . esc_attr($order) . "\">";
64 }
65 $paged = $this->dao->getPostOrGetSanitize('paged');
66 if ($paged !== '') {
67 echo "<input type=\"hidden\" name=\"source_paged\" value=\"" . esc_attr($paged) . "\">";
68 }
69
70 $recnum = null;
71 $recnums_multiple = null;
72 $startDate = '';
73 $endDate = '';
74 if (isset($_GET['id']) && $this->f->regexMatch('[0-9]+', $_GET['id'])) {
75 $this->logger->debugMessage("Edit redirect page. GET ID: " .
76 wp_kses_post((string)json_encode($_GET['id'])));
77 $recnum = absint($_GET['id']);
78
79 } else if (isset($_POST['id']) && $this->f->regexMatch('[0-9]+', $_POST['id'])) {
80 $this->logger->debugMessage("Edit redirect page. POST ID: " .
81 wp_kses_post((string)json_encode($_POST['id'])));
82 $recnum = absint($_POST['id']);
83
84 } else if ($this->dao->getPostOrGetSanitize('idnum') !== '') {
85 $recnums_multiple = array_map('absint', (array)$this->dao->getPostOrGetSanitize('idnum'));
86 $this->logger->debugMessage("Edit redirect page. ids_multiple: " .
87 wp_kses_post((string)json_encode($recnums_multiple)));
88
89 } else {
90 echo __('Error: No ID(s) found for edit request.', '404-solution');
91 $this->logger->debugMessage("No ID(s) found in GET or POST data for edit request.");
92 return;
93 }
94
95 // Decide whether we're editing one or multiple redirects.
96 // If we're editing only one then set the ID to that one value.
97 if ($recnum != null) {
98 $recnumAsArray = array();
99 $recnumAsArray[] = $recnum;
100 $redirects_multiple = $this->dao->getRedirectsByIDs($recnumAsArray);
101
102 if (empty($redirects_multiple)) {
103 echo "Error: Invalid ID Number! (id: " . esc_html((string)$recnum) . ")";
104 $this->logger->errorMessage("Error: Invalid ID Number! (id: " . esc_html((string)$recnum) . ")");
105 return;
106 }
107
108 /** @var array<string, mixed> $redirect */
109 $redirect = reset($redirects_multiple);
110 $row = ABJ_404_Solution_RedirectRow::fromRaw($redirect);
111 $isRegexChecked = '';
112 if ($row !== null && $row->isRegex()) {
113 $isRegexChecked = ' checked ';
114 }
115
116 $redirectId = $row !== null ? (string)$row->getId() : '';
117 $redirectUrl = $row !== null ? $row->getUrl() : '';
118 echo '<input type="hidden" name="id" value="' . esc_attr($redirectId) . '">';
119
120 // URL field (with optional "Matched by" note for auto-created redirects)
121 echo '<div class="abj404-form-group">';
122 echo '<label class="abj404-form-label" for="url">' . esc_html__('URL', '404-solution') . ' *</label>';
123 echo '<input type="text" id="url" name="url" class="abj404-form-input" value="' . esc_attr($redirectUrl) . '" required>';
124 $redirectEngine = $row !== null ? $row->getEngine() : '';
125 if ($redirectEngine !== '') {
126 $humanEngine = $this->humanizeEngineName($redirectEngine);
127 echo '<p class="abj404-form-help abj404-matched-by">' . esc_html__('Auto-matched by:', '404-solution') . ' ' . esc_html($humanEngine) . '</p>';
128 }
129 echo '</div>';
130
131 // Regex checkbox (hidden in Simple mode)
132 if (!$isSimpleMode) {
133 echo '<div class="abj404-form-group">';
134 echo '<div class="abj404-checkbox-group">';
135 echo '<input type="checkbox" name="is_regex_url" id="is_regex_url" class="abj404-checkbox-input" value="1" ' . $isRegexChecked . '>';
136 echo '<label for="is_regex_url" class="abj404-checkbox-label">' . esc_html__('Treat this URL as a regular expression', '404-solution') . '</label>';
137 echo ' <a href="#" class="abj404-regex-toggle" onclick="abj404ToggleRegexInfo(event)">' . esc_html__('(Explain)', '404-solution') . '</a>';
138 echo '</div>';
139 echo '<div class="abj404-regex-info" style="display: none;">';
140 echo '<p>' . esc_html__('When checked, the text is treated as a regular expression. Note that including a bad regular expression or one that takes too long will break your website. So please use caution and test them elsewhere before trying them here. If you don\'t know what you\'re doing please don\'t use this option (as it\'s not necessary for the functioning of the plugin).', '404-solution') . '</p>';
141 echo '<p><strong>' . esc_html__('Example:', '404-solution') . '</strong> <code>/events/(.+)</code></p>';
142 echo '<p>' . esc_html__('/events/(.+) will match any URL that begins with /events/ and redirect to the specified page. Since a capture group is used, you can use a $1 replacement in the destination string of an external URL.', '404-solution') . '</p>';
143 echo '</div>';
144 echo '</div>';
145 }
146
147 // Scheduled redirect dates (rendered inside Advanced Options in echoEditRedirect)
148 $startTs = $row !== null ? $row->getStartTs() : 0;
149 $endTs = $row !== null ? $row->getEndTs() : 0;
150 $startDate = $startTs > 0 ? date('Y-m-d', $startTs) : '';
151 $endDate = $endTs > 0 ? date('Y-m-d', $endTs) : '';
152
153 } else if ($recnums_multiple != null) {
154 $redirects_multiple = $this->dao->getRedirectsByIDs($recnums_multiple);
155 if ($redirects_multiple == null) {
156 echo "Error: Invalid ID Numbers! (ids: " . esc_html(implode(',', $recnums_multiple)) . ")";
157 $this->logger->debugMessage("Error: Invalid ID Numbers! (ids: " .
158 esc_html(implode(',', $recnums_multiple)) . ")");
159 return;
160 }
161
162 echo '<input type="hidden" name="ids_multiple" value="' . esc_attr(implode(',', $recnums_multiple)) . '">';
163
164 // Bulk URL list
165 echo '<div class="abj404-form-group">';
166 echo '<label class="abj404-form-label">' . esc_html__('URLs to redirect', '404-solution') . ' (' . count($redirects_multiple) . ')</label>';
167 echo '<div class="abj404-url-list">';
168 echo '<ul>';
169 foreach ($redirects_multiple as $bulkRedirect) {
170 /** @var array<string, mixed> $bulkRedirect */
171 $bulkUrl = is_string($bulkRedirect['url'] ?? '') ? (string)($bulkRedirect['url'] ?? '') : '';
172 echo '<li><code>' . esc_html($bulkUrl) . '</code></li>';
173 }
174 echo '</ul>';
175 echo '</div>';
176 echo '</div>';
177
178 // here we set the variable to the first value returned because it's used to set default values
179 // in the form data.
180 $redirect = reset($redirects_multiple);
181
182 } else {
183 $idsText = '';
184 echo "Error: Invalid ID Number(s) specified! (id: " . esc_html((string)$recnum) . ", ids: " . esc_html($idsText) . ")";
185 $this->logger->debugMessage("Error: Invalid ID Number(s) specified! (id: " . esc_html((string)$recnum) .
186 ", ids: " . esc_html($idsText) . ")");
187 return;
188 }
189
190 $final = "";
191 $pageIDAndType = "";
192 $redirectType = $redirect['type'] ?? null;
193 $redirectFinalDestRaw = $redirect['final_dest'] ?? 0;
194 $redirectFinalDest = is_scalar($redirectFinalDestRaw) ? (string)$redirectFinalDestRaw : '0';
195 if ($redirectType == ABJ404_TYPE_EXTERNAL) {
196 $final = $redirectFinalDest;
197 $pageIDAndType = ABJ404_TYPE_EXTERNAL . "|" . ABJ404_TYPE_EXTERNAL;
198
199 } else if ($redirectFinalDest != 0) {
200 // if a destination has been specified then let's fill it in.
201 $pageIDAndType = $redirectFinalDest . "|" . $redirectType;
202
203 } else if ($redirectType == ABJ404_TYPE_404_DISPLAYED) {
204 $pageIDAndType = ABJ404_TYPE_404_DISPLAYED . "|" . ABJ404_TYPE_404_DISPLAYED;
205 }
206
207 $rawCode = $redirect['code'] ?? '';
208 if ($rawCode == "") {
209 $rawDefault = $options['default_redirect'] ?? '301';
210 $codeSelected = is_string($rawDefault) ? $rawDefault : '301';
211 } else {
212 $codeSelected = is_string($rawCode) ? $rawCode : '301';
213 }
214
215 // Try to find a suggested destination for captured URLs.
216 // Any captured URL should get a suggestion lookup — the plugin may have auto-assigned
217 // a destination via the spell-checker, but the user hasn't chosen one yet.
218 $suggestion = null;
219 $isSimpleMode = $this->logic->getSettingsMode() === 'simple';
220 if ($isFromCaptured && !empty($redirectUrl)) {
221 $suggestion = $this->getSuggestedDestination($redirectUrl, $options);
222 }
223
224 // Render suggested destination block (if available)
225 if ($suggestion !== null) {
226 echo '<div class="abj404-suggestion-block" id="abj404-suggestion-block">';
227 echo '<div class="abj404-suggestion-label">' . esc_html__('Suggested destination', '404-solution') . '</div>';
228 echo '<div class="abj404-suggestion-content">';
229 echo '<strong>' . esc_html($suggestion['title']) . '</strong>';
230 if (!empty($suggestion['type_label'])) {
231 echo '<span class="abj404-suggestion-type">' . esc_html($suggestion['type_label']) . '</span>';
232 }
233 echo '<span class="abj404-score-badge abj404-score-' . ($suggestion['score'] >= 75 ? 'high' : ($suggestion['score'] >= 50 ? 'medium' : 'low')) . '">'
234 . esc_html($suggestion['score'] . '%') . ' ' . esc_html__('match', '404-solution') . '</span>';
235 echo '</div>';
236 echo '<div class="abj404-suggestion-actions">';
237 echo '<button type="button" class="button button-primary" onclick="abj404AcceptSuggestion(this)"'
238 . ' data-page-title="' . esc_attr($suggestion['title']) . '"'
239 . ' data-page-id-type="' . esc_attr($suggestion['id_and_type']) . '">'
240 . esc_html__('Accept Suggestion', '404-solution') . '</button>';
241 echo '<button type="button" class="button" onclick="abj404ShowManualPicker()">'
242 . esc_html__('Pick a Different Page', '404-solution') . '</button>';
243 echo '</div>';
244 echo '</div>';
245 }
246
247 // When creating from captured URLs, clear the redirect_to field so the
248 // placeholder text is visible. The suggestion block (if shown) handles
249 // presenting the best match separately.
250 if ($isFromCaptured) {
251 $pageTitle = '';
252 $pageIDAndType = '';
253 } else {
254 $pageTitle = $this->logic->getPageTitleFromIDAndType($pageIDAndType, $redirectFinalDest);
255 }
256 $html = ABJ_404_Solution_Functions::readFileContents(__DIR__ .
257 "/html/addManualRedirectPageSearchDropdown.html");
258 $html = $this->f->str_replace('{redirect_to_label}', __('Redirect to', '404-solution'), $html);
259 $html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_EMPTY}',
260 __('(Type a page name or an external URL)', '404-solution'), $html);
261 $html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_PAGE}',
262 __('(A page has been selected.)', '404-solution'), $html);
263 $html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_CUSTOM_STRING}',
264 __('(A custom string has been entered.)', '404-solution'), $html);
265 $html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_URL}',
266 __('(An external URL will be used.)', '404-solution'), $html);
267 $html = $this->f->str_replace('{REDIRECT_TO_USER_FIELD_WARNING}', '', $html);
268 $html = $this->f->str_replace('{redirectPageTitle}', esc_attr($pageTitle), $html);
269 $html = $this->f->str_replace('{pageIDAndType}', esc_attr($pageIDAndType), $html);
270 $html = $this->f->str_replace('{data-url}',
271 "admin-ajax.php?action=echoRedirectToPages&includeDefault404Page=true&includeSpecial=true&nonce=" . wp_create_nonce('abj404_ajax'), $html);
272 $html = $this->f->doNormalReplacements($html);
273
274 // In Simple mode with a suggestion, hide the manual picker initially
275 $manualPickerHiddenClass = ($suggestion !== null && $isSimpleMode) ? ' abj404-hidden' : '';
276 echo '<div class="abj404-form-group abj404-autocomplete-wrapper' . $manualPickerHiddenClass . '" id="abj404-manual-picker">';
277 echo $html;
278 echo '</div>';
279
280 $this->echoEditRedirect($final, $codeSelected, __('Update Redirect', '404-solution'), $source_page, $filter, $orderby, $order, $startDate, $endDate);
281
282 echo '</form>';
283 echo '</div>'; // end abj404-edit-container
284 echo '</div>'; // end abj404-edit-page
285 }
286
287 /**
288 * @param string $dest
289 * @param array<int, object> $rows
290 * @return string
291 */
292 function echoRedirectDestinationOptionsOthers($dest, $rows) {
293 $content = array();
294
295 $rowCounter = 0;
296 $currentPostType = '';
297
298 foreach ($rows as $row) {
299 $rowCounter++;
300 /** @var object{id: int, post_type: string, depth?: int} $row */
301 $id = $row->id;
302 $theTitle = get_the_title($id);
303 $thisval = $id . "|" . ABJ404_TYPE_POST;
304
305 $selected = "";
306 if ($thisval == $dest) {
307 $selected = " selected";
308 }
309
310 abj_service('request_context')->debug_info = 'Before row: ' . $rowCounter . ', Title: ' . $theTitle .
311 ', Post type: ' . $row->post_type;
312
313 if ($row->post_type != $currentPostType) {
314 if ($currentPostType != '') {
315 $content[] = "\n" . '</optgroup>' . "\n";
316 }
317
318 $content[] = "\n" . '<optgroup label="' . __(ucwords($row->post_type), '404-solution') . '">' . "\n";
319 $currentPostType = $row->post_type;
320 }
321
322 // this is split in this ridiculous way to help me figure out how to resolve a memory issue.
323 // (https://wordpress.org/support/topic/options-tab-is-not-loading/)
324 $content[] = "\n <option value=\"";
325 $content[] = esc_attr($thisval);
326 $content[] = "\"";
327 $content[] = $selected;
328 $content[] = ">";
329
330 // insert some spaces for child pages.
331 $depth = property_exists($row, 'depth') ? intval($row->depth) : 0;
332 for ($i = 0; $i < $depth; $i++) {
333 $content[] = "&nbsp;&nbsp;&nbsp;";
334 }
335
336 $content[] = __(ucwords($row->post_type), '404-solution');
337 $content[] = ": ";
338 $content[] = esc_html($theTitle);
339 $content[] = "</option>";
340
341 abj_service('request_context')->debug_info = 'After row: ' . $rowCounter . ', Title: ' . $theTitle .
342 ', Post type: ' . $row->post_type;
343 }
344
345 $content[] = "\n" . '</optgroup>' . "\n";
346
347
348 abj_service('request_context')->debug_info = 'Cleared after building redirect destination page list.';
349
350 return implode('', $content);
351 }
352
353 /**
354 * @param string $dest
355 * @return string
356 */
357 function echoRedirectDestinationOptionsCatsTags($dest) {
358 $content = "";
359 $content .= "\n" . '<optgroup label="Categories">' . "\n";
360
361 $customTagsEtc = array();
362
363 // categories ---------------------------------------------
364 $cats = $this->dao->getPublishedCategories();
365 foreach ($cats as $cat) {
366 /** @var \WP_Term $cat */
367 $taxonomy = $cat->taxonomy;
368 if ($taxonomy != 'category') {
369 continue;
370 }
371
372 $id = $cat->term_id;
373 $theTitle = $cat->name;
374 $thisval = $id . "|" . ABJ404_TYPE_CAT;
375
376 $selected = "";
377 if ($thisval == $dest) {
378 $selected = " selected";
379 }
380 $content .= "\n<option value=\"" . esc_attr($thisval) . "\"" . $selected . ">" . __('Category', '404-solution') . ": " . $theTitle . "</option>";
381 }
382 $content .= "\n" . '</optgroup>' . "\n";
383 /** @var array<int, object{taxonomy: string, name?: string}> $cats */
384 $customTagsEtc = $this->logic->getMapOfCustomCategories($cats);
385
386 // tags ---------------------------------------------
387 $content .= "\n" . '<optgroup label="Tags">' . "\n";
388 $tags = $this->dao->getPublishedTags();
389 foreach ($tags as $tag) {
390 /** @var \WP_Term $tag */
391 $id = $tag->term_id;
392 $theTitle = $tag->name;
393 $thisval = $id . "|" . ABJ404_TYPE_TAG;
394
395 $selected = "";
396 if ($thisval == $dest) {
397 $selected = " selected";
398 }
399 $content .= "\n<option value=\"" . esc_attr($thisval) . "\"" . $selected . ">" . __('Tag', '404-solution') . ": " . $theTitle . "</option>";
400 }
401 $content .= "\n" . '</optgroup>' . "\n";
402
403 // custom ---------------------------------------------
404 foreach ($customTagsEtc as $taxonomy => $catRow) {
405 $content .= "\n" . '<optgroup label="' . esc_html($taxonomy) . '">' . "\n";
406
407 foreach ($catRow as $cat) {
408 /** @var \WP_Term $cat */
409 $id = $cat->term_id;
410 $theTitle = $cat->name;
411 $thisval = $id . "|" . ABJ404_TYPE_CAT;
412
413 $selected = "";
414 if ($thisval == $dest) {
415 $selected = " selected";
416 }
417 $content .= "\n<option value=\"" . esc_attr($thisval) . "\"" . $selected . ">" . __('Custom', '404-solution') . ": " . $theTitle . "</option>";
418 }
419
420 $content .= "\n" . '</optgroup>' . "\n";
421 }
422
423 return $content;
424 }
425
426 /**
427 * Convert a raw engine class name to a human-readable label.
428 *
429 * Examples:
430 * TitleMatchingEngine → "Title Matching"
431 * SpellingMatchingEngine → "Spelling Matching"
432 * CategoryTagMatchingEngine → "Category/Tag Matching"
433 * UrlFixEngine → "URL Fix"
434 * ArchiveFallbackEngine → "Archive Fallback"
435 *
436 * @param string $rawName
437 * @return string
438 */
439 private function humanizeEngineName(string $rawName): string {
440 // Strip full namespace prefix if stored with it.
441 $name = preg_replace('/^ABJ_404_Solution_/', '', $rawName);
442 if (!is_string($name)) {
443 $name = $rawName;
444 }
445 // Strip "MatchingEngine" or bare "Engine" suffix.
446 $name = (string)preg_replace('/MatchingEngine$/', ' Matching', $name);
447 $name = (string)preg_replace('/Engine$/', '', $name);
448 // Insert a space before each upper-case letter that follows a lower-case letter
449 // (e.g. CategoryTag → Category Tag).
450 $name = (string)preg_replace('/(?<=[a-z])([A-Z])/', ' $1', $name);
451 $name = trim($name);
452 // Fix known abbreviations.
453 $name = str_replace(array('Url ', 'Url'), array('URL ', 'URL'), $name);
454 // Fix Category/Tag — appears as "Category Tag Matching", make the separator a slash.
455 $name = str_replace('Category Tag', 'Category/Tag', $name);
456 return $name !== '' ? $name : $rawName;
457 }
458
459 /**
460 * Get the best suggested destination for a captured URL using the spell-checker.
461 *
462 * @param string $url The captured 404 URL.
463 * @param array<string, mixed> $options Plugin options.
464 * @return array{title: string, score: int, id_and_type: string, type_label: string}|null The best match, or null if none found.
465 */
466 private function getSuggestedDestination(string $url, array $options): ?array {
467 try {
468 $spellChecker = abj_service('spell_checker');
469 $permalinksPacket = $spellChecker->findMatchingPosts($url, '1', '1');
470 $permalinks = is_array($permalinksPacket[0] ?? null) ? $permalinksPacket[0] : array();
471 $rowType = is_string($permalinksPacket[1] ?? '') ? (string)($permalinksPacket[1] ?? '') : '';
472
473 if (empty($permalinks)) {
474 return null;
475 }
476
477 // Take the top match
478 $topIdAndType = array_key_first($permalinks);
479 $topScore = intval($permalinks[$topIdAndType]);
480
481 // Only suggest if score is at least 25%
482 if ($topScore < 25) {
483 return null;
484 }
485
486 $permalink = ABJ_404_Solution_Functions::permalinkInfoToArray(
487 $topIdAndType, $topScore, $rowType, $options
488 );
489
490 $title = is_string($permalink['title'] ?? '') ? (string)($permalink['title'] ?? '') : '';
491 if ($title === '' || ($permalink['status'] ?? '') === 'trash') {
492 return null;
493 }
494
495 // Determine a human-readable type label
496 $typeParts = explode('|', is_string($topIdAndType) ? $topIdAndType : '');
497 $typeInt = isset($typeParts[1]) && is_numeric($typeParts[1]) ? (int)$typeParts[1] : -1;
498 $typeLabel = '';
499 if ($typeInt === ABJ404_TYPE_POST) {
500 $postType = get_post_type((int)$typeParts[0]);
501 $typeLabel = ($postType === 'page') ? __('Page', '404-solution') : __('Post', '404-solution');
502 } elseif ($typeInt === ABJ404_TYPE_CAT) {
503 $typeLabel = __('Category', '404-solution');
504 } elseif ($typeInt === ABJ404_TYPE_TAG) {
505 $typeLabel = __('Tag', '404-solution');
506 } elseif ($typeInt === ABJ404_TYPE_HOME) {
507 $typeLabel = __('Home', '404-solution');
508 }
509
510 return array(
511 'title' => $title,
512 'score' => $topScore,
513 'id_and_type' => is_string($topIdAndType) ? $topIdAndType : '',
514 'type_label' => $typeLabel,
515 );
516 } catch (\Throwable $e) {
517 // Spell-checker may fail on some URLs — degrade gracefully
518 return null;
519 }
520 }
521
522 }
523