| 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 |
|