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 / core / PermalinkResolver.php

PermalinkResolver.php in 404 Solution 4.3.0, at includes/core/PermalinkResolver.php

179 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 if (!defined('ABSPATH')) {
5 exit;
6 }
7
8 /**
9 * Resolves stored "ID|TYPE" permalink references into live arrays of
10 * {id, type, score, link, title, status} by querying the WordPress entity
11 * (post, term, attachment, etc.) the reference points at.
12 *
13 * Extracted from ABJ_404_Solution_Functions per design-audit-2026-06-02
14 * M201 (Functions.php grab-bag split). This is a translation/lookup
15 * concern, not a string/url utility.
16 */
17 class ABJ_404_Solution_PermalinkResolver {
18
19 /** Turns ID|TYPE, SCORE into an array with id, type, score, link, and title.
20 *
21 * @param string $idAndType e.g. 15|POST is a page ID of 15 and a type POST.
22 * @param int|float $linkScore
23 * @param string $rowType if this is "image" then wp_get_attachment_image_src() is used.
24 * @param array<string, mixed>|null $options in case an external URL is used.
25 * @return array<string, mixed> an array with id, type, score, link, and title.
26 */
27 static function permalinkInfoToArray($idAndType, $linkScore, $rowType = null, $options = null) {
28 if ($idAndType == null) {
29 return array('score' => -999);
30 }
31
32 $meta = explode("|", $idAndType);
33 $permalink = array(
34 'id' => $meta[0],
35 // Handle malformed data that doesn't contain a pipe separator
36 'type' => isset($meta[1]) ? $meta[1] : '',
37 'score' => $linkScore,
38 'status' => 'unknown',
39 'link' => 'dunno',
40 );
41
42 /** @var int $idInt */
43 $idInt = (int)$permalink['id'];
44 // Use strict comparison to avoid null/false == 0 issues with type coercion
45 // Cast to int for comparison since ABJ404_TYPE_* constants are integers
46 $typeInt = is_numeric($permalink['type']) ? (int)$permalink['type'] : -1;
47
48 self::resolveByType($permalink, $typeInt, $idInt, $rowType, $options);
49
50 if ($permalink['status'] === false) {
51 $permalink['status'] = 'trash';
52 }
53
54 // Decode anything that might be encoded to support utf8 characters
55 $sanitizer = abj_service('sanitizer');
56 $linkVal = is_string($permalink['link']) ? $permalink['link']
57 : (is_scalar($permalink['link']) ? (string)$permalink['link'] : '');
58 $permalink['link'] = $sanitizer->normalizeUrlString($linkVal);
59 $titleVal = (array_key_exists('title', $permalink) && is_string($permalink['title']))
60 ? $permalink['title'] : '';
61 $permalink['title'] = $sanitizer->normalizeUrlString($titleVal);
62
63 return $permalink;
64 }
65
66 /**
67 * Fill link/title/status on $permalink for the recognized $typeInt.
68 *
69 * @param array<string, mixed> $permalink (by-ref) mutated with link/title/status
70 * @param int $typeInt one of the ABJ404_TYPE_* integer constants, or -1 for unrecognized
71 * @param int $idInt
72 * @param string|null $rowType
73 * @param array<string, mixed>|null $options
74 * @return void
75 */
76 private static function resolveByType(array &$permalink, $typeInt, $idInt, $rowType, $options) {
77 if ($typeInt === ABJ404_TYPE_POST) {
78 self::fillPost($permalink, $idInt, $rowType);
79 } else if ($typeInt === ABJ404_TYPE_TAG) {
80 self::fillTag($permalink, $idInt);
81 } else if ($typeInt === ABJ404_TYPE_CAT) {
82 self::fillCategory($permalink, $idInt);
83 } else if ($typeInt === ABJ404_TYPE_HOME) {
84 $permalink['link'] = get_home_url();
85 $permalink['title'] = get_bloginfo('name');
86 $permalink['status'] = 'published';
87 } else if ($typeInt === ABJ404_TYPE_EXTERNAL) {
88 self::fillExternal($permalink, $options);
89 } else if ($typeInt === ABJ404_TYPE_404_DISPLAYED) {
90 $permalink['link'] = '404';
91 $permalink['status'] = 'published';
92 } else {
93 // A corrupt/unrecognized redirect row (e.g. empty or garbage type
94 // from a damaged DB record) is a tolerable data condition: the
95 // resolver simply can't resolve it, so the row is left in its
96 // unresolvable 'unknown'/'dunno' state for the caller to skip.
97 // Per Defensive Coding Philosophy #8, bad-data the plugin can
98 // function past is logged at WARNING level -- not errorMessage(),
99 // which fires a production telemetry report / admin error notice.
100 $logger = abj_service('logging');
101 $logger->warn("Unrecognized permalink type: " .
102 wp_kses_post((string)json_encode($permalink)));
103 }
104 }
105
106 /**
107 * @param array<string, mixed> $permalink (by-ref)
108 * @param int $idInt
109 * @param string|null $rowType
110 * @return void
111 */
112 private static function fillPost(array &$permalink, $idInt, $rowType) {
113 if ($rowType == 'image') {
114 $imageURL = wp_get_attachment_image_src($idInt, "attached-image");
115 $permalink['link'] = is_array($imageURL) ? $imageURL[0] : '';
116 } else {
117 $permalink['link'] = get_permalink($idInt);
118 }
119 $permalink['title'] = get_the_title($idInt);
120 $permalink['status'] = get_post_status($idInt);
121 }
122
123 /**
124 * @param array<string, mixed> $permalink (by-ref)
125 * @param int $idInt
126 * @return void
127 */
128 private static function fillTag(array &$permalink, $idInt) {
129 $permalink['link'] = get_tag_link($idInt);
130 $tag = get_term($idInt);
131 if (is_object($tag) && !is_wp_error($tag)) {
132 $permalink['title'] = $tag->name;
133 } else {
134 $permalink['title'] = $permalink['link'];
135 }
136 $permalink['status'] = ($permalink['title'] == null || $permalink['title'] == '') ? 'trash' : 'published';
137 }
138
139 /**
140 * Uses get_term_link() instead of get_category_link() to support
141 * custom taxonomies like WooCommerce product_cat.
142 *
143 * @param array<string, mixed> $permalink (by-ref)
144 * @param int $idInt
145 * @return void
146 */
147 private static function fillCategory(array &$permalink, $idInt) {
148 $catTerm = get_term($idInt);
149 if (is_object($catTerm) && !is_wp_error($catTerm)) {
150 $termLink = get_term_link($catTerm);
151 $permalink['link'] = is_wp_error($termLink) ? get_category_link($idInt) : $termLink;
152 $permalink['title'] = $catTerm->name;
153 } else {
154 $permalink['link'] = get_category_link($idInt);
155 $permalink['title'] = $permalink['link'];
156 }
157 $permalink['status'] = ($permalink['title'] == null || $permalink['title'] == '') ? 'trash' : 'published';
158 }
159
160 /**
161 * @param array<string, mixed> $permalink (by-ref)
162 * @param array<string, mixed>|null $options
163 * @return void
164 */
165 private static function fillExternal(array &$permalink, $options) {
166 $permalink['link'] = $permalink['id'];
167 if ($permalink['link'] == ABJ404_TYPE_EXTERNAL) {
168 if ($options == null) {
169 $options = abj_service('options_repository')->getOptions();
170 }
171 $urlDestination = (array_key_exists('dest404pageURL', $options) &&
172 isset($options['dest404pageURL']) ? $options['dest404pageURL'] :
173 'External URL not found in options ABJ404 Solution Error');
174 $permalink['link'] = $urlDestination;
175 }
176 $permalink['status'] = 'published';
177 }
178 }
179