| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* ViewTrait_Logs methods. |
| 9 |
*/ |
| 10 |
trait ViewTrait_Logs { |
| 11 |
|
| 12 |
|
| 13 |
/** |
| 14 |
* @return void |
| 15 |
*/ |
| 16 |
function echoAdminLogsPage() { |
| 17 |
|
| 18 |
$sub = 'abj404_logs'; |
| 19 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 20 |
|
| 21 |
// Sanitizing unchecked table options |
| 22 |
$tableOptions = $this->logic->sanitizePostData($tableOptions); |
| 23 |
|
| 24 |
|
| 25 |
// Modern page wrapper |
| 26 |
echo '<div class="abj404-table-page">'; |
| 27 |
|
| 28 |
// Header with page title |
| 29 |
echo '<div class="abj404-table-header">'; |
| 30 |
echo '<h2>' . __('Redirect Logs', '404-solution') . '</h2>'; |
| 31 |
echo '</div>'; |
| 32 |
|
| 33 |
// Extract current table options for the filter bar data attributes |
| 34 |
$perpage = array_key_exists('perpage', $tableOptions) && is_scalar($tableOptions['perpage']) ? $tableOptions['perpage'] : 25; |
| 35 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'timestamp'; |
| 36 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'DESC'; |
| 37 |
$paginationNonce = wp_create_nonce('abj404_updatePaginationLink'); |
| 38 |
$inflightNonce = wp_create_nonce('abj404_fetchInflightStage'); |
| 39 |
|
| 40 |
// Filter bar with search dropdown and AJAX data attributes for deferred table load |
| 41 |
echo '<div class="abj404-filter-bar tablenav"' |
| 42 |
. ' data-pagination-ajax-url="' . esc_attr(admin_url('admin-ajax.php')) . '"' |
| 43 |
. ' data-pagination-ajax-action="ajaxUpdatePaginationLinks"' |
| 44 |
. ' data-pagination-ajax-subpage="' . esc_attr($sub) . '"' |
| 45 |
. ' data-pagination-ajax-nonce="' . esc_attr($paginationNonce) . '"' |
| 46 |
. ' data-pagination-inflight-nonce="' . esc_attr($inflightNonce) . '"' |
| 47 |
. ' data-pagination-current-signature=""' |
| 48 |
. ' data-pagination-current-orderby="' . esc_attr($orderby) . '"' |
| 49 |
. ' data-pagination-current-order="' . esc_attr($order) . '"' |
| 50 |
. ' data-pagination-current-filter="0"' |
| 51 |
. ' data-pagination-current-paged="1"' |
| 52 |
. ' data-pagination-current-score-range="all"' |
| 53 |
. ' data-pagination-current-logsid="' . esc_attr((string)$this->viewGetPostOrGetSanitize('redirect_to_data_field_id')) . '"' |
| 54 |
. ' data-pagination-initial-load="1"' |
| 55 |
. ' data-pagination-auto-refresh="1"' |
| 56 |
. ' data-pagination-refresh-started-text="' . esc_attr(__('Refreshing data in background…', '404-solution')) . '"' |
| 57 |
. ' data-pagination-refresh-finished-text="' . esc_attr(__('Data refreshed', '404-solution')) . '"' |
| 58 |
. ' data-pagination-refresh-available-text="' . esc_attr(__('Refresh available', '404-solution')) . '">'; |
| 59 |
|
| 60 |
// Log search form |
| 61 |
echo '<form id="logs_search_form" name="admin-logs-page" method="GET" action="" class="abj404-logs-search-form">'; |
| 62 |
echo '<input type="hidden" name="page" value="' . ABJ404_PP . '">'; |
| 63 |
echo '<input type="hidden" name="subpage" value="abj404_logs">'; |
| 64 |
|
| 65 |
// ----------------- dropdown search box. begin. |
| 66 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . |
| 67 |
"/html/viewLogsForSearchBox.html"); |
| 68 |
|
| 69 |
$redirectPageTitle = $this->viewGetPostOrGetSanitize('redirect_to_data_field_title'); |
| 70 |
$pageIDAndType = $this->viewGetPostOrGetSanitize('redirect_to_data_field_id'); |
| 71 |
|
| 72 |
$html = $this->f->str_replace('{redirect_to_label}', __('View logs for', '404-solution'), $html); |
| 73 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_EMPTY}', |
| 74 |
__('(Begin typing a URL)', '404-solution'), $html); |
| 75 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_PAGE}', |
| 76 |
__('(A page has been selected.)', '404-solution'), $html); |
| 77 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_CUSTOM_STRING}', |
| 78 |
__('(A custom string has been entered.)', '404-solution'), $html); |
| 79 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_URL}', |
| 80 |
__('(Please choose from the dropdown list instead of typing your own URL.)', '404-solution'), $html); |
| 81 |
$html = $this->f->str_replace('{pageIDAndType}', esc_attr($pageIDAndType), $html); |
| 82 |
$html = $this->f->str_replace('{redirectPageTitle}', esc_attr($redirectPageTitle), $html); |
| 83 |
$html = $this->f->str_replace('{data-url}', |
| 84 |
"admin-ajax.php?action=echoViewLogsFor&nonce=" . wp_create_nonce('abj404_ajax'), $html); |
| 85 |
$html = $this->f->doNormalReplacements($html); |
| 86 |
echo $html; |
| 87 |
// ----------------- dropdown search box. end. |
| 88 |
|
| 89 |
echo '</form>'; |
| 90 |
|
| 91 |
// Rows per page |
| 92 |
echo '<div class="abj404-rows-per-page">'; |
| 93 |
echo '<span>' . __('Rows per page:', '404-solution') . '</span>'; |
| 94 |
echo '<select class="abj404-filter-select perpage" name="perpage" onchange="paginationLinksChange(this);">'; |
| 95 |
$perPageOptions = array(10, 25, 50, 100, 250); |
| 96 |
foreach ($perPageOptions as $opt) { |
| 97 |
$selected = ($perpage == $opt) ? ' selected' : ''; |
| 98 |
echo '<option value="' . $opt . '"' . $selected . '>' . $opt . '</option>'; |
| 99 |
} |
| 100 |
echo '</select>'; |
| 101 |
echo '</div>'; |
| 102 |
|
| 103 |
echo '<span class="abj404-refresh-status" aria-live="polite"></span>'; |
| 104 |
echo '</div><!-- .abj404-filter-bar -->'; |
| 105 |
|
| 106 |
// Table placeholder — data loaded via AJAX |
| 107 |
echo '<table class="abj404-table" data-table-awaiting-load="1">'; |
| 108 |
echo '<thead><tr><th>' . esc_html__('Loading logs…', '404-solution') . '</th></tr></thead>'; |
| 109 |
echo '<tbody><tr><td class="abj404-empty-message">' . esc_html__('Loading logs…', '404-solution') . '</td></tr></tbody>'; |
| 110 |
echo '</table>'; |
| 111 |
|
| 112 |
// Bottom pagination placeholder |
| 113 |
echo '<div class="abj404-pagination tablenav abj404-pagination-right abj404-pagination-bottom">'; |
| 114 |
echo '<span class="abj404-refresh-status" aria-live="polite">' . esc_html__('Loading…', '404-solution') . '</span>'; |
| 115 |
echo '</div>'; |
| 116 |
|
| 117 |
echo '</div><!-- .abj404-table-page -->'; |
| 118 |
} |
| 119 |
|
| 120 |
/** |
| 121 |
* @param string $sub |
| 122 |
* @return string |
| 123 |
*/ |
| 124 |
function getAdminLogsPageTable($sub) { |
| 125 |
|
| 126 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 127 |
|
| 128 |
// Build column headers with sorting. |
| 129 |
// Engine is displayed inside the Action cell; User is displayed inside the Date cell. |
| 130 |
// Perf audit F5: only `url` (alias for requested_url) and `timestamp` |
| 131 |
// have indexes on logsv2. IP / Referrer / Action map to unindexed |
| 132 |
// columns, so we render them as plain non-clickable headers to avoid |
| 133 |
// offering a sort that would trigger filesort on large logs tables. |
| 134 |
$columns = array( |
| 135 |
'url' => array('title' => __('URL', '404-solution'), 'orderby' => 'url'), |
| 136 |
'host' => array('title' => __('IP Address', '404-solution'), 'orderby' => ''), |
| 137 |
'refer' => array('title' => __('Referrer', '404-solution'), 'orderby' => ''), |
| 138 |
'dest' => array('title' => __('Action', '404-solution'), 'orderby' => ''), |
| 139 |
'timestamp' => array('title' => __('Date', '404-solution'), 'orderby' => 'timestamp'), |
| 140 |
); |
| 141 |
|
| 142 |
$html = '<table class="abj404-table abj404-logs-table">'; |
| 143 |
$html .= '<thead><tr>'; |
| 144 |
|
| 145 |
// Detail toggle column (not sortable) |
| 146 |
$html .= '<th scope="col" class="abj404-trace-th"></th>'; |
| 147 |
|
| 148 |
// Generate column headers (sortable if the underlying column is indexed). |
| 149 |
foreach ($columns as $key => $col) { |
| 150 |
$orderbyCol = $col['orderby']; |
| 151 |
if ($orderbyCol === '') { |
| 152 |
$html .= '<th scope="col">' . esc_html($col['title']) . '</th>'; |
| 153 |
continue; |
| 154 |
} |
| 155 |
$sortUrl = "?page=" . ABJ404_PP . "&subpage=abj404_logs"; |
| 156 |
$sortUrl .= "&orderby=" . $orderbyCol; |
| 157 |
$sortState = $this->getHeaderSortState($tableOptions, $orderbyCol, false); |
| 158 |
$newOrder = $sortState['nextOrder']; |
| 159 |
$sortUrl .= "&order=" . $newOrder; |
| 160 |
|
| 161 |
$sortClass = trim($sortState['thClass']); |
| 162 |
$sortClassAttr = ($sortClass !== '') ? ' class="' . $sortClass . '"' : ''; |
| 163 |
$sortIndicator = $sortState['indicator']; |
| 164 |
|
| 165 |
$html .= '<th scope="col"' . $sortClassAttr . '><a href="' . esc_url($sortUrl) . '">' . esc_html($col['title']) . $sortIndicator . '</a></th>'; |
| 166 |
} |
| 167 |
|
| 168 |
$html .= '</tr></thead>'; |
| 169 |
$html .= '<tbody id="the-list">'; |
| 170 |
|
| 171 |
$rows = $this->logsRepository->getLogRecords($tableOptions); |
| 172 |
/** @var array<int, array<string, mixed>> $typedLogRows */ |
| 173 |
$typedLogRows = array_values(array_filter($rows, 'is_array')); |
| 174 |
$this->rememberTableDataSignature($sub, $typedLogRows); |
| 175 |
$logRecordsDisplayed = 0; |
| 176 |
|
| 177 |
foreach ($typedLogRows as $row) { |
| 178 |
$logId = is_scalar($row['log_id'] ?? '') ? (string)($row['log_id'] ?? '') : ''; |
| 179 |
$rawTrace = isset($row['pipeline_trace']) && is_string($row['pipeline_trace']) ? $row['pipeline_trace'] : null; |
| 180 |
$traceSteps = ABJ_404_Solution_LogsRepository::decompressPipelineTrace($rawTrace); |
| 181 |
$hasTrace = is_array($traceSteps) && !empty($traceSteps); |
| 182 |
|
| 183 |
$html .= '<tr>'; |
| 184 |
|
| 185 |
// Detail toggle button |
| 186 |
$html .= '<td class="abj404-trace-toggle-cell">'; |
| 187 |
if ($hasTrace) { |
| 188 |
$html .= '<button class="abj404-trace-toggle" data-row-id="' . esc_attr($logId) . '" title="' . esc_attr__('Show pipeline trace', '404-solution') . '">▶</button>'; |
| 189 |
} else { |
| 190 |
$html .= '<button class="abj404-trace-toggle" disabled title="' . esc_attr__('No trace available', '404-solution') . '">▶</button>'; |
| 191 |
} |
| 192 |
$html .= '</td>'; |
| 193 |
|
| 194 |
// URL column |
| 195 |
$url = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : ''; |
| 196 |
$urlDetail = is_string($row['url_detail'] ?? '') ? (string)($row['url_detail'] ?? '') : ''; |
| 197 |
$fullVisitorURL = esc_url(home_url($url)); |
| 198 |
$urlDisplay = '<a href="' . $fullVisitorURL . '" target="_blank" title="' . esc_attr($url) . '">' . esc_html($url) . '</a>'; |
| 199 |
if ($urlDetail !== '' && trim($urlDetail) !== '') { |
| 200 |
$urlDisplay .= ' <span class="abj404-url-detail">(' . esc_html(trim($urlDetail)) . ')</span>'; |
| 201 |
} |
| 202 |
$html .= '<td class="abj404-url-cell">' . $urlDisplay . '</td>'; |
| 203 |
|
| 204 |
// IP Address |
| 205 |
$remoteHost = is_string($row['remote_host'] ?? '') ? (string)($row['remote_host'] ?? '') : ''; |
| 206 |
$html .= '<td class="abj404-ip-cell">' . esc_html($remoteHost) . '</td>'; |
| 207 |
|
| 208 |
// Referrer |
| 209 |
$referrer = is_string($row['referrer'] ?? '') ? (string)($row['referrer'] ?? '') : ''; |
| 210 |
$html .= '<td class="abj404-url-cell">'; |
| 211 |
if ($referrer != "") { |
| 212 |
$html .= '<a href="' . esc_url($referrer) . '" title="' . esc_attr($referrer) . '" target="_blank">' . esc_html($referrer) . '</a>'; |
| 213 |
} else { |
| 214 |
$html .= '<span class="abj404-text-muted">-</span>'; |
| 215 |
} |
| 216 |
$html .= '</td>'; |
| 217 |
|
| 218 |
// Action Taken (with engine on second line) |
| 219 |
$action = trim(is_string($row['action'] ?? '') ? (string)($row['action'] ?? '') : ''); |
| 220 |
$engineVal = is_string($row['engine'] ?? '') ? (string)($row['engine'] ?? '') : ''; |
| 221 |
$html .= '<td>'; |
| 222 |
if ($action === '' || $action == "404" || $action == "http://404") { |
| 223 |
$html .= '<span class="abj404-badge abj404-badge-404">' . __('404', '404-solution') . '</span>'; |
| 224 |
} else { |
| 225 |
$html .= '<span class="abj404-badge abj404-badge-redirect">' . __('Redirect', '404-solution') . '</span>'; |
| 226 |
$html .= '<br><a href="' . esc_url($action) . '" title="' . esc_attr($action) . '" target="_blank" class="abj404-action-url">' . esc_html($action) . '</a>'; |
| 227 |
} |
| 228 |
if ($engineVal !== '') { |
| 229 |
$html .= '<br><span class="abj404-engine-label" title="' . esc_attr__('Engine', '404-solution') . '">' . esc_html($engineVal) . '</span>'; |
| 230 |
} |
| 231 |
$html .= '</td>'; |
| 232 |
|
| 233 |
// Date (with user on second line) |
| 234 |
$timeToDisplay = abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0); |
| 235 |
$rowUsername = is_string($row['username'] ?? '') ? (string)($row['username'] ?? '') : ''; |
| 236 |
$html .= '<td class="abj404-date-cell">' . (string)wp_date('Y/m/d', $timeToDisplay) . '<br>' . (string)wp_date('h:i:s A', $timeToDisplay); |
| 237 |
if ($rowUsername !== '') { |
| 238 |
$html .= '<br><span class="abj404-username-label">' . esc_html($rowUsername) . '</span>'; |
| 239 |
} |
| 240 |
$html .= '</td>'; |
| 241 |
|
| 242 |
$html .= '</tr>'; |
| 243 |
|
| 244 |
// Hidden detail row with pipeline trace |
| 245 |
if ($hasTrace && $logId !== '') { |
| 246 |
$html .= '<tr id="abj404-trace-' . esc_attr($logId) . '" class="abj404-trace-detail" style="display:none">'; |
| 247 |
$html .= '<td colspan="6" class="abj404-trace-detail-cell">'; |
| 248 |
$html .= '<ol class="abj404-trace-steps">'; |
| 249 |
foreach ($traceSteps as $step) { |
| 250 |
$stepName = esc_html($this->translateTraceLabel($step['step'])); |
| 251 |
$outcome = esc_html($this->translateTraceLabel($step['outcome'])); |
| 252 |
$detail = ($step['detail'] !== '') |
| 253 |
? ' <span class="abj404-trace-detail-text">' . esc_html($step['detail']) . '</span>' : ''; |
| 254 |
$outcomeClass = $this->traceOutcomeClass($step['outcome']); |
| 255 |
$html .= '<li><span class="abj404-trace-step-name">' . $stepName . '</span>' |
| 256 |
. '<span class="abj404-trace-outcome ' . $outcomeClass . '">' . $outcome . '</span>' |
| 257 |
. $detail . '</li>'; |
| 258 |
} |
| 259 |
$html .= '</ol>'; |
| 260 |
$html .= '</td>'; |
| 261 |
$html .= '</tr>'; |
| 262 |
} |
| 263 |
|
| 264 |
$logRecordsDisplayed++; |
| 265 |
} |
| 266 |
|
| 267 |
$this->logger->debugMessage($logRecordsDisplayed . " log records displayed on the page."); |
| 268 |
|
| 269 |
if ($logRecordsDisplayed == 0) { |
| 270 |
$html .= '<tr><td colspan="6" class="abj404-empty-message">' . __('No Results To Display', '404-solution') . '</td></tr>'; |
| 271 |
} |
| 272 |
|
| 273 |
$html .= '</tbody></table>'; |
| 274 |
|
| 275 |
// Inline JS for trace row expand/collapse (output once per page) |
| 276 |
$html .= '<script> |
| 277 |
(function() { |
| 278 |
if (window._abj404TraceToggleInit) { return; } |
| 279 |
window._abj404TraceToggleInit = true; |
| 280 |
document.addEventListener("click", function(e) { |
| 281 |
var btn = e.target; |
| 282 |
if (!btn || !btn.classList || !btn.classList.contains("abj404-trace-toggle")) { return; } |
| 283 |
var rowId = btn.getAttribute("data-row-id"); |
| 284 |
if (!rowId) { return; } |
| 285 |
var detail = document.getElementById("abj404-trace-" + rowId); |
| 286 |
if (!detail) { return; } |
| 287 |
var visible = detail.style.display !== "none"; |
| 288 |
detail.style.display = visible ? "none" : ""; |
| 289 |
btn.textContent = visible ? "\u25B6" : "\u25BC"; |
| 290 |
}); |
| 291 |
}()); |
| 292 |
</script>'; |
| 293 |
|
| 294 |
return $html; |
| 295 |
} |
| 296 |
|
| 297 |
/** |
| 298 |
* Translate a known pipeline-trace string for admin display. |
| 299 |
* |
| 300 |
* Step names and outcomes are stored in English in the database so they |
| 301 |
* remain stable across language switches. This method translates them |
| 302 |
* at render time into the current admin locale. |
| 303 |
* |
| 304 |
* @param string $text The English string stored in the trace. |
| 305 |
* @return string The translated string (falls through unchanged |
| 306 |
* for dynamic/unknown values like engine names or URLs). |
| 307 |
*/ |
| 308 |
private function translateTraceLabel(string $text): string { |
| 309 |
// Static map — evaluated once per request. |
| 310 |
static $map = null; |
| 311 |
if ($map === null) { |
| 312 |
$map = [ |
| 313 |
// Step names |
| 314 |
'Ignore list' => __('Ignore list', '404-solution'), |
| 315 |
'Redirect lookup' => __('Redirect lookup', '404-solution'), |
| 316 |
'Redirect lookup (without comments)' => __('Redirect lookup (without comments)', '404-solution'), |
| 317 |
'Conditions' => __('Conditions', '404-solution'), |
| 318 |
'Conditions (without comments)' => __('Conditions (without comments)', '404-solution'), |
| 319 |
'Health check' => __('Health check', '404-solution'), |
| 320 |
'Health check (without comments)' => __('Health check (without comments)', '404-solution'), |
| 321 |
'Regex rules' => __('Regex rules', '404-solution'), |
| 322 |
'Suggestion engines' => __('Suggestion engines', '404-solution'), |
| 323 |
'Result' => __('Result', '404-solution'), |
| 324 |
// Outcomes |
| 325 |
'Not ignored' => __('Not ignored', '404-solution'), |
| 326 |
'Matched — request ignored' => __('Matched — request ignored', '404-solution'), |
| 327 |
'Found existing redirect' => __('Found existing redirect', '404-solution'), |
| 328 |
'No matching redirect' => __('No matching redirect', '404-solution'), |
| 329 |
'All conditions met' => __('All conditions met', '404-solution'), |
| 330 |
'Blocked by conditions' => __('Blocked by conditions', '404-solution'), |
| 331 |
'Destination unreachable — skipped' => __('Destination unreachable — skipped', '404-solution'), |
| 332 |
'Matched' => __('Matched', '404-solution'), |
| 333 |
'No match' => __('No match', '404-solution'), |
| 334 |
'Skipped' => __('Skipped', '404-solution'), |
| 335 |
'Excluded' => __('Excluded', '404-solution'), |
| 336 |
'Error' => __('Error', '404-solution'), |
| 337 |
'No match found' => __('No match found', '404-solution'), |
| 338 |
'Showed 404 page' => __('Showed 404 page', '404-solution'), |
| 339 |
'Redirected to external URL' => __('Redirected to external URL', '404-solution'), |
| 340 |
'No redirect — showed 404 page' => __('No redirect — showed 404 page', '404-solution'), |
| 341 |
]; |
| 342 |
} |
| 343 |
|
| 344 |
// Exact match |
| 345 |
if (isset($map[$text])) { |
| 346 |
return $map[$text]; |
| 347 |
} |
| 348 |
|
| 349 |
// Dynamic patterns: "Engine: Spelling", "Redirected (301)", etc. |
| 350 |
if (strpos($text, 'Engine: ') === 0) { |
| 351 |
$engineName = substr($text, 8); |
| 352 |
return sprintf(__('Engine: %s', '404-solution'), $engineName); |
| 353 |
} |
| 354 |
if (preg_match('/^Redirected \((\d+)\)$/', $text, $m)) { |
| 355 |
return sprintf(__('Redirected (%s)', '404-solution'), $m[1]); |
| 356 |
} |
| 357 |
if (preg_match('/^Responded with (.+)$/', $text, $m)) { |
| 358 |
return sprintf(__('Responded with %s', '404-solution'), $m[1]); |
| 359 |
} |
| 360 |
if (preg_match('/^Showed 404 page — (.+)$/', $text, $m)) { |
| 361 |
return sprintf(__('Showed 404 page — %s', '404-solution'), $m[1]); |
| 362 |
} |
| 363 |
|
| 364 |
return $text; |
| 365 |
} |
| 366 |
|
| 367 |
/** |
| 368 |
* Return a CSS class for the trace outcome badge based on the outcome text. |
| 369 |
* |
| 370 |
* @param string $outcome |
| 371 |
* @return string |
| 372 |
*/ |
| 373 |
private function traceOutcomeClass(string $outcome): string { |
| 374 |
$lower = strtolower($outcome); |
| 375 |
// Negative / blocking outcomes |
| 376 |
if (strpos($lower, 'blocked') !== false || strpos($lower, 'unreachable') !== false |
| 377 |
|| strpos($lower, 'error') !== false || strpos($lower, 'ignored') !== false |
| 378 |
|| strpos($lower, 'missing') !== false || strpos($lower, 'invalid') !== false) { |
| 379 |
return 'abj404-trace-outcome-fail'; |
| 380 |
} |
| 381 |
// Final result outcomes |
| 382 |
if (strpos($lower, 'redirected') !== false || strpos($lower, 'responded') !== false |
| 383 |
|| strpos($lower, 'showed 404') !== false || strpos($lower, 'no redirect') !== false) { |
| 384 |
return 'abj404-trace-outcome-result'; |
| 385 |
} |
| 386 |
// Positive / pass-through outcomes |
| 387 |
if (strpos($lower, 'found') !== false || strpos($lower, 'matched') !== false |
| 388 |
|| strpos($lower, 'passed') !== false || strpos($lower, 'not ignored') !== false |
| 389 |
|| strpos($lower, 'all conditions met') !== false) { |
| 390 |
return 'abj404-trace-outcome-pass'; |
| 391 |
} |
| 392 |
// Neutral (skipped, no match, etc.) |
| 393 |
return 'abj404-trace-outcome-neutral'; |
| 394 |
} |
| 395 |
|
| 396 |
/** |
| 397 |
* @param string $sub |
| 398 |
* @param array<string, array<string, string>> $columns |
| 399 |
* @return string |
| 400 |
*/ |
| 401 |
function getTableColumns($sub, $columns) { |
| 402 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 403 |
|
| 404 |
$html = "<tr>"; |
| 405 |
|
| 406 |
$cbinfo = 'class="manage-column column-cb check-column" style="{cb-info-style}"'; |
| 407 |
$cbinfoStyle = 'vertical-align: middle; padding-bottom: 6px;'; |
| 408 |
if ($sub == 'abj404_logs') { |
| 409 |
$cbinfoStyle .= ' width: 0px;'; |
| 410 |
} |
| 411 |
$cbinfo = $this->f->str_replace('{cb-info-style}', $cbinfoStyle, $cbinfo); |
| 412 |
|
| 413 |
$html .= "<th scope=\"col\" " . $cbinfo . ">"; |
| 414 |
if ($sub != 'abj404_logs') { |
| 415 |
$html .= "<input type=\"checkbox\" name=\"bulkSelectorCheckbox\" onchange=\"enableDisableApplyButton();\" aria-label=\"" . esc_attr__('Select all', '404-solution') . "\">"; |
| 416 |
} |
| 417 |
$html .= "</th>"; |
| 418 |
|
| 419 |
foreach ($columns as $column) { |
| 420 |
$style = ""; |
| 421 |
if (isset($column['width']) && $column['width'] != "") { |
| 422 |
$style = " style=\"width: " . esc_attr($column['width']) . ";\" "; |
| 423 |
} |
| 424 |
$nolink = 0; |
| 425 |
$sortorder = ""; |
| 426 |
$sortIndicator = ''; |
| 427 |
$orderby = isset($column['orderby']) ? $column['orderby'] : ''; |
| 428 |
$preferDescOnFirstClick = ($orderby == "timestamp" || |
| 429 |
$orderby == "last_used" || |
| 430 |
$orderby == "logshits"); |
| 431 |
$sortState = $this->getHeaderSortState($tableOptions, (string)$orderby, $preferDescOnFirstClick); |
| 432 |
if (!$sortState['isSortable']) { |
| 433 |
$thClass = ""; |
| 434 |
$nolink = 1; |
| 435 |
} else { |
| 436 |
$thClass = " " . $sortState['thClass']; |
| 437 |
$sortorder = $sortState['nextOrder']; |
| 438 |
$sortIndicator = $sortState['indicator']; |
| 439 |
} |
| 440 |
|
| 441 |
$url = "?page=" . ABJ404_PP; |
| 442 |
if ($sub !== '') { |
| 443 |
$url .= "&subpage=" . rawurlencode((string)$sub); |
| 444 |
} |
| 445 |
if ($sub == 'abj404_logs') { |
| 446 |
$url .= "&id=" . ($tableOptions['logsid'] ?? 0); |
| 447 |
} |
| 448 |
if (($tableOptions['filter'] ?? 0) != 0) { |
| 449 |
$url .= "&filter=" . $tableOptions['filter']; |
| 450 |
} |
| 451 |
$url .= "&orderby=" . $orderby . "&order=" . $sortorder; |
| 452 |
|
| 453 |
$tooltipHtml = ''; |
| 454 |
if (array_key_exists('title_attr_html', $column) && !empty($column['title_attr_html'])) { |
| 455 |
// Raw HTML (already escaped where needed) |
| 456 |
$tooltipHtml = '<span class="abj404-header-tooltip lefty-tooltip" aria-label="' . esc_attr__('More info', '404-solution') . '">' . |
| 457 |
'<span class="abj404-header-tooltip-icon" aria-hidden="true">?</span>' . |
| 458 |
'<span class="lefty-tooltiptext">' . $column['title_attr_html'] . '</span>' . |
| 459 |
'</span>' . "\n"; |
| 460 |
} elseif (array_key_exists('title_attr', $column) && !empty($column['title_attr'])) { |
| 461 |
// Plain text - escape it |
| 462 |
$tooltipHtml = '<span class="abj404-header-tooltip lefty-tooltip" aria-label="' . esc_attr__('More info', '404-solution') . '">' . |
| 463 |
'<span class="abj404-header-tooltip-icon" aria-hidden="true">?</span>' . |
| 464 |
'<span class="lefty-tooltiptext">' . esc_html($column['title_attr']) . '</span>' . |
| 465 |
'</span>' . "\n"; |
| 466 |
} |
| 467 |
|
| 468 |
// Support custom column classes (e.g., hide-on-tablet, hide-on-mobile) |
| 469 |
if (isset($column['class']) && $column['class'] != '') { |
| 470 |
$thClass .= ' ' . esc_attr($column['class']); |
| 471 |
} |
| 472 |
|
| 473 |
$html .= "<th scope=\"col\" " . $style . " class=\"manage-column column-title" . $thClass . "\"> \n"; |
| 474 |
|
| 475 |
$title = isset($column['title']) ? $column['title'] : ''; |
| 476 |
if ($nolink == 1) { |
| 477 |
$html .= $title; |
| 478 |
$html .= $tooltipHtml; |
| 479 |
} else { |
| 480 |
$html .= "<a href=\"" . esc_url($url) . "\">"; |
| 481 |
$html .= '<span class="table_header_' . $orderby . '">' . |
| 482 |
esc_html($title) . $sortIndicator . "</span>"; |
| 483 |
$html .= "</a>"; |
| 484 |
$html .= $tooltipHtml; |
| 485 |
} |
| 486 |
$html .= "</th>"; |
| 487 |
} |
| 488 |
$html .= "</tr>"; |
| 489 |
|
| 490 |
return $html; |
| 491 |
} |
| 492 |
|
| 493 |
/** |
| 494 |
* @param string $sub |
| 495 |
* @param bool $showSearchFilter |
| 496 |
*/ |
| 497 |
/** |
| 498 |
* @param string $sub |
| 499 |
* @param bool $showSearchFilter |
| 500 |
* @return string |
| 501 |
*/ |
| 502 |
function getPaginationLinks($sub, $showSearchFilter = true) { |
| 503 |
|
| 504 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 505 |
$logsid = array_key_exists('logsid', $tableOptions) && is_scalar($tableOptions['logsid']) ? $tableOptions['logsid'] : 0; |
| 506 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 507 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 508 |
$filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 509 |
|
| 510 |
$url = "?page=" . ABJ404_PP; |
| 511 |
if ($sub !== '') { |
| 512 |
$url .= "&subpage=" . rawurlencode((string)$sub); |
| 513 |
} |
| 514 |
if ($sub == 'abj404_logs') { |
| 515 |
$url .= "&id=" . $logsid; |
| 516 |
} |
| 517 |
|
| 518 |
$url .= "&orderby=" . sanitize_text_field($orderby); |
| 519 |
$url .= "&order=" . sanitize_text_field($order); |
| 520 |
$url .= "&filter=" . absint((int)$filter); |
| 521 |
|
| 522 |
if ($sub == 'abj404_logs') { |
| 523 |
$num_records = $this->viewReadService->getLogsCount((int)$logsid); |
| 524 |
} else { |
| 525 |
$num_records = $this->viewReadService->getRedirectsForViewCount($sub, $tableOptions); |
| 526 |
} |
| 527 |
|
| 528 |
// Ensure perpage is never 0 to prevent division by zero |
| 529 |
$perpage = absint(array_key_exists('perpage', $tableOptions) && is_scalar($tableOptions['perpage']) ? $tableOptions['perpage'] : ABJ404_OPTION_MIN_PERPAGE); |
| 530 |
if ($perpage == 0) { |
| 531 |
$perpage = ABJ404_OPTION_MIN_PERPAGE; |
| 532 |
} |
| 533 |
|
| 534 |
// Ensure paged is a valid integer |
| 535 |
$paged = absint(array_key_exists('paged', $tableOptions) && is_scalar($tableOptions['paged']) ? $tableOptions['paged'] : 1); |
| 536 |
if ($paged == 0) { |
| 537 |
$paged = 1; |
| 538 |
} |
| 539 |
|
| 540 |
$total_pages = ceil($num_records / $perpage); |
| 541 |
if ($total_pages == 0) { |
| 542 |
$total_pages = 1; |
| 543 |
} |
| 544 |
|
| 545 |
$firsturl = $url; |
| 546 |
|
| 547 |
if ($paged == 1) { |
| 548 |
$prevurl = $url; |
| 549 |
} else { |
| 550 |
$prev = $paged - 1; |
| 551 |
$prevurl = $url . "&paged=" . $prev; |
| 552 |
} |
| 553 |
|
| 554 |
if ($paged + 1 > $total_pages) { |
| 555 |
if ($paged == 1) { |
| 556 |
$nexturl = $url; |
| 557 |
} else { |
| 558 |
$nexturl = $url . "&paged=" . $paged; |
| 559 |
} |
| 560 |
} else { |
| 561 |
$next = $paged + 1; |
| 562 |
$nexturl = $url . "&paged=" . $next; |
| 563 |
} |
| 564 |
|
| 565 |
if ($paged + 1 > $total_pages) { |
| 566 |
if ($paged == 1) { |
| 567 |
$lasturl = $url; |
| 568 |
} else { |
| 569 |
$lasturl = $url . "&paged=" . $paged; |
| 570 |
} |
| 571 |
} else { |
| 572 |
$lasturl = $url . "&paged=" . $total_pages; |
| 573 |
} |
| 574 |
|
| 575 |
// ------------ |
| 576 |
$start = (($paged - 1) * $perpage) + 1; |
| 577 |
$end = min($start + $perpage - 1, $num_records); |
| 578 |
/* Translators: 1: Starting number, 2: Ending number, 3: Total count. */ |
| 579 |
$currentlyShowingText = sprintf(__('%1$s - %2$s of %3$s', '404-solution'), $start, $end, $num_records); |
| 580 |
$currentPageText = __('Page', '404-solution') . " " . $paged . " " . __('of', '404-solution') . " " . esc_html((string)$total_pages); |
| 581 |
$showRowsText = __('Rows per page:', '404-solution'); |
| 582 |
$showRowsLink = wp_nonce_url($url . '&action=changeItemsPerRow', "abj404_changeItemsPerRow"); |
| 583 |
|
| 584 |
$ajaxAction = 'ajaxUpdatePaginationLinks'; |
| 585 |
$ajaxNonce = wp_create_nonce('abj404_updatePaginationLink'); |
| 586 |
$inflightNonce = wp_create_nonce('abj404_fetchInflightStage'); |
| 587 |
|
| 588 |
$searchFilterControl = '<!--'; |
| 589 |
if ($sub == 'abj404_redirects' || $sub == 'abj404_captured') { |
| 590 |
$searchFilterControl = ''; |
| 591 |
} |
| 592 |
if (!$showSearchFilter) { |
| 593 |
$searchFilterControl = '<!--'; |
| 594 |
} |
| 595 |
|
| 596 |
$filterText = array_key_exists('filterText', $tableOptions) && is_string($tableOptions['filterText']) ? $tableOptions['filterText'] : ''; |
| 597 |
if ($filterText != '') { |
| 598 |
$encodedFilterText = rawurlencode((string)$filterText); |
| 599 |
$nexturl .= '&filterText=' . $encodedFilterText; |
| 600 |
$prevurl .= '&filterText=' . $encodedFilterText; |
| 601 |
$firsturl .= '&filterText=' . $encodedFilterText; |
| 602 |
$lasturl .= '&filterText=' . $encodedFilterText; |
| 603 |
} |
| 604 |
|
| 605 |
// read the html content. |
| 606 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/paginationLinks.html"); |
| 607 |
// do special replacements |
| 608 |
$perpage = array_key_exists('perpage', $tableOptions) && is_scalar($tableOptions['perpage']) ? $tableOptions['perpage'] : ABJ404_OPTION_DEFAULT_PERPAGE; |
| 609 |
$html = $this->f->str_replace(' value="' . $perpage . '"', |
| 610 |
' value="' . $perpage . '" selected', |
| 611 |
$html); |
| 612 |
$html = $this->f->str_replace('{changeItemsPerPage}', $showRowsLink, $html); |
| 613 |
$html = $this->f->str_replace('{showSearchFilter}', $searchFilterControl, $html); |
| 614 |
$html = $this->f->str_replace('{TEXT_BEFORE_LINKS}', $currentlyShowingText, $html); |
| 615 |
$html = $this->f->str_replace('{TEXT_SHOW_ROWS}', $showRowsText, $html); |
| 616 |
// Build navigation buttons: disabled (span) when at the boundary page, link (a) otherwise. |
| 617 |
$onFirstPage = ($paged <= 1); |
| 618 |
$onLastPage = ($paged >= $total_pages); |
| 619 |
|
| 620 |
if ($onFirstPage) { |
| 621 |
$btnFirst = '<span class="abj404-page-btn disabled" aria-label="' . esc_attr__('Go to first page', '404-solution') . '" aria-disabled="true">«</span>'; |
| 622 |
$btnPrev = '<span class="abj404-page-btn disabled" aria-label="' . esc_attr__('Go to previous page', '404-solution') . '" aria-disabled="true">‹</span>'; |
| 623 |
} else { |
| 624 |
$btnFirst = '<a href="' . esc_url($firsturl) . '" class="abj404-page-btn" title="' . esc_attr__('Go to first page', '404-solution') . '" aria-label="' . esc_attr__('Go to first page', '404-solution') . '">«</a>'; |
| 625 |
$btnPrev = '<a href="' . esc_url($prevurl) . '" class="abj404-page-btn" title="' . esc_attr__('Go to previous page', '404-solution') . '" aria-label="' . esc_attr__('Go to previous page', '404-solution') . '">‹</a>'; |
| 626 |
} |
| 627 |
|
| 628 |
if ($onLastPage) { |
| 629 |
$btnNext = '<span class="abj404-page-btn disabled" aria-label="' . esc_attr__('Go to next page', '404-solution') . '" aria-disabled="true">›</span>'; |
| 630 |
$btnLast = '<span class="abj404-page-btn disabled" aria-label="' . esc_attr__('Go to last page', '404-solution') . '" aria-disabled="true">»</span>'; |
| 631 |
} else { |
| 632 |
$btnNext = '<a href="' . esc_url($nexturl) . '" class="abj404-page-btn" title="' . esc_attr__('Go to next page', '404-solution') . '" aria-label="' . esc_attr__('Go to next page', '404-solution') . '">›</a>'; |
| 633 |
$btnLast = '<a href="' . esc_url($lasturl) . '" class="abj404-page-btn" title="' . esc_attr__('Go to last page', '404-solution') . '" aria-label="' . esc_attr__('Go to last page', '404-solution') . '">»</a>'; |
| 634 |
} |
| 635 |
|
| 636 |
$html = $this->f->str_replace('{BTN_FIRST_PAGE}', $btnFirst, $html); |
| 637 |
$html = $this->f->str_replace('{BTN_PREV_PAGE}', $btnPrev, $html); |
| 638 |
$html = $this->f->str_replace('{TEXT_CURRENT_PAGE}', $currentPageText, $html); |
| 639 |
$html = $this->f->str_replace('{BTN_NEXT_PAGE}', $btnNext, $html); |
| 640 |
$html = $this->f->str_replace('{BTN_LAST_PAGE}', $btnLast, $html); |
| 641 |
$html = $this->f->str_replace('{filterText}', esc_attr($filterText), $html); |
| 642 |
$html = $this->f->str_replace('{data-pagination-ajax-url}', esc_attr(admin_url('admin-ajax.php')), $html); |
| 643 |
$html = $this->f->str_replace('{data-pagination-ajax-action}', esc_attr($ajaxAction), $html); |
| 644 |
$html = $this->f->str_replace('{data-pagination-ajax-subpage}', esc_attr($sub), $html); |
| 645 |
$html = $this->f->str_replace('{data-pagination-ajax-nonce}', esc_attr($ajaxNonce), $html); |
| 646 |
$html = $this->f->str_replace('{data-pagination-inflight-nonce}', esc_attr($inflightNonce), $html); |
| 647 |
$html = $this->f->str_replace('{data-pagination-current-signature}', esc_attr($this->getCurrentTableDataSignature($sub)), $html); |
| 648 |
$html = $this->f->str_replace('{data-pagination-current-orderby}', esc_attr((string)$orderby), $html); |
| 649 |
$html = $this->f->str_replace('{data-pagination-current-order}', esc_attr((string)$order), $html); |
| 650 |
$html = $this->f->str_replace('{data-pagination-current-filter}', esc_attr((string)$filter), $html); |
| 651 |
$html = $this->f->str_replace('{data-pagination-current-paged}', esc_attr((string)$paged), $html); |
| 652 |
$rawScoreRange = $tableOptions['score_range'] ?? 'all'; |
| 653 |
$scoreRangeForAttr = is_string($rawScoreRange) ? $rawScoreRange : 'all'; |
| 654 |
$html = $this->f->str_replace('{data-pagination-current-score-range}', esc_attr($scoreRangeForAttr), $html); |
| 655 |
$html = $this->f->str_replace('{data-pagination-current-logsid}', esc_attr((string)$logsid), $html); |
| 656 |
$autoRefresh = (($sub === 'abj404_redirects' || $sub === 'abj404_captured' || $sub === 'abj404_logs') ? '1' : '0'); |
| 657 |
$html = $this->f->str_replace('{data-pagination-auto-refresh}', esc_attr($autoRefresh), $html); |
| 658 |
$html = $this->f->str_replace('{data-pagination-refresh-started-text}', esc_attr(__('Refreshing data in background…', '404-solution')), $html); |
| 659 |
$html = $this->f->str_replace('{data-pagination-refresh-finished-text}', esc_attr(__('Data refreshed', '404-solution')), $html); |
| 660 |
$html = $this->f->str_replace('{data-pagination-refresh-available-text}', esc_attr(__('Refresh available', '404-solution')), $html); |
| 661 |
// constants and translations. |
| 662 |
$html = $this->f->doNormalReplacements($html); |
| 663 |
|
| 664 |
return $html; |
| 665 |
} |
| 666 |
|
| 667 |
/** Output the filters for a tab. |
| 668 |
* @param string $sub |
| 669 |
* @param array<string, mixed> $tableOptions |
| 670 |
* @return string |
| 671 |
*/ |
| 672 |
function getTabFilters($sub, $tableOptions) { |
| 673 |
|
| 674 |
if (empty($tableOptions)) { |
| 675 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 676 |
} |
| 677 |
|
| 678 |
$html = ''; |
| 679 |
$html .= "<span class=\"clearbothdisplayblock\" style=\"clear: both; display: block;\" ></span>"; |
| 680 |
|
| 681 |
$html .= $this->getSubSubSub($sub); |
| 682 |
|
| 683 |
$html .= "</span>"; |
| 684 |
|
| 685 |
return $html; |
| 686 |
} |
| 687 |
|
| 688 |
/** |
| 689 |
* @param string $sub |
| 690 |
* @return string |
| 691 |
*/ |
| 692 |
function getSubSubSub($sub) { |
| 693 |
global $abj404_redirect_types; |
| 694 |
global $abj404_captured_types; |
| 695 |
|
| 696 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 697 |
$filter = isset($tableOptions['filter']) ? intval(is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0) : 0; |
| 698 |
$orderby = isset($tableOptions['orderby']) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 699 |
$order = isset($tableOptions['order']) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 700 |
|
| 701 |
$url = "?page=" . ABJ404_PP; |
| 702 |
if ($sub == 'abj404_captured') { |
| 703 |
$url .= "&subpage=abj404_captured"; |
| 704 |
} else if ($sub == 'abj404_redirects') { |
| 705 |
$url .= "&subpage=abj404_redirects"; |
| 706 |
} else { |
| 707 |
$this->logger->errorMessage("Unexpected sub page: " . $sub); |
| 708 |
} |
| 709 |
|
| 710 |
$url .= "&orderby=" . sanitize_text_field($orderby); |
| 711 |
$url .= "&order=" . sanitize_text_field($order); |
| 712 |
|
| 713 |
if ($sub == 'abj404_redirects') { |
| 714 |
$types = array(ABJ404_STATUS_MANUAL, ABJ404_STATUS_AUTO, ABJ404_STATUS_REGEX); |
| 715 |
if (isset($abj404_redirect_types) && is_array($abj404_redirect_types)) { |
| 716 |
// Some tests/plugins may set this global to a label map; only accept a numeric status list. |
| 717 |
$candidate = array_values($abj404_redirect_types); |
| 718 |
$isNumericList = true; |
| 719 |
foreach ($candidate as $v) { |
| 720 |
if (!is_int($v) && !(is_string($v) && ctype_digit($v))) { |
| 721 |
$isNumericList = false; |
| 722 |
break; |
| 723 |
} |
| 724 |
} |
| 725 |
if ($isNumericList && !empty($candidate)) { |
| 726 |
$types = array_map('intval', $candidate); |
| 727 |
} |
| 728 |
} |
| 729 |
$counts = $this->viewReadService->getRedirectStatusCounts(); |
| 730 |
} else if ($sub == 'abj404_captured') { |
| 731 |
$types = array(ABJ404_STATUS_CAPTURED, ABJ404_STATUS_IGNORED, ABJ404_STATUS_LATER); |
| 732 |
if (isset($abj404_captured_types) && is_array($abj404_captured_types)) { |
| 733 |
$candidate = array_values($abj404_captured_types); |
| 734 |
$isNumericList = true; |
| 735 |
foreach ($candidate as $v) { |
| 736 |
if (!is_int($v) && !(is_string($v) && ctype_digit($v))) { |
| 737 |
$isNumericList = false; |
| 738 |
break; |
| 739 |
} |
| 740 |
} |
| 741 |
if ($isNumericList && !empty($candidate)) { |
| 742 |
$types = array_map('intval', $candidate); |
| 743 |
} |
| 744 |
} |
| 745 |
$counts = $this->viewReadService->getCapturedStatusCounts(); |
| 746 |
} else { |
| 747 |
$this->logger->debugMessage("Unexpected sub type for tab filter: " . $sub); |
| 748 |
$types = array(ABJ404_STATUS_CAPTURED, ABJ404_STATUS_IGNORED, ABJ404_STATUS_LATER); |
| 749 |
$counts = array(); |
| 750 |
} |
| 751 |
|
| 752 |
$class = ""; |
| 753 |
if ($filter == 0) { |
| 754 |
$class = " class=\"current\""; |
| 755 |
} |
| 756 |
|
| 757 |
$html = '<ul class="subsubsub" >'; |
| 758 |
if ($sub != 'abj404_captured') { |
| 759 |
$html .= "<li>"; |
| 760 |
$html .= "<a href=\"" . esc_url($url) . "\"" . $class . ">" . __('All', '404-solution'); |
| 761 |
$html .= " <span class=\"count\">(" . esc_html((string)($counts['all'] ?? 0)) . ")</span>"; |
| 762 |
$html .= "</a>"; |
| 763 |
$html .= "</li>"; |
| 764 |
} |
| 765 |
foreach ($types as $type) { |
| 766 |
$thisurl = $url . "&filter=" . $type; |
| 767 |
|
| 768 |
$class = ""; |
| 769 |
if ($filter == $type) { |
| 770 |
$class = " class=\"current\""; |
| 771 |
} |
| 772 |
|
| 773 |
$recordCount = 0; |
| 774 |
$title = __('Unknown', '404-solution'); |
| 775 |
if ($type == ABJ404_STATUS_MANUAL) { |
| 776 |
$title = __('Manual Redirects', '404-solution'); |
| 777 |
$recordCount = intval($counts['manual'] ?? 0) + intval($counts['regex'] ?? 0); |
| 778 |
} else if ($type == ABJ404_STATUS_AUTO) { |
| 779 |
$title = __('Automatic Redirects', '404-solution'); |
| 780 |
$recordCount = intval($counts['auto'] ?? 0); |
| 781 |
} else if ($type == ABJ404_STATUS_CAPTURED) { |
| 782 |
$title = "Captured URLs"; |
| 783 |
$recordCount = intval($counts['captured'] ?? 0); |
| 784 |
} else if ($type == ABJ404_STATUS_IGNORED) { |
| 785 |
$title = "Ignored 404s"; |
| 786 |
$recordCount = intval($counts['ignored'] ?? 0); |
| 787 |
} else if ($type == ABJ404_STATUS_LATER) { |
| 788 |
$title = "Organize Later"; |
| 789 |
$recordCount = intval($counts['later'] ?? 0); |
| 790 |
} else if ($type == ABJ404_STATUS_REGEX) { |
| 791 |
// don't include a tab here because these are included in the manual redirects. |
| 792 |
continue; |
| 793 |
} else { |
| 794 |
$this->logger->errorMessage("Unrecognized redirect type in View: " . esc_html((string)$type)); |
| 795 |
} |
| 796 |
|
| 797 |
$html .= "<li>"; |
| 798 |
if ($sub != 'abj404_captured' || $type != ABJ404_STATUS_CAPTURED) { |
| 799 |
$html .= " | "; |
| 800 |
} |
| 801 |
$html .= "<a href=\"" . esc_url($thisurl) . "\"" . $class . ">" . ( $title ); |
| 802 |
$html .= " <span class=\"count\">(" . esc_html((string)$recordCount) . ")</span>"; |
| 803 |
$html .= "</a>"; |
| 804 |
$html .= "</li>"; |
| 805 |
} |
| 806 |
|
| 807 |
|
| 808 |
$trashurl = $url . "&filter=" . ABJ404_TRASH_FILTER; |
| 809 |
$class = ""; |
| 810 |
if (($tableOptions['filter'] ?? 0) == ABJ404_TRASH_FILTER) { |
| 811 |
$class = " class=\"current\""; |
| 812 |
} |
| 813 |
$html .= "<li> | "; |
| 814 |
$html .= "<a href=\"" . esc_url($trashurl) . "\"" . $class . ">" . __('Trash', '404-solution'); |
| 815 |
$html .= " <span class=\"count\">(" . esc_html((string)($counts['trash'] ?? 0)) . ")</span>"; |
| 816 |
$html .= "</a>"; |
| 817 |
$html .= "</li>"; |
| 818 |
$html .= "</ul>"; |
| 819 |
$html .= "\n\n<!-- page-form big outer form could go here -->\n\n"; |
| 820 |
|
| 821 |
$oneBigFormActionURL = $this->getBulkOperationsFormURL($sub, $tableOptions); |
| 822 |
$html .= '<form method="POST" name="bulk-operations-form" action="' . $oneBigFormActionURL . '">'; |
| 823 |
|
| 824 |
|
| 825 |
return $html; |
| 826 |
} |
| 827 |
|
| 828 |
|
| 829 |
} |
| 830 |
|