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 / admin / RedirectRowPresenter.php

RedirectRowPresenter.php in 404 Solution 4.3.0, at includes/admin/RedirectRowPresenter.php

429 lines 16.6 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 * Presents one Page Redirects table row using the row templates.
9 */
10 class ABJ_404_Solution_RedirectRowPresenter {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $functions;
14
15 /** @var ABJ_404_Solution_RedirectDestinationWarningPolicy */
16 private $warningPolicy;
17
18 /** @var ABJ_404_Solution_RedirectDestinationLinkResolver */
19 private $linkResolver;
20
21 /** @var ABJ_404_Solution_RedirectRowActionsPresenter */
22 private $actionsPresenter;
23
24 public function __construct(ABJ_404_Solution_Functions $functions,
25 ABJ_404_Solution_RedirectDestinationWarningPolicy $warningPolicy,
26 ABJ_404_Solution_RedirectDestinationLinkResolver $linkResolver,
27 ABJ_404_Solution_RedirectRowActionsPresenter $actionsPresenter) {
28 $this->functions = $functions;
29 $this->warningPolicy = $warningPolicy;
30 $this->linkResolver = $linkResolver;
31 $this->actionsPresenter = $actionsPresenter;
32 }
33
34 /**
35 * @param array<string, mixed> $row
36 * @param string $sub
37 * @param array<string, mixed> $tableOptions
38 * @param array<mixed> $deadDestIds
39 */
40 public function render(array $row, string $sub, array $tableOptions, array $deadDestIds, int $y): string {
41 $state = $this->viewState($row, $sub, $tableOptions, $deadDestIds, $y);
42 return $this->fillRedirectRowTemplate($this->templateReplacements($row, $state));
43 }
44
45 /**
46 * @param array<string, mixed> $row
47 * @param array<string, mixed> $tableOptions
48 * @param array<mixed> $deadDestIds
49 * @return array{
50 * rowType: mixed,
51 * rowStatus: int,
52 * rowFinalDest: string,
53 * rowUrl: string,
54 * lastUsed: int,
55 * destLink: array{link: string, title: string},
56 * link: string,
57 * regexState: array{class: string, normalStyle: string, warningStyle: string},
58 * rowClass: string,
59 * actions: array{
60 * edit: string,
61 * logs: string,
62 * trash: string,
63 * delete: string,
64 * links: array{editlink: string, logslink: string, trashlink: string, ajaxTrashLink: string, trashtitle: string, deletelink: string}
65 * },
66 * warning: array{exists: string, notExists: string, text: string, destForView: string},
67 * rowEngine: string
68 * }
69 */
70 private function viewState(array $row, string $sub, array $tableOptions, array $deadDestIds, int $y): array {
71 $rowType = $this->rowType($row);
72 $rowStatus = $this->rowStatus($row);
73 $rowFinalDest = $this->rowFinalDest($row);
74 $destForView = $this->displayDestForView($row, $rowType);
75 $rowUrl = $this->rowUrl($row);
76 $lastUsed = $this->lastUsed($row);
77 $destLink = $this->linkResolver->resolve($rowType, $rowFinalDest);
78 $destinationIsMissing = $this->destinationIsMissing($rowType, $rowFinalDest);
79 $regexState = $this->regexState($rowUrl, $rowStatus);
80
81 return array(
82 'rowType' => $rowType,
83 'rowStatus' => $rowStatus,
84 'rowFinalDest' => $rowFinalDest,
85 'rowUrl' => $rowUrl,
86 'lastUsed' => $lastUsed,
87 'destLink' => $destLink,
88 'link' => $destLink['link'] !== '' ? "href='" . esc_url($destLink['link']) . "'" : '',
89 'regexState' => $regexState,
90 'rowClass' => $this->rowClass($y) . $this->destinationClass($row, $destinationIsMissing) . $regexState['class'],
91 'actions' => $this->actionsPresenter->render($row, $sub, $tableOptions),
92 'warning' => $this->warningPolicy->resolve(
93 $row,
94 $rowType,
95 $rowFinalDest,
96 $destForView,
97 $destinationIsMissing,
98 $deadDestIds
99 ),
100 'rowEngine' => $this->rowEngine($row),
101 );
102 }
103
104 /**
105 * @param array<string, mixed> $row
106 * @param array{
107 * rowType: mixed,
108 * rowStatus: int,
109 * rowFinalDest: string,
110 * rowUrl: string,
111 * lastUsed: int,
112 * destLink: array{link: string, title: string},
113 * link: string,
114 * regexState: array{class: string, normalStyle: string, warningStyle: string},
115 * rowClass: string,
116 * actions: array{
117 * edit: string,
118 * logs: string,
119 * trash: string,
120 * delete: string,
121 * links: array{editlink: string, logslink: string, trashlink: string, ajaxTrashLink: string, trashtitle: string, deletelink: string}
122 * },
123 * warning: array{exists: string, notExists: string, text: string, destForView: string},
124 * rowEngine: string
125 * } $state
126 * @return array<string, string>
127 */
128 private function templateReplacements(array $row, array $state): array {
129 $rowStatus = $state['rowStatus'];
130 $lastUsed = $state['lastUsed'];
131 $rowEngine = $state['rowEngine'];
132 $rowCode = is_scalar($row['code'] ?? '') ? (string)($row['code'] ?? '') : '';
133 $last = $lastUsed != 0 ? (string)wp_date('Y/m/d h:i:s A', abs($lastUsed)) : __('Never Used', '404-solution');
134 $rowId = is_scalar($row['id'] ?? '') ? (string)($row['id'] ?? '') : '';
135 $regexState = $state['regexState'];
136 $actions = $state['actions'];
137 $warning = $state['warning'];
138 $destLink = $state['destLink'];
139 $links = $actions['links'];
140
141 return array(
142 '{rowid}' => $rowId,
143 '{rowClass}' => $state['rowClass'],
144 '{visitorURL}' => esc_url(home_url($state['rowUrl'])),
145 '{rowURL}' => esc_html($state['rowUrl']),
146 '{url-is-normal}' => $regexState['normalStyle'],
147 '{url-looks-like-regex}' => $regexState['warningStyle'],
148 '{editBtnHTML}' => $actions['edit'],
149 '{logsBtnHTML}' => $actions['logs'],
150 '{trashBtnHTML}' => $actions['trash'],
151 '{deleteBtnHTML}' => $actions['delete'],
152 '{statusBadgeClass}' => $this->statusBadgeClass($rowStatus),
153 '{codeBadgeClass}' => $this->codeBadgeClass($rowCode),
154 '{lastUsedClass}' => $lastUsed == 0 ? 'abj404-never-used' : '',
155 '{link}' => $this->scalarString($state['link'] ?? ''),
156 '{title}' => esc_attr($destLink['title']),
157 '{dest}' => esc_attr($warning['destForView']),
158 '{destination-exists}' => $warning['exists'],
159 '{destination-does-not-exist}' => $warning['notExists'],
160 '{destination-warning-text}' => $warning['text'],
161 '{status}' => esc_html($this->statusLabel($rowStatus)),
162 '{statusTitle}' => $this->statusTitle($rowStatus),
163 '{engineHTML}' => $this->engineLabelHtml($rowEngine),
164 '{rowScore}' => '',
165 '{scoreCell}' => $this->buildScoreCell($row['score'] ?? null, $rowEngine),
166 '{type}' => esc_html($this->typeLabel($row)),
167 '{rowCode}' => esc_html($this->codeDisplay($rowCode)),
168 '{hits}' => esc_html((string)(is_scalar($row['logshits'] ?? 0) ? (int)($row['logshits'] ?? 0) : 0)),
169 '{logsLink}' => $links['logslink'],
170 '{trashLink}' => $links['trashlink'],
171 '{ajaxTrashLink}' => $links['ajaxTrashLink'],
172 '{trashtitle}' => $links['trashtitle'],
173 '{deletelink}' => $links['deletelink'],
174 '{created_date}' => esc_html((string)wp_date(
175 'Y/m/d h:i:s A',
176 abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0)
177 )),
178 '{last_used_date}' => esc_html($last),
179 );
180 }
181
182 /**
183 * @param array<string, mixed> $row
184 */
185 private function rowType(array $row): int {
186 return is_scalar($row['type'] ?? 0) ? (int)($row['type'] ?? 0) : 0;
187 }
188
189 /** @param array<string, mixed> $row */
190 private function rowStatus(array $row): int {
191 return is_scalar($row['status'] ?? 0) ? (int)($row['status'] ?? 0) : 0;
192 }
193
194 /** @param array<string, mixed> $row */
195 private function rowFinalDest(array $row): string {
196 return is_string($row['final_dest'] ?? '') ? (string)($row['final_dest'] ?? '') : '';
197 }
198
199 /** @param array<string, mixed> $row */
200 private function destForView(array $row): string {
201 return trim(is_scalar($row['dest_for_view'] ?? '') ? (string)($row['dest_for_view'] ?? '') : '');
202 }
203
204 /** @param array<string, mixed> $row */
205 private function displayDestForView(array $row, int $rowType): string {
206 $destForView = $this->destForView($row);
207 if ($rowType === ABJ404_TYPE_404_DISPLAYED && $destForView === '') {
208 return __('(404 page)', '404-solution');
209 }
210 return $destForView;
211 }
212
213 /** @param array<string, mixed> $row */
214 private function rowUrl(array $row): string {
215 return is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : '';
216 }
217
218 /** @param array<string, mixed> $row */
219 private function lastUsed(array $row): int {
220 return is_scalar($row['last_used'] ?? 0) ? (int)($row['last_used'] ?? 0) : 0;
221 }
222
223 /** @param mixed $rowType */
224 private function destinationIsMissing($rowType, string $rowFinalDest): bool {
225 return $rowType != ABJ404_TYPE_404_DISPLAYED && trim((string)$rowFinalDest) === '';
226 }
227
228 /** @param array<string, mixed> $row */
229 private function rowEngine(array $row): string {
230 return is_string($row['engine'] ?? '') ? trim((string)($row['engine'] ?? '')) : '';
231 }
232
233 /**
234 * @param array<string, string> $replacements
235 */
236 public function fillRedirectRowTemplate(array $replacements): string {
237 $html = $this->tpl('tableRowPageRedirects.html');
238 $html = $this->functions->str_replace(array_keys($replacements), array_values($replacements), $html);
239 return $this->functions->doNormalReplacements($html);
240 }
241
242 /**
243 * @param mixed $rawScore
244 */
245 public function buildScoreCell($rawScore, string $rowEngine): string {
246 if ($rawScore !== null && $rawScore !== '') {
247 $scoreNum = (float)(is_numeric($rawScore) ? $rawScore : 0);
248 $scorePct = number_format($scoreNum, 0);
249 return $this->functions->str_replace(
250 array('{badge_class}', '{score_pct}'),
251 array($this->scoreBadgeClass($scoreNum), esc_html($scorePct)),
252 $this->tpl('viewRedirectsTableScoreBadge.html')
253 );
254 }
255
256 $noScoreTitle = ($rowEngine !== '')
257 ? __('No confidence score for this engine', '404-solution')
258 : __('Manual redirect, no confidence score', '404-solution');
259 return $this->functions->str_replace(
260 '{title_attr}',
261 esc_attr($noScoreTitle),
262 $this->tpl('viewRedirectsTableScoreManual.html')
263 );
264 }
265
266 /**
267 * @param mixed $rowStatus
268 */
269 private function statusTitle($rowStatus): string {
270 if ($rowStatus == ABJ404_STATUS_MANUAL) {
271 return __('Manually created', '404-solution');
272 }
273 if ($rowStatus == ABJ404_STATUS_AUTO) {
274 return __('Automatically created', '404-solution');
275 }
276 if ($rowStatus == ABJ404_STATUS_REGEX) {
277 return __('Regular Expression (Manually Created)', '404-solution');
278 }
279 return __('Unknown', '404-solution');
280 }
281
282 private function statusLabel(int $rowStatus): string {
283 $labels = array(
284 ABJ404_STATUS_MANUAL => __('Manual', '404-solution'),
285 ABJ404_STATUS_AUTO => __('Auto', '404-solution'),
286 ABJ404_STATUS_REGEX => __('Regex', '404-solution'),
287 );
288 if (defined('ABJ404_STATUS_CAPTURED')) {
289 $labels[ABJ404_STATUS_CAPTURED] = __('Captured', '404-solution');
290 }
291 if (defined('ABJ404_STATUS_IGNORED')) {
292 $labels[ABJ404_STATUS_IGNORED] = __('Ignored', '404-solution');
293 }
294 if (defined('ABJ404_STATUS_LATER')) {
295 $labels[ABJ404_STATUS_LATER] = __('Later', '404-solution');
296 }
297 return isset($labels[$rowStatus]) ? $labels[$rowStatus] : __('Unknown', '404-solution');
298 }
299
300 /** @param array<string, mixed> $row */
301 private function typeLabel(array $row): string {
302 $rowType = is_scalar($row['type'] ?? 0) ? (int)($row['type'] ?? 0) : 0;
303 $labels = array(
304 ABJ404_TYPE_EXTERNAL => __('External', '404-solution'),
305 ABJ404_TYPE_CAT => __('Category', '404-solution'),
306 ABJ404_TYPE_TAG => __('Tag', '404-solution'),
307 ABJ404_TYPE_HOME => __('Home', '404-solution'),
308 ABJ404_TYPE_404_DISPLAYED => __('(404 page)', '404-solution'),
309 );
310 if ($rowType === ABJ404_TYPE_POST) {
311 return $this->postTypeLabel($row);
312 }
313 return isset($labels[$rowType]) ? $labels[$rowType] : '';
314 }
315
316 /** @param array<string, mixed> $row */
317 private function postTypeLabel(array $row): string {
318 $slug = is_scalar($row['wp_post_type'] ?? '') ? trim((string)($row['wp_post_type'] ?? '')) : '';
319 if ($slug === '') {
320 $slug = 'post';
321 }
322 if (function_exists('get_post_type_object')) {
323 $postType = get_post_type_object($slug);
324 $singular = is_object($postType) && property_exists($postType, 'labels') && is_object($postType->labels)
325 && property_exists($postType->labels, 'singular_name') && is_scalar($postType->labels->singular_name)
326 ? trim((string)$postType->labels->singular_name)
327 : '';
328 if ($singular !== '') {
329 return $singular;
330 }
331 }
332 return ucfirst(strtolower($slug));
333 }
334
335 /**
336 * @param mixed $rowStatus
337 */
338 private function statusBadgeClass($rowStatus): string {
339 if ($rowStatus == ABJ404_STATUS_AUTO) {
340 return 'abj404-badge-auto';
341 }
342 if ($rowStatus == ABJ404_STATUS_REGEX) {
343 return 'abj404-badge-regex';
344 }
345 return 'abj404-badge-manual';
346 }
347
348 private function codeBadgeClass(string $rowCode): string {
349 $codeBadgeMap = array(
350 '301' => 'abj404-badge-301',
351 '302' => 'abj404-badge-302',
352 '307' => 'abj404-badge-307',
353 '308' => 'abj404-badge-308',
354 '410' => 'abj404-badge-410',
355 '451' => 'abj404-badge-451',
356 '0' => 'abj404-badge-meta',
357 );
358 return isset($codeBadgeMap[$rowCode]) ? $codeBadgeMap[$rowCode] : 'abj404-badge-302';
359 }
360
361 private function codeDisplay(string $rowCode): string {
362 if (abj_service('settings_mode_preference')->getMode() === 'simple') {
363 return ABJ_404_Solution_View_RedirectTypeUI::getPlainLanguageCodeLabel($rowCode);
364 }
365 return $rowCode;
366 }
367
368 private function scoreBadgeClass(float $scoreNum): string {
369 if ($scoreNum >= 80) {
370 return 'abj404-score-high';
371 }
372 if ($scoreNum >= 50) {
373 return 'abj404-score-medium';
374 }
375 return 'abj404-score-low';
376 }
377
378 /** @return array{class: string, normalStyle: string, warningStyle: string} */
379 private function regexState(string $rowUrl, int $rowStatus): array {
380 /** @var ABJ_404_Solution_RegexHelper $regexHelper */
381 $regexHelper = abj_service('regex_helper');
382 $urlLooksLikeRegex = $regexHelper->urlLooksLikeRegex($rowUrl);
383 if ($urlLooksLikeRegex && $rowStatus != ABJ404_STATUS_REGEX) {
384 return array('class' => ' url-looks-like-regex', 'normalStyle' => 'display: none;', 'warningStyle' => '');
385 }
386 return array('class' => '', 'normalStyle' => '', 'warningStyle' => 'display: none;');
387 }
388
389 /**
390 * @param array<string, mixed> $row
391 */
392 private function destinationClass(array $row, bool $destinationIsMissing): string {
393 if ($destinationIsMissing) {
394 return ' destination-does-not-exist';
395 }
396 if (array_key_exists('published_status', $row) && $row['published_status'] == '0') {
397 return ' destination-does-not-exist';
398 }
399 return '';
400 }
401
402 private function rowClass(int $y): string {
403 return $y == 0 ? 'alternate' : 'normal-non-alternate';
404 }
405
406 private function engineLabelHtml(string $rowEngine): string {
407 if ($rowEngine === '') {
408 return '';
409 }
410 return $this->functions->str_replace(
411 '{engine}',
412 esc_html($rowEngine),
413 $this->tpl('viewRedirectsTableEngineLabel.html')
414 );
415 }
416
417 /**
418 * @param mixed $value
419 */
420 private function scalarString($value): string {
421 return is_string($value) ? $value : '';
422 }
423
424 private function tpl(string $name): string {
425 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
426 return rtrim((string)$raw, "\n");
427 }
428 }
429