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 / spelling / SpellSuggestionExclusionPolicy.php

SpellSuggestionExclusionPolicy.php in 404 Solution trunk, at includes/spelling/SpellSuggestionExclusionPolicy.php

210 lines 6.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 * Applies configured page and regex exclusions to spell suggestions.
9 */
10 class ABJ_404_Solution_SpellSuggestionExclusionPolicy {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $f;
14
15 /** @var ABJ_404_Solution_PluginLogic */
16 private $logic;
17
18 /** @var ABJ_404_Solution_Logging */
19 private $logger;
20
21 /** @var ABJ_404_Solution_SpellURLMatcher */
22 private $urlMatcher;
23
24 /** @var string|int|null */
25 private $custom404PageID;
26
27 /**
28 * @param ABJ_404_Solution_Functions $functions
29 * @param ABJ_404_Solution_PluginLogic $logic
30 * @param ABJ_404_Solution_Logging $logger
31 * @param ABJ_404_Solution_SpellURLMatcher $urlMatcher
32 * @param string|int|null $custom404PageID
33 */
34 public function __construct($functions, $logic, $logger, $urlMatcher, $custom404PageID) {
35 $this->f = $functions;
36 $this->logic = $logic;
37 $this->logger = $logger;
38 $this->urlMatcher = $urlMatcher;
39 $this->custom404PageID = $custom404PageID;
40 }
41
42 /**
43 * @param array<string, mixed> $options
44 * @param array<string, string> $permalinks
45 * @return array<string, string>
46 */
47 public function removeExcludedPages(array $options, array $permalinks): array {
48 $excludePagesJsonRaw = isset($options['excludePages[]']) ? $options['excludePages[]'] : '';
49 $excludePagesJson = is_string($excludePagesJsonRaw) ? $excludePagesJsonRaw : '';
50 if (trim($excludePagesJson) == '' && $this->custom404PageID == null) {
51 return $permalinks;
52 }
53
54 $excludePages = json_decode($excludePagesJson);
55 if (!is_array($excludePages)) {
56 $excludePages = array($excludePages);
57 }
58
59 if ($this->custom404PageID != null) {
60 array_push($excludePages, $this->custom404PageID);
61 }
62
63 for ($i = 0; $i < count($excludePages); $i++) {
64 $excludePage = $excludePages[$i];
65 if (!is_scalar($excludePage)) {
66 continue;
67 }
68 $excludePageKey = trim((string)$excludePage);
69 if ($excludePageKey == '') {
70 continue;
71 }
72 unset($permalinks[$excludePageKey]);
73 }
74
75 return $permalinks;
76 }
77
78 /**
79 * @param array<string, mixed> $options
80 * @param array<string, string> $permalinks
81 * @param int $maxCacheCount
82 * @return array<string, string>
83 */
84 public function removeExcludedPagesWithRegex(array $options, array $permalinks, int $maxCacheCount): array {
85 if (!isset($options['suggest_regex_exclusions_usable']) ||
86 !is_array($options['suggest_regex_exclusions_usable']) ||
87 empty($options['suggest_regex_exclusions_usable'])) {
88 return $permalinks;
89 }
90
91 $suggestionsKeptSoFar = 0;
92 $regexExclusions = $options['suggest_regex_exclusions_usable'];
93 $keys_to_check = array_keys($permalinks);
94
95 foreach ($keys_to_check as $key) {
96 if (!array_key_exists($key, $permalinks)) {
97 continue;
98 }
99
100 $keyParts = explode('|', $key);
101 if (count($keyParts) !== 2 || !is_numeric($keyParts[0])) {
102 $this->logger->debugMessage("Skipping invalid key format in removeExcludedPagesWithRegex: " . $key);
103 continue;
104 }
105
106 $id = (int)$keyParts[0];
107 $typeConstant = $keyParts[1];
108
109 $stringToMatch = $this->pathForRegexExclusion($id, $typeConstant, $key);
110 if ($stringToMatch === null) {
111 continue;
112 }
113
114 $kept = true;
115 if ($this->matchesAnyRegexExclusion(array_values($regexExclusions), $stringToMatch, $key)) {
116 unset($permalinks[$key]);
117 $kept = false;
118 }
119
120 if ($kept) {
121 $suggestionsKeptSoFar++;
122 }
123 if ($suggestionsKeptSoFar >= $maxCacheCount) {
124 break;
125 }
126 }
127
128 return $permalinks;
129 }
130
131 /**
132 * @param mixed $typeConstant
133 * @return string|null
134 */
135 private function pathForRegexExclusion(int $id, $typeConstant, string $key): ?string {
136 $rowTypeString = $this->mapTypeConstantToString($typeConstant);
137 if ($rowTypeString === null) {
138 $typeConstantForLog = is_scalar($typeConstant) ? (string)$typeConstant : gettype($typeConstant);
139 $this->logger->debugMessage("Skipping unknown type constant in removeExcludedPagesWithRegex: " . $typeConstantForLog . " for key: " . $key);
140 return null;
141 }
142
143 $urlOfPage = $this->urlMatcher->getPermalink($id, $rowTypeString);
144 if ($urlOfPage === null || trim($urlOfPage) === '') {
145 $this->logger->debugMessage("Skipping null/empty URL for key in removeExcludedPagesWithRegex: " . $key);
146 return null;
147 }
148
149 $urlParts = parse_url($urlOfPage);
150 if (!is_array($urlParts) || !isset($urlParts['path'])) {
151 $this->logger->debugMessage("Skipping URL that failed parse_url for key in removeExcludedPagesWithRegex: " . $key . ", URL: " . esc_url($urlOfPage));
152 return null;
153 }
154
155 $pathOnly = $this->logic->urlNormalization()->removeHomeDirectory($urlParts['path']);
156 if ($pathOnly !== '' && substr($pathOnly, 0, 1) !== '/') {
157 $pathOnly = '/' . $pathOnly;
158 }
159 if ($pathOnly === '') {
160 return '/';
161 }
162
163 return $pathOnly;
164 }
165
166 /**
167 * @param array<int, mixed> $regexExclusions
168 */
169 private function matchesAnyRegexExclusion(array $regexExclusions, string $stringToMatch, string $key): bool {
170 foreach ($regexExclusions as $pattern) {
171 if (!is_scalar($pattern)) {
172 $this->logger->debugMessage("Skipping non-scalar regex exclusion pattern.");
173 continue;
174 }
175 $patternToExcludeNoSlashes = stripslashes((string)$pattern);
176 $matches = array();
177
178 if ($this->f->regexMatch($patternToExcludeNoSlashes, $stringToMatch, $matches)) {
179 $this->logger->debugMessage("Regex excluded suggestion. Key: " . $key .
180 ", Path: '" . esc_html($stringToMatch) . "', Pattern: '" . esc_html($patternToExcludeNoSlashes) . "'");
181 return true;
182 }
183 }
184
185 return false;
186 }
187
188 /**
189 * @param mixed $typeConstant
190 * @return string|null
191 */
192 public function mapTypeConstantToString($typeConstant) {
193 if (!defined('ABJ404_TYPE_POST')) define('ABJ404_TYPE_POST', 1);
194 if (!defined('ABJ404_TYPE_CAT')) define('ABJ404_TYPE_CAT', 2);
195 if (!defined('ABJ404_TYPE_TAG')) define('ABJ404_TYPE_TAG', 3);
196
197 $typeConstantStr = is_scalar($typeConstant) ? (string)$typeConstant : '';
198 switch ($typeConstantStr) {
199 case ABJ404_TYPE_POST:
200 return 'pages';
201 case ABJ404_TYPE_TAG:
202 return 'tags';
203 case ABJ404_TYPE_CAT:
204 return 'categories';
205 default:
206 return null;
207 }
208 }
209 }
210