PluginProbe
404 Solution / trunk
404 Solution vtrunk
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 / engine / ArchiveFallbackEngine.php

ArchiveFallbackEngine.php in 404 Solution trunk, at includes/engine/ArchiveFallbackEngine.php

129 lines 4.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 * Fallback matching engine that redirects to post type archive pages.
9 *
10 * When all other engines fail, if the URL path starts with a known post type slug
11 * that has an archive, redirect to that post type's archive page.
12 * E.g., /products/nonexistent-item → /products/
13 */
14 class ABJ_404_Solution_ArchiveFallbackEngine implements ABJ_404_Solution_MatchingEngine {
15
16 /** @var ABJ_404_Solution_Functions */
17 private $f;
18
19 /** @var ABJ_404_Solution_Logging */
20 private $logger;
21
22 /**
23 * @param ABJ_404_Solution_Functions $f
24 * @param ABJ_404_Solution_Logging $logger
25 */
26 public function __construct(
27 ABJ_404_Solution_Functions $f,
28 ABJ_404_Solution_Logging $logger
29 ) {
30 $this->f = $f;
31 $this->logger = $logger;
32 }
33
34 /** @return string */
35 public function getName(): string {
36 return __('archive fallback', '404-solution');
37 }
38
39 /** @param ABJ_404_Solution_MatchRequest $request */
40 public function shouldRun(ABJ_404_Solution_MatchRequest $request): bool {
41 $slug = $request->getUrlSlugOnly();
42
43 if ($slug === '') {
44 return false;
45 }
46
47 $options = $request->getOptions();
48 $autoRedirects = isset($options['auto_redirects']) ? $options['auto_redirects'] : '0';
49
50 if ($autoRedirects !== '1') {
51 return false;
52 }
53
54 return true;
55 }
56
57 /** @param ABJ_404_Solution_MatchRequest $request */
58 public function match(ABJ_404_Solution_MatchRequest $request): ?ABJ_404_Solution_MatchResult {
59 $slug = $request->getUrlSlugOnly();
60
61 // Get the first path segment
62 $segments = array_values(array_filter(explode('/', $slug), function ($s) {
63 return $s !== '';
64 }));
65
66 if (empty($segments)) {
67 return null;
68 }
69
70 $firstSegment = $this->f->strtolower($segments[0]);
71
72 // Get post types with archives
73 /** @var array<string, object> $postTypes */
74 $postTypes = get_post_types(['has_archive' => true], 'objects');
75
76 if (empty($postTypes)) {
77 $this->logger->debugMessage("Archive fallback engine: no post types with archives");
78 return null;
79 }
80
81 foreach ($postTypes as $postTypeName => $postTypeObj) {
82 $hasArchive = isset($postTypeObj->has_archive) ? $postTypeObj->has_archive : false;
83
84 // has_archive can be true (use post type name as slug) or a string (custom archive slug)
85 $archiveSlug = '';
86 if (is_string($hasArchive) && $hasArchive !== '') {
87 $archiveSlug = $this->f->strtolower($hasArchive);
88 } elseif ($hasArchive === true) {
89 $archiveSlug = $this->f->strtolower((string)$postTypeName);
90 }
91
92 if ($archiveSlug === '') {
93 continue;
94 }
95
96 if ($firstSegment !== $archiveSlug && $firstSegment !== $this->f->strtolower((string)$postTypeName)) {
97 continue;
98 }
99
100 // Found a matching post type — get its archive URL
101 $archiveUrl = get_post_type_archive_link((string)$postTypeName);
102
103 if (!is_string($archiveUrl) || $archiveUrl === '') {
104 continue;
105 }
106
107 $label = isset($postTypeObj->label) && is_string($postTypeObj->label)
108 ? $postTypeObj->label : (string)$postTypeName;
109
110 $this->logger->debugMessage("Archive fallback engine: matched post type '" .
111 $postTypeName . "' archive for segment '" . $firstSegment . "'");
112
113 return new ABJ_404_Solution_MatchResult(
114 '0',
115 (string)ABJ404_TYPE_POST,
116 $archiveUrl,
117 $label,
118 100.0,
119 $this->getName()
120 );
121 }
122
123 $this->logger->debugMessage("Archive fallback engine: no matching post type for segment '" .
124 $firstSegment . "'");
125
126 return null;
127 }
128 }
129