| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Redirects and captured URLs table/list pages. |
| 9 |
*/ |
| 10 |
trait ViewTrait_RedirectsTable { |
| 11 |
|
| 12 |
/** @return void */ |
| 13 |
function echoAdminCapturedURLsPage() { |
| 14 |
$sub = 'abj404_captured'; |
| 15 |
|
| 16 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 17 |
|
| 18 |
// Modern page wrapper |
| 19 |
echo '<div class="abj404-table-page">'; |
| 20 |
|
| 21 |
// Header with page title |
| 22 |
$isSimpleMode = $this->logic->getSettingsMode() === 'simple'; |
| 23 |
echo '<div class="abj404-table-header">'; |
| 24 |
echo '<div class="abj404-table-header-text">'; |
| 25 |
echo '<h2>' . __('Captured 404 URLs', '404-solution') . '</h2>'; |
| 26 |
if ($isSimpleMode) { |
| 27 |
echo '<p class="abj404-guidance-subtitle">' |
| 28 |
. esc_html__('Broken links visitors tried to reach. Create Redirect for important ones, Dismiss the rest.', '404-solution') |
| 29 |
. '</p>'; |
| 30 |
} |
| 31 |
echo '</div>'; |
| 32 |
echo '</div>'; |
| 33 |
|
| 34 |
// Content tabs — counts are placeholders, populated via AJAX |
| 35 |
echo '<div class="abj404-content-tabs" data-tab-counts-placeholder="1">'; |
| 36 |
if ($isSimpleMode) { |
| 37 |
// Simple mode: two tabs — "Needs Review" and "Handled" |
| 38 |
$this->echoContentTab('abj404_captured', ABJ404_STATUS_CAPTURED, __('Needs Review', '404-solution'), '…', $tableOptions); |
| 39 |
$this->echoContentTab('abj404_captured', ABJ404_HANDLED_FILTER, __('Handled', '404-solution'), '…', $tableOptions); |
| 40 |
} else { |
| 41 |
// Advanced mode: full 5-tab layout |
| 42 |
$this->echoContentTab('abj404_captured', 0, __('All', '404-solution'), '…', $tableOptions); |
| 43 |
$this->echoContentTab('abj404_captured', ABJ404_STATUS_CAPTURED, __('Captured', '404-solution'), '…', $tableOptions); |
| 44 |
$this->echoContentTab('abj404_captured', ABJ404_STATUS_IGNORED, __('Ignored', '404-solution'), '…', $tableOptions); |
| 45 |
$this->echoContentTab('abj404_captured', ABJ404_STATUS_LATER, __('Later', '404-solution'), '…', $tableOptions); |
| 46 |
$this->echoContentTab('abj404_captured', ABJ404_TRASH_FILTER, __('Trash', '404-solution'), '…', $tableOptions); |
| 47 |
} |
| 48 |
echo '</div>'; |
| 49 |
|
| 50 |
// Filter bar with server-side search |
| 51 |
$filterText = is_string($tableOptions['filterText'] ?? '') ? (string)($tableOptions['filterText'] ?? '') : ''; |
| 52 |
$perPage = isset($tableOptions['perpage']) ? $tableOptions['perpage'] : 25; |
| 53 |
|
| 54 |
$paginationNonce = wp_create_nonce('abj404_updatePaginationLink'); |
| 55 |
$inflightNonce = wp_create_nonce('abj404_fetchInflightStage'); |
| 56 |
$autoRefresh = '1'; // $sub is always 'abj404_captured' here |
| 57 |
$rawFilter = $tableOptions['filter'] ?? 0; |
| 58 |
$currentFilter = is_scalar($rawFilter) ? $rawFilter : 0; |
| 59 |
$rawOrderBy = $tableOptions['orderby'] ?? ''; |
| 60 |
$currentOrderBy = is_string($rawOrderBy) ? $rawOrderBy : 'url'; |
| 61 |
$rawOrder = $tableOptions['order'] ?? ''; |
| 62 |
$currentOrder = is_string($rawOrder) ? $rawOrder : 'ASC'; |
| 63 |
$rawPaged = $tableOptions['paged'] ?? 1; |
| 64 |
$currentPaged = is_scalar($rawPaged) ? intval($rawPaged) : 1; |
| 65 |
if ($currentPaged < 1) { |
| 66 |
$currentPaged = 1; |
| 67 |
} |
| 68 |
$rawScoreRange = $tableOptions['score_range'] ?? 'all'; |
| 69 |
$currentScoreRange = is_string($rawScoreRange) ? $rawScoreRange : 'all'; |
| 70 |
|
| 71 |
echo '<div class="abj404-filter-bar tablenav"' |
| 72 |
. ' data-pagination-ajax-url="' . esc_attr(admin_url('admin-ajax.php')) . '"' |
| 73 |
. ' data-pagination-ajax-action="ajaxUpdatePaginationLinks"' |
| 74 |
. ' data-pagination-ajax-subpage="' . esc_attr($sub) . '"' |
| 75 |
. ' data-pagination-ajax-nonce="' . esc_attr($paginationNonce) . '"' |
| 76 |
. ' data-pagination-inflight-nonce="' . esc_attr($inflightNonce) . '"' |
| 77 |
. ' data-pagination-current-signature=""' |
| 78 |
. ' data-pagination-current-orderby="' . esc_attr($currentOrderBy) . '"' |
| 79 |
. ' data-pagination-current-order="' . esc_attr($currentOrder) . '"' |
| 80 |
. ' data-pagination-current-filter="' . esc_attr((string)$currentFilter) . '"' |
| 81 |
. ' data-pagination-current-paged="' . esc_attr((string)$currentPaged) . '"' |
| 82 |
. ' data-pagination-current-score-range="' . esc_attr($currentScoreRange) . '"' |
| 83 |
. ' data-pagination-current-logsid=""' |
| 84 |
. ' data-pagination-initial-load="1"' |
| 85 |
. ' data-pagination-auto-refresh="' . esc_attr($autoRefresh) . '"' |
| 86 |
. ' data-pagination-refresh-started-text="' . esc_attr(__('Refreshing data in background…', '404-solution')) . '"' |
| 87 |
. ' data-pagination-refresh-finished-text="' . esc_attr(__('Data refreshed', '404-solution')) . '"' |
| 88 |
. ' data-pagination-refresh-available-text="' . esc_attr(__('Refresh available', '404-solution')) . '">'; |
| 89 |
echo '<div class="abj404-search-box">'; |
| 90 |
echo '<input type="search" name="searchFilter" placeholder="' . esc_attr__('Type to filter URLs... (press Enter)', '404-solution') . '" value="' . esc_attr($filterText) . '" data-lpignore="true">'; |
| 91 |
echo '</div>'; |
| 92 |
echo '<div class="abj404-rows-per-page">'; |
| 93 |
echo '<span>' . esc_html__('Rows per page:', '404-solution') . '</span>'; |
| 94 |
echo '<select class="abj404-filter-select perpage" name="perpage" onchange="paginationLinksChange(this);">'; |
| 95 |
foreach ([10, 25, 50, 100, 200] as $opt) { |
| 96 |
$selected = ($perPage == $opt) ? ' selected' : ''; |
| 97 |
echo '<option value="' . $opt . '"' . $selected . '>' . $opt . '</option>'; |
| 98 |
} |
| 99 |
echo '</select>'; |
| 100 |
echo '</div>'; |
| 101 |
|
| 102 |
// Empty trash button |
| 103 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 104 |
$eturl = "?page=" . ABJ404_PP . "&subpage=abj404_captured&filter=" . ABJ404_TRASH_FILTER; |
| 105 |
$eturl = wp_nonce_url($eturl, 'abj404_bulkProcess'); |
| 106 |
echo '<a href="' . esc_url($eturl) . '&abj404action=emptyCapturedTrash" class="button abj404-empty-trash-btn" onclick="return confirm(\'' . esc_js(__('Are you sure you want to permanently delete all items in trash?', '404-solution')) . '\');">'; |
| 107 |
echo esc_html__('Empty Trash', '404-solution'); |
| 108 |
echo '</a>'; |
| 109 |
} |
| 110 |
echo '<span class="abj404-refresh-status" aria-live="polite"></span>'; |
| 111 |
echo '</div>'; |
| 112 |
|
| 113 |
// Bulk actions bar |
| 114 |
$url = $this->getBulkOperationsFormURL($sub, $tableOptions); |
| 115 |
echo '<div class="abj404-bulk-actions">'; |
| 116 |
echo '<div class="abj404-selection-info"><strong>0</strong> ' . __('selected', '404-solution') . '</div>'; |
| 117 |
echo '<div class="abj404-bulk-buttons">'; |
| 118 |
|
| 119 |
// Bulk action buttons based on current filter and mode |
| 120 |
if ($isSimpleMode) { |
| 121 |
// Simple mode: just "Dismiss" and "Create Redirect" |
| 122 |
echo '<button type="submit" name="abj404action" value="bulkignore" form="bulk-action-form" class="button">' . __('Dismiss', '404-solution') . '</button>'; |
| 123 |
echo '<button type="submit" name="abj404action" value="editRedirect" form="bulk-action-form" class="button">' . __('Create Redirect', '404-solution') . '</button>'; |
| 124 |
} else { |
| 125 |
// Advanced mode: full set of bulk actions |
| 126 |
if ($currentFilter != ABJ404_STATUS_CAPTURED) { |
| 127 |
echo '<button type="submit" name="abj404action" value="bulkcaptured" form="bulk-action-form" class="button">' . __('Mark Captured', '404-solution') . '</button>'; |
| 128 |
} |
| 129 |
if ($currentFilter != ABJ404_STATUS_IGNORED) { |
| 130 |
echo '<button type="submit" name="abj404action" value="bulkignore" form="bulk-action-form" class="button">' . __('Mark Ignored', '404-solution') . '</button>'; |
| 131 |
} |
| 132 |
if ($currentFilter != ABJ404_STATUS_LATER) { |
| 133 |
echo '<button type="submit" name="abj404action" value="bulklater" form="bulk-action-form" class="button">' . __('Organize Later', '404-solution') . '</button>'; |
| 134 |
} |
| 135 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 136 |
echo '<button type="submit" name="abj404action" value="bulktrash" form="bulk-action-form" class="button">' . __('Move to Trash', '404-solution') . '</button>'; |
| 137 |
} |
| 138 |
echo '<button type="submit" name="abj404action" value="editRedirect" form="bulk-action-form" class="button">' . __('Create Redirect', '404-solution') . '</button>'; |
| 139 |
} |
| 140 |
|
| 141 |
echo '</div>'; |
| 142 |
echo '<button type="button" class="abj404-clear-selection" onclick="abj404ClearSelection()">' . __('Clear', '404-solution') . '</button>'; |
| 143 |
echo '</div>'; |
| 144 |
|
| 145 |
// Hidden form for bulk actions |
| 146 |
echo '<form id="bulk-action-form" method="POST" action="' . esc_url($url) . '">'; |
| 147 |
wp_nonce_field('abj404_bulkProcess'); |
| 148 |
|
| 149 |
// Table + pagination placeholders. Full data is loaded via AJAX |
| 150 |
// so initial page render is not blocked on heavy queries. |
| 151 |
echo '<table class="abj404-table" data-table-awaiting-load="1">'; |
| 152 |
echo '<thead><tr><th>' . esc_html__('Loading captured URLs…', '404-solution') . '</th></tr></thead>'; |
| 153 |
echo '<tbody><tr><td class="abj404-empty-message">' . esc_html__('Loading captured URLs…', '404-solution') . '</td></tr></tbody>'; |
| 154 |
echo '</table>'; |
| 155 |
|
| 156 |
// Pagination placeholder (bottom only, matching original layout). |
| 157 |
echo '<div class="abj404-pagination tablenav abj404-pagination-right">'; |
| 158 |
echo '<span class="abj404-refresh-status" aria-live="polite">' . esc_html__('Loading…', '404-solution') . '</span>'; |
| 159 |
echo '</div>'; |
| 160 |
|
| 161 |
echo '</form>'; |
| 162 |
echo '</div><!-- .abj404-table-page -->'; |
| 163 |
} |
| 164 |
|
| 165 |
/** |
| 166 |
* @param string $sub |
| 167 |
* @return string |
| 168 |
*/ |
| 169 |
function getCapturedURLSPageTable($sub) { |
| 170 |
|
| 171 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 172 |
|
| 173 |
// Build column headers with sorting |
| 174 |
$hitsTooltip = $this->getHitsColumnTooltip($tableOptions); |
| 175 |
$columns = array( |
| 176 |
'url' => array('title' => __('URL', '404-solution'), 'orderby' => 'url'), |
| 177 |
'status' => array('title' => __('Status', '404-solution'), 'orderby' => 'status'), |
| 178 |
'hits' => array('title' => __('Hits', '404-solution'), 'orderby' => 'logshits', 'title_attr_html' => $hitsTooltip), |
| 179 |
'timestamp' => array('title' => __('Created', '404-solution'), 'orderby' => 'timestamp', 'class' => 'hide-on-tablet'), |
| 180 |
'last_used' => array('title' => __('Last Used', '404-solution'), 'orderby' => 'last_used', 'title_attr_html' => $hitsTooltip), |
| 181 |
); |
| 182 |
|
| 183 |
$html = '<table class="abj404-table">'; |
| 184 |
$html .= '<thead><tr>'; |
| 185 |
$html .= '<th scope="col"><input type="checkbox" id="select-all-captured" aria-label="' . esc_attr__('Select all', '404-solution') . '"></th>'; |
| 186 |
|
| 187 |
// Generate sortable column headers |
| 188 |
foreach ($columns as $key => $col) { |
| 189 |
$sortUrl = "?page=" . ABJ404_PP . "&subpage=abj404_captured&filter=" . ($tableOptions['filter'] ?? 0); |
| 190 |
$sortUrl .= "&orderby=" . $col['orderby']; |
| 191 |
$sortState = $this->getHeaderSortState($tableOptions, (string)$col['orderby'], false); |
| 192 |
$newOrder = $sortState['nextOrder']; |
| 193 |
$sortUrl .= "&order=" . $newOrder; |
| 194 |
|
| 195 |
$extraClass = isset($col['class']) ? ' ' . esc_attr($col['class']) : ''; |
| 196 |
$sortClass = trim($sortState['thClass'] . $extraClass); |
| 197 |
$sortIndicator = $sortState['indicator']; |
| 198 |
|
| 199 |
$classAttr = $sortClass ? ' class="' . trim($sortClass) . '"' : ''; |
| 200 |
|
| 201 |
// Build tooltip HTML if present |
| 202 |
$tooltipHtml = ''; |
| 203 |
if (isset($col['title_attr_html']) && !empty($col['title_attr_html'])) { |
| 204 |
// Raw HTML (already escaped where needed) |
| 205 |
$tooltipHtml = '<span class="abj404-header-tooltip lefty-tooltip" aria-label="' . esc_attr__('More info', '404-solution') . '">' . |
| 206 |
'<span class="abj404-header-tooltip-icon" aria-hidden="true">?</span>' . |
| 207 |
'<span class="lefty-tooltiptext">' . $col['title_attr_html'] . '</span>' . |
| 208 |
'</span>'; |
| 209 |
} |
| 210 |
|
| 211 |
$html .= '<th scope="col"' . $classAttr . '><a href="' . esc_url($sortUrl) . '">' . esc_html($col['title']) . $sortIndicator . '</a>' . $tooltipHtml . '</th>'; |
| 212 |
} |
| 213 |
|
| 214 |
$html .= '</tr></thead>'; |
| 215 |
$html .= '<tbody id="the-list">'; |
| 216 |
|
| 217 |
$rows = $this->viewReadService->getRedirectsForView($sub, $tableOptions); |
| 218 |
/** @var array<int, array<string, mixed>> $typedRows */ |
| 219 |
$typedRows = array_values(array_filter($rows, 'is_array')); |
| 220 |
$this->rememberTableDataSignature($sub, $typedRows); |
| 221 |
$displayed = 0; |
| 222 |
|
| 223 |
foreach ($typedRows as $row) { |
| 224 |
$displayed++; |
| 225 |
|
| 226 |
$hits = is_scalar($row['logshits'] ?? 0) ? (int)($row['logshits'] ?? 0) : 0; |
| 227 |
|
| 228 |
$last_used = is_scalar($row['last_used'] ?? 0) ? (int)($row['last_used'] ?? 0) : 0; |
| 229 |
$lastUsedClass = ''; |
| 230 |
if ($last_used != 0) { |
| 231 |
$last = (string)wp_date("Y/m/d h:i:s A", abs($last_used)); |
| 232 |
} else { |
| 233 |
$last = __('Never', '404-solution'); |
| 234 |
$lastUsedClass = 'abj404-never-used'; |
| 235 |
} |
| 236 |
|
| 237 |
// Build action links using helper method |
| 238 |
/** @var array<string, mixed> $row */ |
| 239 |
$links = $this->buildTableActionLinks($row, $sub, $tableOptions, true); |
| 240 |
$editlink = ''; |
| 241 |
$logslink = ''; |
| 242 |
$trashlink = ''; |
| 243 |
$trashtitle = ''; |
| 244 |
$deletelink = ''; |
| 245 |
$ignorelink = ''; |
| 246 |
$ignoretitle = ''; |
| 247 |
$laterlink = ''; |
| 248 |
$latertitle = ''; |
| 249 |
$ajaxTrashLink = ''; |
| 250 |
extract($links); |
| 251 |
|
| 252 |
// Determine status badge |
| 253 |
$statusBadgeClass = 'abj404-badge-captured'; |
| 254 |
$statusText = __('Captured', '404-solution'); |
| 255 |
$statusTitle = __('Captured 404 URL', '404-solution'); |
| 256 |
|
| 257 |
if ($row['status'] == ABJ404_STATUS_IGNORED) { |
| 258 |
$statusBadgeClass = 'abj404-badge-ignored'; |
| 259 |
$statusText = __('Ignored', '404-solution'); |
| 260 |
$statusTitle = __('Ignored URL - will not be suggested', '404-solution'); |
| 261 |
} else if ($row['status'] == ABJ404_STATUS_LATER) { |
| 262 |
$statusBadgeClass = 'abj404-badge-later'; |
| 263 |
$statusText = __('Later', '404-solution'); |
| 264 |
$statusTitle = __('Organize Later', '404-solution'); |
| 265 |
} |
| 266 |
|
| 267 |
// Build row action buttons |
| 268 |
$editBtnHTML = ''; |
| 269 |
$logsBtnHTML = ''; |
| 270 |
$trashBtnHTML = ''; |
| 271 |
$deleteBtnHTML = ''; |
| 272 |
$ignoreBtnHTML = ''; |
| 273 |
$laterBtnHTML = ''; |
| 274 |
|
| 275 |
$currentFilter = $tableOptions['filter'] ?? 0; |
| 276 |
$isSimpleModeRow = $this->logic->getSettingsMode() === 'simple'; |
| 277 |
|
| 278 |
if ($isSimpleModeRow) { |
| 279 |
// Simple mode: just "Create Redirect" and "Dismiss" |
| 280 |
$editBtnHTML = '<a href="' . esc_url($editlink) . '" class="abj404-action-link" title="' . esc_attr__('Create Redirect', '404-solution') . '">' |
| 281 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg> ' |
| 282 |
. esc_html__('Create Redirect', '404-solution') . '</a>'; |
| 283 |
if ($row['status'] != ABJ404_STATUS_IGNORED) { |
| 284 |
$ignoreBtnHTML = ' | <a href="' . esc_url($ignorelink) . '" class="abj404-action-link" title="' . esc_attr__('Dismiss', '404-solution') . '">' |
| 285 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"/></svg> ' |
| 286 |
. esc_html__('Dismiss', '404-solution') . '</a>'; |
| 287 |
} |
| 288 |
} else { |
| 289 |
// Advanced mode: full action links |
| 290 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 291 |
$editBtnHTML = '<a href="' . esc_url($editlink) . '" class="abj404-action-link" title="' . esc_attr__('Edit', '404-solution') . '">' |
| 292 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg> ' |
| 293 |
. esc_html__('Edit', '404-solution') . '</a>'; |
| 294 |
} |
| 295 |
|
| 296 |
if (($row['logsid'] ?? 0) > 0) { |
| 297 |
$logsBtnHTML = '<a href="' . esc_url($logslink) . '" class="abj404-action-link" title="' . esc_attr__('View Logs', '404-solution') . '">' |
| 298 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/></svg> ' |
| 299 |
. esc_html__('Logs', '404-solution') . '</a>'; |
| 300 |
} |
| 301 |
|
| 302 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 303 |
$trashBtnHTML = '<a href="' . esc_url($trashlink) . '" class="abj404-action-link danger" title="' . esc_attr($trashtitle) . '">' |
| 304 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg> ' |
| 305 |
. esc_html__('Trash', '404-solution') . '</a>'; |
| 306 |
} |
| 307 |
|
| 308 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 309 |
// Show Restore button |
| 310 |
$trashBtnHTML = '<a href="' . esc_url($trashlink) . '" class="abj404-action-link" title="' . esc_attr__('Restore', '404-solution') . '">' |
| 311 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd"/></svg> ' |
| 312 |
. esc_html__('Restore', '404-solution') . '</a>'; |
| 313 |
$deleteBtnHTML = ' | <a href="' . esc_url($deletelink) . '" class="abj404-action-link danger" title="' . esc_attr__('Delete Permanently', '404-solution') . '" onclick="return confirm(\'' . esc_js(__('Are you sure you want to permanently delete this item?', '404-solution')) . '\');">' |
| 314 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/></svg> ' |
| 315 |
. esc_html__('Delete', '404-solution') . '</a>'; |
| 316 |
} else { |
| 317 |
// Show Ignore and Later buttons (with separators) |
| 318 |
if ($row['status'] != ABJ404_STATUS_IGNORED) { |
| 319 |
$ignoreBtnHTML = ' | <a href="' . esc_url($ignorelink) . '" class="abj404-action-link" title="' . esc_attr($ignoretitle) . '">' |
| 320 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M13.477 14.89A6 6 0 015.11 6.524l8.367 8.368zm1.414-1.414L6.524 5.11a6 6 0 018.367 8.367zM18 10a8 8 0 11-16 0 8 8 0 0116 0z" clip-rule="evenodd"/></svg> ' |
| 321 |
. esc_html__('Ignore', '404-solution') . '</a>'; |
| 322 |
} |
| 323 |
if ($row['status'] != ABJ404_STATUS_LATER) { |
| 324 |
$laterBtnHTML = ' | <a href="' . esc_url($laterlink) . '" class="abj404-action-link" title="' . esc_attr($latertitle) . '">' |
| 325 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clip-rule="evenodd"/></svg> ' |
| 326 |
. esc_html__('Later', '404-solution') . '</a>'; |
| 327 |
} |
| 328 |
} |
| 329 |
} |
| 330 |
|
| 331 |
// Build full URL for visiting |
| 332 |
$capturedRowUrl = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : ''; |
| 333 |
$capturedRowId = is_scalar($row['id'] ?? '') ? (string)($row['id'] ?? '') : ''; |
| 334 |
$fullVisitorURL = esc_url(home_url($capturedRowUrl)); |
| 335 |
|
| 336 |
$tempHtml = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/tableRowCapturedURLs.html"); |
| 337 |
$tempHtml = $this->f->str_replace('{rowid}', $capturedRowId, $tempHtml); |
| 338 |
$tempHtml = $this->f->str_replace('{rowClass}', '', $tempHtml); |
| 339 |
$tempHtml = $this->f->str_replace('{visitorURL}', $fullVisitorURL, $tempHtml); |
| 340 |
$tempHtml = $this->f->str_replace('{url}', esc_html($capturedRowUrl), $tempHtml); |
| 341 |
$tempHtml = $this->f->str_replace('{statusBadgeClass}', $statusBadgeClass, $tempHtml); |
| 342 |
$tempHtml = $this->f->str_replace('{statusTitle}', esc_attr($statusTitle), $tempHtml); |
| 343 |
$tempHtml = $this->f->str_replace('{status}', $statusText, $tempHtml); |
| 344 |
$capturedEngine = is_string($row['engine'] ?? '') ? trim((string)($row['engine'] ?? '')) : ''; |
| 345 |
$capturedEngineHTML = ($capturedEngine !== '') ? '<br><span class="abj404-engine-label">' . esc_html($capturedEngine) . '</span>' : ''; |
| 346 |
$tempHtml = $this->f->str_replace('{engineHTML}', $capturedEngineHTML, $tempHtml); |
| 347 |
$tempHtml = $this->f->str_replace('{hits}', esc_html((string)$hits), $tempHtml); |
| 348 |
$tempHtml = $this->f->str_replace('{created_date}', |
| 349 |
esc_html((string)wp_date("Y/m/d h:i:s A", abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0))), $tempHtml); |
| 350 |
$tempHtml = $this->f->str_replace('{last_used_date}', esc_html($last), $tempHtml); |
| 351 |
$tempHtml = $this->f->str_replace('{lastUsedClass}', $lastUsedClass, $tempHtml); |
| 352 |
$tempHtml = $this->f->str_replace('{editBtnHTML}', $editBtnHTML, $tempHtml); |
| 353 |
$tempHtml = $this->f->str_replace('{logsBtnHTML}', $logsBtnHTML, $tempHtml); |
| 354 |
$tempHtml = $this->f->str_replace('{trashBtnHTML}', $trashBtnHTML, $tempHtml); |
| 355 |
$tempHtml = $this->f->str_replace('{deleteBtnHTML}', $deleteBtnHTML, $tempHtml); |
| 356 |
$tempHtml = $this->f->str_replace('{ignoreBtnHTML}', $ignoreBtnHTML, $tempHtml); |
| 357 |
$tempHtml = $this->f->str_replace('{laterBtnHTML}', $laterBtnHTML, $tempHtml); |
| 358 |
|
| 359 |
$tempHtml = $this->f->doNormalReplacements($tempHtml); |
| 360 |
$html .= $tempHtml; |
| 361 |
} |
| 362 |
|
| 363 |
if ($displayed == 0) { |
| 364 |
$html .= '<tr><td colspan="6" class="abj404-empty-message">' . __('No Captured 404 Records To Display', '404-solution') . '</td></tr>'; |
| 365 |
} |
| 366 |
|
| 367 |
$html .= '</tbody></table>'; |
| 368 |
|
| 369 |
return $html; |
| 370 |
} |
| 371 |
|
| 372 |
/** |
| 373 |
* @return void |
| 374 |
*/ |
| 375 |
function echoAdminRedirectsPage() { |
| 376 |
|
| 377 |
$sub = 'abj404_redirects'; |
| 378 |
|
| 379 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 380 |
|
| 381 |
// Sanitizing unchecked table options |
| 382 |
$tableOptions = $this->logic->sanitizePostData($tableOptions); |
| 383 |
$rawFilter = $tableOptions['filter'] ?? 0; |
| 384 |
$currentFilter = is_scalar($rawFilter) ? $rawFilter : 0; |
| 385 |
|
| 386 |
// Modern table page wrapper |
| 387 |
echo '<div class="abj404-table-page">'; |
| 388 |
|
| 389 |
// Health status summary — placeholder, populated via a dedicated AJAX call |
| 390 |
// (ajaxRefreshHealthBar) so the slow getHighImpactCapturedCount() query |
| 391 |
// never blocks the table's first paint. JS reads the attrs below to fire |
| 392 |
// an independent request as soon as the placeholder is in the DOM. |
| 393 |
$healthBarNonce = wp_create_nonce('abj404_refreshHealthBar'); |
| 394 |
echo '<div class="abj404-health-bar" data-health-bar-placeholder="1"' |
| 395 |
. ' data-health-bar-ajax-url="' . esc_attr(admin_url('admin-ajax.php')) . '"' |
| 396 |
. ' data-health-bar-ajax-action="ajaxRefreshHealthBar"' |
| 397 |
. ' data-health-bar-nonce="' . esc_attr($healthBarNonce) . '">'; |
| 398 |
echo '<span class="abj404-health-dot"></span>'; |
| 399 |
echo esc_html__('Loading status…', '404-solution'); |
| 400 |
echo '</div>'; |
| 401 |
|
| 402 |
// Page header with Add Redirect button |
| 403 |
echo '<div class="abj404-table-header">'; |
| 404 |
echo '<div class="abj404-table-header-text">'; |
| 405 |
echo '<h1>' . esc_html__('Page Redirects', '404-solution') . '</h1>'; |
| 406 |
if ($this->logic->getSettingsMode() === 'simple') { |
| 407 |
echo '<p class="abj404-guidance-subtitle">' |
| 408 |
. esc_html__('The plugin creates these automatically. You only need to act when the status bar above says so.', '404-solution') |
| 409 |
. '</p>'; |
| 410 |
} |
| 411 |
echo '</div>'; |
| 412 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 413 |
echo '<button type="button" class="abj404-btn abj404-btn-primary" data-modal-open="abj404-add-redirect-modal">'; |
| 414 |
echo '+ ' . esc_html__('Add Redirect', '404-solution'); |
| 415 |
echo '</button>'; |
| 416 |
} |
| 417 |
echo '</div>'; |
| 418 |
|
| 419 |
// Content tabs — counts are placeholders, populated via AJAX |
| 420 |
echo '<div class="abj404-content-tabs" data-tab-counts-placeholder="1">'; |
| 421 |
$this->echoContentTab($sub, 0, __('All', '404-solution'), '…', $tableOptions); |
| 422 |
$this->echoContentTab($sub, ABJ404_STATUS_MANUAL, __('Manual', '404-solution'), '…', $tableOptions); |
| 423 |
$this->echoContentTab($sub, ABJ404_STATUS_AUTO, __('Automatic', '404-solution'), '…', $tableOptions); |
| 424 |
$this->echoContentTab($sub, ABJ404_TRASH_FILTER, __('Trash', '404-solution'), '…', $tableOptions); |
| 425 |
echo '</div>'; |
| 426 |
|
| 427 |
// Filter bar with server-side search |
| 428 |
$filterText = is_string($tableOptions['filterText'] ?? '') ? (string)($tableOptions['filterText'] ?? '') : ''; |
| 429 |
$perPage = isset($tableOptions['perpage']) ? $tableOptions['perpage'] : 25; |
| 430 |
|
| 431 |
$paginationNonce = wp_create_nonce('abj404_updatePaginationLink'); |
| 432 |
$inflightNonce = wp_create_nonce('abj404_fetchInflightStage'); |
| 433 |
$autoRefresh = '1'; // $sub is always 'abj404_redirects' here |
| 434 |
$rawOrderBy = $tableOptions['orderby'] ?? ''; |
| 435 |
$currentOrderBy = is_string($rawOrderBy) ? $rawOrderBy : 'url'; |
| 436 |
$rawOrder = $tableOptions['order'] ?? ''; |
| 437 |
$currentOrder = is_string($rawOrder) ? $rawOrder : 'ASC'; |
| 438 |
$rawPaged = $tableOptions['paged'] ?? 1; |
| 439 |
$currentPaged = is_scalar($rawPaged) ? intval($rawPaged) : 1; |
| 440 |
if ($currentPaged < 1) { |
| 441 |
$currentPaged = 1; |
| 442 |
} |
| 443 |
$rawScoreRangeForAttr = $tableOptions['score_range'] ?? 'all'; |
| 444 |
$currentScoreRangeForAttr = is_string($rawScoreRangeForAttr) ? $rawScoreRangeForAttr : 'all'; |
| 445 |
echo '<div class="abj404-filter-bar tablenav"' |
| 446 |
. ' data-pagination-ajax-url="' . esc_attr(admin_url('admin-ajax.php')) . '"' |
| 447 |
. ' data-pagination-ajax-action="ajaxUpdatePaginationLinks"' |
| 448 |
. ' data-pagination-ajax-subpage="' . esc_attr($sub) . '"' |
| 449 |
. ' data-pagination-ajax-nonce="' . esc_attr($paginationNonce) . '"' |
| 450 |
. ' data-pagination-inflight-nonce="' . esc_attr($inflightNonce) . '"' |
| 451 |
. ' data-pagination-current-signature=""' |
| 452 |
. ' data-pagination-current-orderby="' . esc_attr($currentOrderBy) . '"' |
| 453 |
. ' data-pagination-current-order="' . esc_attr($currentOrder) . '"' |
| 454 |
. ' data-pagination-current-filter="' . esc_attr((string)$currentFilter) . '"' |
| 455 |
. ' data-pagination-current-paged="' . esc_attr((string)$currentPaged) . '"' |
| 456 |
. ' data-pagination-current-score-range="' . esc_attr($currentScoreRangeForAttr) . '"' |
| 457 |
. ' data-pagination-current-logsid=""' |
| 458 |
. ' data-pagination-initial-load="1"' |
| 459 |
. ' data-pagination-auto-refresh="' . esc_attr($autoRefresh) . '"' |
| 460 |
. ' data-pagination-refresh-started-text="' . esc_attr(__('Refreshing data in background…', '404-solution')) . '"' |
| 461 |
. ' data-pagination-refresh-finished-text="' . esc_attr(__('Data refreshed', '404-solution')) . '"' |
| 462 |
. ' data-pagination-refresh-available-text="' . esc_attr(__('Refresh available', '404-solution')) . '">'; |
| 463 |
echo '<div class="abj404-search-box">'; |
| 464 |
echo '<input type="search" name="searchFilter" placeholder="' . esc_attr__('Type to filter redirects... (press Enter)', '404-solution') . '" value="' . esc_attr($filterText) . '" data-lpignore="true">'; |
| 465 |
echo '</div>'; |
| 466 |
echo '<div class="abj404-rows-per-page">'; |
| 467 |
echo '<span>' . esc_html__('Rows per page:', '404-solution') . '</span>'; |
| 468 |
echo '<select class="abj404-filter-select perpage" name="perpage" onchange="paginationLinksChange(this);">'; |
| 469 |
foreach ([10, 25, 50, 100, 200] as $opt) { |
| 470 |
$selected = ($perPage == $opt) ? ' selected' : ''; |
| 471 |
echo '<option value="' . $opt . '"' . $selected . '>' . $opt . '</option>'; |
| 472 |
} |
| 473 |
echo '</select>'; |
| 474 |
echo '</div>'; |
| 475 |
|
| 476 |
// Confidence filter dropdown |
| 477 |
$rawScoreRange = $tableOptions['score_range'] ?? 'all'; |
| 478 |
$currentScoreRange = is_string($rawScoreRange) ? $rawScoreRange : 'all'; |
| 479 |
$rawFilter = $tableOptions['filter'] ?? 0; |
| 480 |
$scoreRangeBaseUrl = '?page=' . ABJ404_PP . '&subpage=' . esc_attr($sub) . '&filter=' . (int)(is_scalar($rawFilter) ? $rawFilter : 0); |
| 481 |
echo '<div class="abj404-rows-per-page">'; |
| 482 |
echo '<span>' . esc_html__('Confidence:', '404-solution') . '</span>'; |
| 483 |
$scoreRangeBaseUrlJs = esc_js(esc_url($scoreRangeBaseUrl)); |
| 484 |
echo '<select class="abj404-filter-select" name="score_range_filter" onchange="window.location=\'' . $scoreRangeBaseUrlJs . '&score_range=\'+encodeURIComponent(this.value);">'; |
| 485 |
$scoreRangeOptions = array( |
| 486 |
'all' => __('All', '404-solution'), |
| 487 |
'high' => __('High (≥80%)', '404-solution'), |
| 488 |
'medium' => __('Medium (50–79%)', '404-solution'), |
| 489 |
'low' => __('Low (<50%)', '404-solution'), |
| 490 |
'manual' => __('Manual (no score)', '404-solution'), |
| 491 |
); |
| 492 |
foreach ($scoreRangeOptions as $val => $label) { |
| 493 |
$sel = ($currentScoreRange === $val) ? ' selected' : ''; |
| 494 |
echo '<option value="' . esc_attr($val) . '"' . $sel . '>' . esc_html($label) . '</option>'; |
| 495 |
} |
| 496 |
echo '</select>'; |
| 497 |
echo '</div>'; |
| 498 |
|
| 499 |
echo '<span class="abj404-refresh-status" aria-live="polite"></span>'; |
| 500 |
echo '</div>'; |
| 501 |
|
| 502 |
// Bulk actions bar (hidden by default, shown when items selected) |
| 503 |
$url = $this->getBulkOperationsFormURL($sub, $tableOptions); |
| 504 |
|
| 505 |
// Form must wrap both bulk actions and table so dropdown values are submitted |
| 506 |
echo '<form method="POST" action="' . esc_url($url) . '">'; |
| 507 |
|
| 508 |
echo '<div class="abj404-bulk-actions" id="abj404-bulk-actions">'; |
| 509 |
echo '<span class="abj404-selection-info"><strong>0</strong> ' . esc_html__('redirects selected', '404-solution') . '</span>'; |
| 510 |
echo '<select class="abj404-filter-select" name="abj404action">'; |
| 511 |
echo '<option value="">' . esc_html__('Bulk Actions', '404-solution') . '</option>'; |
| 512 |
if ($currentFilter != ABJ404_STATUS_AUTO) { |
| 513 |
echo '<option value="editRedirect">' . esc_html__('Edit Redirects', '404-solution') . '</option>'; |
| 514 |
} |
| 515 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 516 |
echo '<option value="bulktrash">' . esc_html__('Move to Trash', '404-solution') . '</option>'; |
| 517 |
} |
| 518 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 519 |
echo '<option value="bulk_trash_restore">' . esc_html__('Restore Redirects', '404-solution') . '</option>'; |
| 520 |
echo '<option value="bulk_trash_delete_permanently">' . esc_html__('Delete Permanently', '404-solution') . '</option>'; |
| 521 |
} |
| 522 |
echo '</select>'; |
| 523 |
echo '<button type="submit" class="abj404-btn abj404-btn-primary">' . esc_html__('Apply', '404-solution') . '</button>'; |
| 524 |
echo '<button type="button" class="abj404-btn abj404-btn-secondary abj404-clear-selection" onclick="abj404ClearSelection()">' . esc_html__('Clear Selection', '404-solution') . '</button>'; |
| 525 |
echo '</div>'; |
| 526 |
|
| 527 |
// Table container |
| 528 |
echo '<div class="abj404-table-container">'; |
| 529 |
echo '<table class="abj404-table" data-table-awaiting-load="1">'; |
| 530 |
echo '<thead><tr><th>' . esc_html__('Loading redirects…', '404-solution') . '</th></tr></thead>'; |
| 531 |
echo '<tbody><tr><td class="abj404-empty-message">' . esc_html__('Loading redirects…', '404-solution') . '</td></tr></tbody>'; |
| 532 |
echo '</table>'; |
| 533 |
echo '</div>'; |
| 534 |
|
| 535 |
// Pagination placeholder (bottom only, matching original layout). |
| 536 |
echo '<div class="abj404-pagination tablenav abj404-pagination-right">'; |
| 537 |
echo '<span class="abj404-refresh-status" aria-live="polite">' . esc_html__('Loading…', '404-solution') . '</span>'; |
| 538 |
echo '</div>'; |
| 539 |
|
| 540 |
echo '</form>'; |
| 541 |
|
| 542 |
// Empty trash button (within page container but outside form) |
| 543 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 544 |
$eturl = "?page=" . ABJ404_PP . "&filter=" . ABJ404_TRASH_FILTER . "&subpage=" . $sub; |
| 545 |
$eturl = wp_nonce_url($eturl, "abj404_bulkProcess"); |
| 546 |
echo '<div style="padding: 0 32px 20px;">'; |
| 547 |
echo '<form method="POST" action="' . esc_url($eturl) . '">'; |
| 548 |
echo '<input type="hidden" name="action" value="emptyRedirectTrash">'; |
| 549 |
echo '<button type="submit" class="abj404-btn abj404-btn-secondary" onclick="return confirm(\'' . esc_js(__('Are you sure you want to permanently delete all items in the trash?', '404-solution')) . '\')">'; |
| 550 |
echo esc_html__('Empty Trash', '404-solution'); |
| 551 |
echo '</button>'; |
| 552 |
echo '</form>'; |
| 553 |
echo '</div>'; |
| 554 |
} |
| 555 |
|
| 556 |
echo '</div>'; // end abj404-table-page |
| 557 |
|
| 558 |
// Add redirect modal (outside the main container) |
| 559 |
if (($tableOptions['filter'] ?? 0) != ABJ404_TRASH_FILTER) { |
| 560 |
$this->echoAddRedirectModal($tableOptions); |
| 561 |
} |
| 562 |
} |
| 563 |
|
| 564 |
/** |
| 565 |
* Echo a content tab for the table pages |
| 566 |
*/ |
| 567 |
/** |
| 568 |
* @param string $sub |
| 569 |
* @param int|string $filter |
| 570 |
* @param string $label |
| 571 |
* @param int|string $count |
| 572 |
* @param array<string, mixed> $tableOptions |
| 573 |
* @return void |
| 574 |
*/ |
| 575 |
function echoContentTab($sub, $filter, $label, $count, $tableOptions) { |
| 576 |
$currentFilter = $tableOptions['filter'] ?? 0; |
| 577 |
$isActive = ($currentFilter == $filter) ? 'active' : ''; |
| 578 |
$url = "?page=" . ABJ404_PP . "&subpage=" . $sub; |
| 579 |
if ($filter != 0) { |
| 580 |
$url .= "&filter=" . $filter; |
| 581 |
} |
| 582 |
echo '<a href="' . esc_url($url) . '" class="abj404-content-tab ' . $isActive . '" data-tab-filter="' . esc_attr((string)$filter) . '">'; |
| 583 |
echo esc_html($label); |
| 584 |
echo '<span class="abj404-tab-count">' . esc_html((string)$count) . '</span>'; |
| 585 |
echo '</a>'; |
| 586 |
} |
| 587 |
|
| 588 |
/** |
| 589 |
* Echo the modern Add Redirect modal |
| 590 |
*/ |
| 591 |
/** |
| 592 |
* @param array<string, mixed> $tableOptions |
| 593 |
* @return void |
| 594 |
*/ |
| 595 |
function echoAddRedirectModal($tableOptions) { |
| 596 |
$options = $this->getOptionsWithDefaults(); |
| 597 |
$url = "?page=" . ABJ404_PP . "&subpage=abj404_redirects"; |
| 598 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 599 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 600 |
if (!($orderby == "url" && $order == "ASC")) { |
| 601 |
$url .= "&orderby=" . sanitize_text_field($orderby) . "&order=" . sanitize_text_field($order); |
| 602 |
} |
| 603 |
$filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 604 |
if ($filter != 0) { |
| 605 |
$url .= "&filter=" . $filter; |
| 606 |
} |
| 607 |
$link = wp_nonce_url($url, "abj404addRedirect"); |
| 608 |
$urlPlaceholder = parse_url(get_home_url(), PHP_URL_PATH) . "/example"; |
| 609 |
|
| 610 |
echo '<div class="abj404-modal" id="abj404-add-redirect-modal">'; |
| 611 |
echo '<div class="abj404-modal-content">'; |
| 612 |
echo '<div class="abj404-modal-header">'; |
| 613 |
echo '<h2>' . esc_html__('Add Manual Redirect', '404-solution') . '</h2>'; |
| 614 |
echo '<button type="button" class="abj404-modal-close" onclick="abj404CloseAddRedirectModal()">×</button>'; |
| 615 |
echo '</div>'; |
| 616 |
echo '<form method="POST" action="' . esc_url($link) . '" onsubmit="return validateAddManualRedirectForm(event);">'; |
| 617 |
echo '<input type="hidden" name="action" value="addRedirect">'; |
| 618 |
echo '<div class="abj404-modal-body">'; |
| 619 |
|
| 620 |
// URL field |
| 621 |
echo '<div class="abj404-form-group">'; |
| 622 |
echo '<label class="abj404-form-label">' . esc_html__('URL', '404-solution') . ' *</label>'; |
| 623 |
echo '<input type="text" name="manual_redirect_url" class="abj404-form-input" placeholder="' . esc_attr($urlPlaceholder) . '" required>'; |
| 624 |
echo '<p class="abj404-form-help">' . esc_html__('The URL path that should be redirected (without domain)', '404-solution') . '</p>'; |
| 625 |
echo '<div class="abj404-checkbox-group" style="margin-top: 12px;">'; |
| 626 |
echo '<input type="checkbox" name="is_regex_url" id="modal_is_regex" class="abj404-checkbox-input" value="1">'; |
| 627 |
echo '<label for="modal_is_regex" class="abj404-checkbox-label">' . esc_html__('Treat this URL as a regular expression', '404-solution') . '</label>'; |
| 628 |
echo ' <a href="#" class="abj404-regex-toggle" onclick="abj404ToggleRegexInfo(event)">' . esc_html__('(Explain)', '404-solution') . '</a>'; |
| 629 |
echo '</div>'; |
| 630 |
echo '<div class="abj404-regex-info" style="display: none;">'; |
| 631 |
echo '<p>' . esc_html__('When checked, the text is treated as a regular expression. Note that including a bad regular expression or one that takes too long will break your website. So please use caution and test them elsewhere before trying them here. If you don\'t know what you\'re doing please don\'t use this option (as it\'s not necessary for the functioning of the plugin).', '404-solution') . '</p>'; |
| 632 |
echo '<p><strong>' . esc_html__('Example:', '404-solution') . '</strong> <code>/events/(.+)</code></p>'; |
| 633 |
echo '<p>' . esc_html__('/events/(.+) will match any URL that begins with /events/ and redirect to the specified page. Since a capture group is used, you can use a $1 replacement in the destination string of an external URL.', '404-solution') . '</p>'; |
| 634 |
echo '<p>' . esc_html__('First, all of the normal "exact match" URLs are checked, then all of the regular expression URLs are checked.', '404-solution') . '</p>'; |
| 635 |
echo '</div>'; |
| 636 |
echo '</div>'; |
| 637 |
|
| 638 |
// Redirect to field - using the existing AJAX autocomplete template. |
| 639 |
// The template provides the label via {redirect_to_label}; no separate PHP label needed. |
| 640 |
echo '<div class="abj404-form-group abj404-autocomplete-wrapper">'; |
| 641 |
|
| 642 |
$redirectHtml = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/addManualRedirectPageSearchDropdown.html"); |
| 643 |
$redirectHtml = $this->f->str_replace('{redirect_to_label}', esc_html__('Redirect to', '404-solution') . ' *', $redirectHtml); |
| 644 |
$redirectHtml = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_EMPTY}', |
| 645 |
__('(Type a page name or an external URL)', '404-solution'), $redirectHtml); |
| 646 |
$redirectHtml = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_PAGE}', |
| 647 |
__('(A page has been selected.)', '404-solution'), $redirectHtml); |
| 648 |
$redirectHtml = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_CUSTOM_STRING}', |
| 649 |
__('(A custom string has been entered.)', '404-solution'), $redirectHtml); |
| 650 |
$redirectHtml = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_URL}', |
| 651 |
__('(An external URL will be used.)', '404-solution'), $redirectHtml); |
| 652 |
$redirectHtml = $this->f->str_replace('{REDIRECT_TO_USER_FIELD_WARNING}', '', $redirectHtml); |
| 653 |
$redirectHtml = $this->f->str_replace('{redirectPageTitle}', '', $redirectHtml); |
| 654 |
$redirectHtml = $this->f->str_replace('{pageIDAndType}', '', $redirectHtml); |
| 655 |
$redirectHtml = $this->f->str_replace('{data-url}', |
| 656 |
"admin-ajax.php?action=echoRedirectToPages&includeDefault404Page=true&includeSpecial=true&nonce=" . wp_create_nonce('abj404_ajax'), $redirectHtml); |
| 657 |
$redirectHtml = $this->f->doNormalReplacements($redirectHtml); |
| 658 |
echo $redirectHtml; |
| 659 |
|
| 660 |
echo '</div>'; |
| 661 |
|
| 662 |
// Redirect type — button grid |
| 663 |
$rawDefault = $options['default_redirect'] ?? '301'; |
| 664 |
$defaultCode = is_string($rawDefault) ? $rawDefault : '301'; |
| 665 |
$this->echoRedirectTypeButtonGrid($defaultCode); |
| 666 |
|
| 667 |
// Advanced Options: schedule + conditions |
| 668 |
echo '<details class="abj404-advanced-options">'; |
| 669 |
echo '<summary class="abj404-advanced-options__summary">' . esc_html__('Advanced Options', '404-solution') . '</summary>'; |
| 670 |
echo '<div class="abj404-advanced-options__body">'; |
| 671 |
|
| 672 |
// Active From |
| 673 |
echo '<div class="abj404-form-group">'; |
| 674 |
echo '<label class="abj404-form-label" for="redirect_start_date">' . esc_html__('Active From (optional)', '404-solution') . '</label>'; |
| 675 |
echo '<input type="date" name="redirect_start_date" id="redirect_start_date" class="abj404-form-input" value="">'; |
| 676 |
echo '<p class="abj404-form-help">' . esc_html__('Leave blank to activate immediately', '404-solution') . '</p>'; |
| 677 |
echo '</div>'; |
| 678 |
|
| 679 |
// Active Until |
| 680 |
echo '<div class="abj404-form-group">'; |
| 681 |
echo '<label class="abj404-form-label" for="redirect_end_date">' . esc_html__('Active Until (optional)', '404-solution') . '</label>'; |
| 682 |
echo '<input type="date" name="redirect_end_date" id="redirect_end_date" class="abj404-form-input" value="">'; |
| 683 |
echo '<p class="abj404-form-help">' . esc_html__('Leave blank to never expire', '404-solution') . '</p>'; |
| 684 |
echo '</div>'; |
| 685 |
|
| 686 |
// Conditions |
| 687 |
$this->echoRedirectConditionsSection(); |
| 688 |
|
| 689 |
echo '</div>'; // end .abj404-advanced-options__body |
| 690 |
echo '</details>'; |
| 691 |
|
| 692 |
echo '</div>'; // end .abj404-modal-body |
| 693 |
echo '<div class="abj404-modal-footer">'; |
| 694 |
echo '<button type="button" class="abj404-btn abj404-btn-secondary" onclick="abj404CloseAddRedirectModal()">' . esc_html__('Cancel', '404-solution') . '</button>'; |
| 695 |
echo '<button type="submit" class="abj404-btn abj404-btn-primary">' . esc_html__('Add Redirect', '404-solution') . '</button>'; |
| 696 |
echo '</div>'; |
| 697 |
echo '</form>'; |
| 698 |
echo '</div>'; |
| 699 |
echo '</div>'; |
| 700 |
} |
| 701 |
|
| 702 |
/** |
| 703 |
* Render the redirect type button grid, hidden input, and the JS handler. |
| 704 |
* Used by both the Add Redirect modal and the Edit Redirect page. |
| 705 |
* |
| 706 |
* @param string $selectedCode The currently selected code value (e.g. '301'). |
| 707 |
* @return void |
| 708 |
*/ |
| 709 |
/** |
| 710 |
* Get modern pagination HTML |
| 711 |
*/ |
| 712 |
/** |
| 713 |
* @param string $sub |
| 714 |
* @param array<string, mixed> $tableOptions |
| 715 |
* @return string |
| 716 |
*/ |
| 717 |
function getModernPagination($sub, $tableOptions) { |
| 718 |
$logsid = isset($tableOptions['logsid']) ? intval(is_scalar($tableOptions['logsid']) ? $tableOptions['logsid'] : 0) : 0; |
| 719 |
$filter = isset($tableOptions['filter']) ? intval(is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0) : 0; |
| 720 |
$orderby = isset($tableOptions['orderby']) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 721 |
$order = isset($tableOptions['order']) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 722 |
|
| 723 |
// Use appropriate count method based on sub type |
| 724 |
$logsidInt = (int)$logsid; |
| 725 |
if ($sub == 'abj404_logs') { |
| 726 |
$totalRows = $this->viewReadService->getLogsCount($logsidInt); |
| 727 |
} else { |
| 728 |
$totalRows = $this->viewReadService->getRedirectsForViewCount($sub, $tableOptions); |
| 729 |
} |
| 730 |
$rawPerpage = array_key_exists('perpage', $tableOptions) && is_scalar($tableOptions['perpage']) ? $tableOptions['perpage'] : 25; |
| 731 |
$perPage = intval($rawPerpage); |
| 732 |
if ($perPage <= 0) { |
| 733 |
$perPage = 25; |
| 734 |
} |
| 735 |
$rawPaged = array_key_exists('paged', $tableOptions) && is_scalar($tableOptions['paged']) ? $tableOptions['paged'] : 1; |
| 736 |
$currentPage = intval($rawPaged); |
| 737 |
$totalPages = ceil($totalRows / $perPage); |
| 738 |
|
| 739 |
if ($totalPages <= 1) { |
| 740 |
return ''; |
| 741 |
} |
| 742 |
|
| 743 |
$startItem = (($currentPage - 1) * $perPage) + 1; |
| 744 |
$endItem = min($currentPage * $perPage, $totalRows); |
| 745 |
|
| 746 |
$baseUrl = "?page=" . ABJ404_PP . "&subpage=" . $sub; |
| 747 |
// Include logsid for logs pagination |
| 748 |
if ($sub == 'abj404_logs' && isset($tableOptions['logsid'])) { |
| 749 |
$baseUrl .= "&id=" . $tableOptions['logsid']; |
| 750 |
} |
| 751 |
if ($filter != 0) { |
| 752 |
$baseUrl .= "&filter=" . $filter; |
| 753 |
} |
| 754 |
if (!( $orderby == "url" && $order == "ASC" )) { |
| 755 |
$baseUrl .= "&orderby=" . sanitize_text_field($orderby) . "&order=" . sanitize_text_field($order); |
| 756 |
} |
| 757 |
|
| 758 |
// Different label for logs vs redirects |
| 759 |
$itemLabel = ($sub == 'abj404_logs') ? __('logs', '404-solution') : __('redirects', '404-solution'); |
| 760 |
|
| 761 |
$html = '<div class="abj404-pagination">'; |
| 762 |
$html .= '<div class="abj404-pagination-info">'; |
| 763 |
$html .= sprintf( |
| 764 |
/* translators: %1$d is start item, %2$d is end item, %3$d is total count, %4$s is item type (logs/redirects) */ |
| 765 |
esc_html__('Showing %1$d-%2$d of %3$d %4$s', '404-solution'), |
| 766 |
$startItem, |
| 767 |
$endItem, |
| 768 |
$totalRows, |
| 769 |
$itemLabel |
| 770 |
); |
| 771 |
$html .= '</div>'; |
| 772 |
$html .= '<div class="abj404-pagination-controls">'; |
| 773 |
|
| 774 |
// Previous button |
| 775 |
if ($currentPage > 1) { |
| 776 |
$html .= '<a href="' . esc_url($baseUrl . '&paged=' . ($currentPage - 1)) . '" class="abj404-page-btn">‹</a>'; |
| 777 |
} else { |
| 778 |
$html .= '<span class="abj404-page-btn disabled">‹</span>'; |
| 779 |
} |
| 780 |
|
| 781 |
// Page numbers |
| 782 |
$range = 2; |
| 783 |
for ($i = 1; $i <= $totalPages; $i++) { |
| 784 |
if ($i == 1 || $i == $totalPages || ($i >= $currentPage - $range && $i <= $currentPage + $range)) { |
| 785 |
$activeClass = ($i == $currentPage) ? ' active' : ''; |
| 786 |
$html .= '<a href="' . esc_url($baseUrl . '&paged=' . $i) . '" class="abj404-page-btn' . $activeClass . '">' . $i . '</a>'; |
| 787 |
} elseif ($i == $currentPage - $range - 1 || $i == $currentPage + $range + 1) { |
| 788 |
$html .= '<span class="abj404-page-ellipsis">…</span>'; |
| 789 |
} |
| 790 |
} |
| 791 |
|
| 792 |
// Next button |
| 793 |
if ($currentPage < $totalPages) { |
| 794 |
$html .= '<a href="' . esc_url($baseUrl . '&paged=' . ($currentPage + 1)) . '" class="abj404-page-btn">›</a>'; |
| 795 |
} else { |
| 796 |
$html .= '<span class="abj404-page-btn disabled">›</span>'; |
| 797 |
} |
| 798 |
|
| 799 |
$html .= '</div>'; |
| 800 |
$html .= '</div>'; |
| 801 |
|
| 802 |
return $html; |
| 803 |
} |
| 804 |
|
| 805 |
/** |
| 806 |
* @param string $sub |
| 807 |
* @param array<string, mixed> $tableOptions |
| 808 |
* @return string |
| 809 |
*/ |
| 810 |
function getBulkOperationsFormURL($sub, $tableOptions) { |
| 811 |
$url = "?page=" . ABJ404_PP . "&subpage=" . $sub; |
| 812 |
$filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 813 |
if ($filter != 0) { |
| 814 |
$url .= "&filter=" . $filter; |
| 815 |
} |
| 816 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 817 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 818 |
if (!($orderby == "url" && $order == "ASC")) { |
| 819 |
$url .= "&orderby=" . sanitize_text_field($orderby) . "&order=" . sanitize_text_field($order); |
| 820 |
} |
| 821 |
$url = wp_nonce_url($url, 'abj404_bulkProcess'); |
| 822 |
return $url; |
| 823 |
} |
| 824 |
|
| 825 |
/** |
| 826 |
* @param string $sub |
| 827 |
* @return string |
| 828 |
*/ |
| 829 |
function getAdminRedirectsPageTable($sub) { |
| 830 |
$tableOptions = $this->logic->getTableOptions($sub); |
| 831 |
$columns = $this->buildRedirectsColumnDefs($tableOptions); |
| 832 |
|
| 833 |
$html = "<table class=\"abj404-table\"><thead>"; |
| 834 |
$html .= $this->getTableColumns($sub, $columns); |
| 835 |
$html .= "</thead><tbody id=\"the-list\">"; |
| 836 |
|
| 837 |
$deadDestIds = function_exists('get_transient') ? get_transient('abj404_dead_dest_ids') : false; |
| 838 |
if (!is_array($deadDestIds)) { |
| 839 |
$deadDestIds = array(); |
| 840 |
} |
| 841 |
|
| 842 |
$rows = $this->viewReadService->getRedirectsForView($sub, $tableOptions); |
| 843 |
/** @var array<int, array<string, mixed>> $typedRedirectRows */ |
| 844 |
$typedRedirectRows = array_values(array_filter($rows, 'is_array')); |
| 845 |
$this->rememberTableDataSignature($sub, $typedRedirectRows); |
| 846 |
$displayed = 0; |
| 847 |
$y = 1; |
| 848 |
foreach ($typedRedirectRows as $row) { |
| 849 |
$html .= $this->buildRedirectRowHTML($row, $sub, $tableOptions, $deadDestIds, $y); |
| 850 |
$y = ($y === 0) ? 1 : 0; |
| 851 |
$displayed++; |
| 852 |
} |
| 853 |
if ($displayed == 0) { |
| 854 |
$html .= "<tr>\n" . |
| 855 |
"<td colspan=\"10\" class=\"abj404-empty-state\">" . |
| 856 |
"<div class=\"abj404-empty-state-icon\">📋</div>" . |
| 857 |
"<h3>" . __('No Redirect Records To Display', '404-solution') . "</h3>" . |
| 858 |
"<p>" . __('Redirects will appear here once created.', '404-solution') . "</p>" . |
| 859 |
"</td></tr>"; |
| 860 |
} |
| 861 |
$html .= "</tbody></table>"; |
| 862 |
|
| 863 |
return $html; |
| 864 |
} |
| 865 |
|
| 866 |
/** |
| 867 |
* @param array<string, mixed> $tableOptions |
| 868 |
* @return array<string, array<string, string>> |
| 869 |
*/ |
| 870 |
private function buildRedirectsColumnDefs(array $tableOptions): array { |
| 871 |
$columns = array(); |
| 872 |
$columns['url']['title'] = __('URL', '404-solution'); |
| 873 |
$columns['url']['orderby'] = "url"; |
| 874 |
$columns['url']['width'] = "25%"; |
| 875 |
$columns['status']['title'] = __('Status', '404-solution'); |
| 876 |
$columns['status']['orderby'] = "status"; |
| 877 |
$columns['status']['width'] = "5%"; |
| 878 |
$columns['type']['title'] = __('Type', '404-solution'); |
| 879 |
$columns['type']['orderby'] = "type"; |
| 880 |
$columns['type']['width'] = "10%"; |
| 881 |
$columns['dest']['title'] = __('Destination', '404-solution');; |
| 882 |
$columns['dest']['orderby'] = "final_dest"; |
| 883 |
$columns['dest']['width'] = "22%"; |
| 884 |
$columns['code']['title'] = __('Redirect', '404-solution'); |
| 885 |
$columns['code']['orderby'] = "code"; |
| 886 |
$columns['code']['width'] = "5%"; |
| 887 |
$columns['confidence']['title'] = __('Confidence', '404-solution'); |
| 888 |
$columns['confidence']['orderby'] = "score"; |
| 889 |
$columns['confidence']['width'] = "7%"; |
| 890 |
$columns['confidence']['class'] = "hide-on-tablet"; |
| 891 |
$columns['hits']['title'] = __('Hits', '404-solution'); |
| 892 |
$columns['hits']['orderby'] = "logshits"; |
| 893 |
$columns['hits']['width'] = "7%"; |
| 894 |
$hitsTooltip = $this->getHitsColumnTooltip($tableOptions); |
| 895 |
$columns['hits']['title_attr_html'] = $hitsTooltip; |
| 896 |
$columns['timestamp']['title'] = __('Created', '404-solution'); |
| 897 |
$columns['timestamp']['orderby'] = "timestamp"; |
| 898 |
$columns['timestamp']['width'] = "10%"; |
| 899 |
$columns['timestamp']['class'] = "hide-on-tablet"; |
| 900 |
$columns['last_used']['title'] = __('Last Used', '404-solution'); |
| 901 |
$columns['last_used']['orderby'] = "last_used"; |
| 902 |
$columns['last_used']['width'] = "10%"; |
| 903 |
$columns['last_used']['title_attr_html'] = $hitsTooltip; |
| 904 |
return $columns; |
| 905 |
} |
| 906 |
|
| 907 |
/** |
| 908 |
* @param array<string, mixed> $row |
| 909 |
* @param string $sub |
| 910 |
* @param array<string, mixed> $tableOptions |
| 911 |
* @param array<mixed> $deadDestIds |
| 912 |
* @param int $y |
| 913 |
* @return string |
| 914 |
*/ |
| 915 |
private function buildRedirectRowHTML(array $row, string $sub, array $tableOptions, array $deadDestIds, int $y): string { |
| 916 |
$rowType = $row['type'] ?? 0; |
| 917 |
$rowStatus = $row['status'] ?? 0; |
| 918 |
$rowFinalDest = is_string($row['final_dest'] ?? '') ? (string)($row['final_dest'] ?? '') : ''; |
| 919 |
$destForView = trim(is_scalar($row['dest_for_view'] ?? '') ? (string)($row['dest_for_view'] ?? '') : ''); |
| 920 |
$statusTitle = ''; |
| 921 |
if ($rowStatus == ABJ404_STATUS_MANUAL) { |
| 922 |
$statusTitle = __('Manually created', '404-solution'); |
| 923 |
} else if ($rowStatus == ABJ404_STATUS_AUTO) { |
| 924 |
$statusTitle = __('Automatically created', '404-solution'); |
| 925 |
} else if ($rowStatus == ABJ404_STATUS_REGEX) { |
| 926 |
$statusTitle = __('Regular Expression (Manually Created)', '404-solution'); |
| 927 |
} else { |
| 928 |
$statusTitle = __('Unknown', '404-solution'); |
| 929 |
} |
| 930 |
|
| 931 |
$destLink = $this->resolveRedirectDestLink($rowType, $rowFinalDest); |
| 932 |
$link = $destLink['link']; |
| 933 |
$title = $destLink['title']; |
| 934 |
if ($link != '') { |
| 935 |
$link = "href='" . esc_url($link) . "'"; |
| 936 |
} |
| 937 |
|
| 938 |
$hits = is_scalar($row['logshits'] ?? 0) ? (int)($row['logshits'] ?? 0) : 0; |
| 939 |
|
| 940 |
$last_used = is_scalar($row['last_used'] ?? 0) ? (int)($row['last_used'] ?? 0) : 0; |
| 941 |
if ($last_used != 0) { |
| 942 |
$last = (string)wp_date("Y/m/d h:i:s A", abs($last_used)); |
| 943 |
} else { |
| 944 |
$last = __('Never Used', '404-solution'); |
| 945 |
} |
| 946 |
|
| 947 |
// Build action links using helper method |
| 948 |
/** @var array<string, mixed> $row */ |
| 949 |
$links = $this->buildTableActionLinks($row, $sub, $tableOptions, false); |
| 950 |
$editlink = ''; |
| 951 |
$logslink = ''; |
| 952 |
$trashlink = ''; |
| 953 |
$trashtitle = ''; |
| 954 |
$deletelink = ''; |
| 955 |
$ajaxTrashLink = ''; |
| 956 |
extract($links); |
| 957 |
|
| 958 |
$class = ""; |
| 959 |
if ($y == 0) { |
| 960 |
$class = "alternate"; |
| 961 |
$y++; |
| 962 |
} else { |
| 963 |
$y = 0; |
| 964 |
$class = "normal-non-alternate"; |
| 965 |
} |
| 966 |
// make the entire row red if the destination is missing, doesn't exist, or is unpublished. |
| 967 |
$destinationDoesNotExistClass = ''; |
| 968 |
$destinationIsMissing = false; |
| 969 |
if ($rowType != ABJ404_TYPE_404_DISPLAYED && trim((string)$rowFinalDest) === '') { |
| 970 |
$destinationIsMissing = true; |
| 971 |
$destinationDoesNotExistClass = ' destination-does-not-exist'; |
| 972 |
} |
| 973 |
if (array_key_exists('published_status', $row)) { |
| 974 |
if ($row['published_status'] == '0') { |
| 975 |
$destinationDoesNotExistClass = ' destination-does-not-exist'; |
| 976 |
} |
| 977 |
} |
| 978 |
|
| 979 |
// Check if URL looks like a regex pattern but is not marked as a regex redirect |
| 980 |
$urlLooksLikeRegexClass = ''; |
| 981 |
$rowUrl = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : ''; |
| 982 |
$urlLooksLikeRegex = ABJ_404_Solution_Functions::urlLooksLikeRegex($rowUrl); |
| 983 |
$isRegexStatus = ($rowStatus == ABJ404_STATUS_REGEX); |
| 984 |
if ($urlLooksLikeRegex && !$isRegexStatus) { |
| 985 |
$urlLooksLikeRegexClass = ' url-looks-like-regex'; |
| 986 |
} |
| 987 |
|
| 988 |
$class = $class . $destinationDoesNotExistClass . $urlLooksLikeRegexClass; |
| 989 |
|
| 990 |
// ------------------------------------------- |
| 991 |
// Build modern row action buttons with icons |
| 992 |
$editBtnHTML = ''; |
| 993 |
$logsBtnHTML = ''; |
| 994 |
$trashBtnHTML = ''; |
| 995 |
$deleteBtnHTML = ''; |
| 996 |
|
| 997 |
$currentFilter = $tableOptions['filter'] ?? 0; |
| 998 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 999 |
$editBtnHTML = '<a href="' . esc_url($editlink) . '" class="abj404-action-link" title="{Edit Redirect Details}">' |
| 1000 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z"/></svg> ' |
| 1001 |
. '{Edit}</a>'; |
| 1002 |
$trashBtnHTML = '<a href="#" class="abj404-action-link danger ajax-trash-link" data-url="{ajaxTrashLink}" title="{Trash Redirected URL}">' |
| 1003 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg> ' |
| 1004 |
. '{Trash}</a>'; |
| 1005 |
} |
| 1006 |
if (($row['logsid'] ?? 0) > 0) { |
| 1007 |
$logsBtnHTML = '<a href="{logsLink}" class="abj404-action-link" title="{View Redirect Logs}">' |
| 1008 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd"/></svg> ' |
| 1009 |
. '{Logs}</a>'; |
| 1010 |
} |
| 1011 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 1012 |
$trashBtnHTML = '<a href="{trashLink}" class="abj404-action-link" title="{Restore}">' |
| 1013 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd"/></svg> ' |
| 1014 |
. '{Restore}</a>'; |
| 1015 |
$deleteBtnHTML = ' | <a href="{deletelink}" class="abj404-action-link danger" title="{Delete Redirect Permanently}">' |
| 1016 |
. '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd"/></svg> ' |
| 1017 |
. '{Delete}</a>'; |
| 1018 |
} |
| 1019 |
|
| 1020 |
// Determine badge classes |
| 1021 |
$statusBadgeClass = 'abj404-badge-manual'; |
| 1022 |
if ($rowStatus == ABJ404_STATUS_AUTO) { |
| 1023 |
$statusBadgeClass = 'abj404-badge-auto'; |
| 1024 |
} else if ($rowStatus == ABJ404_STATUS_REGEX) { |
| 1025 |
$statusBadgeClass = 'abj404-badge-regex'; |
| 1026 |
} |
| 1027 |
|
| 1028 |
$rowCode = is_scalar($row['code'] ?? '') ? (string)($row['code'] ?? '') : ''; |
| 1029 |
$codeBadgeMap = array( |
| 1030 |
'301' => 'abj404-badge-301', |
| 1031 |
'302' => 'abj404-badge-302', |
| 1032 |
'307' => 'abj404-badge-307', |
| 1033 |
'308' => 'abj404-badge-308', |
| 1034 |
'410' => 'abj404-badge-410', |
| 1035 |
'451' => 'abj404-badge-451', |
| 1036 |
'0' => 'abj404-badge-meta', |
| 1037 |
); |
| 1038 |
$codeBadgeClass = isset($codeBadgeMap[$rowCode]) ? $codeBadgeMap[$rowCode] : 'abj404-badge-302'; |
| 1039 |
|
| 1040 |
// In Simple mode, show plain language labels instead of numeric codes |
| 1041 |
$codeDisplay = $rowCode; |
| 1042 |
if ($this->logic->getSettingsMode() === 'simple') { |
| 1043 |
$codeDisplay = self::getPlainLanguageCodeLabel($rowCode); |
| 1044 |
} |
| 1045 |
|
| 1046 |
$lastUsedClass = ''; |
| 1047 |
if ($last_used == 0) { |
| 1048 |
$lastUsedClass = 'abj404-never-used'; |
| 1049 |
} |
| 1050 |
|
| 1051 |
$destWarning = $this->resolveDestinationWarnings($row, $rowType, $rowFinalDest, $destForView, $destinationIsMissing, $deadDestIds); |
| 1052 |
$destinationExists = $destWarning['exists']; |
| 1053 |
$destinationDoesNotExist = $destWarning['notExists']; |
| 1054 |
$destinationWarningText = $destWarning['text']; |
| 1055 |
$destForView = $destWarning['destForView']; |
| 1056 |
|
| 1057 |
// URL regex warning visibility |
| 1058 |
$urlIsNormal = ''; |
| 1059 |
$urlLooksLikeRegexWarning = 'display: none;'; |
| 1060 |
if ($urlLooksLikeRegex && !$isRegexStatus) { |
| 1061 |
$urlIsNormal = 'display: none;'; |
| 1062 |
$urlLooksLikeRegexWarning = ''; |
| 1063 |
} |
| 1064 |
|
| 1065 |
// Build full URL with WordPress base path for subdirectory installations |
| 1066 |
$rowId = is_scalar($row['id'] ?? '') ? (string)($row['id'] ?? '') : ''; |
| 1067 |
$fullVisitorURL = esc_url(home_url($rowUrl)); |
| 1068 |
|
| 1069 |
$rowEngine = is_string($row['engine'] ?? '') ? trim((string)($row['engine'] ?? '')) : ''; |
| 1070 |
$engineHTML = ($rowEngine !== '') ? '<br><span class="abj404-engine-label">' . esc_html($rowEngine) . '</span>' : ''; |
| 1071 |
$scoreCell = $this->buildScoreCell($row['score'] ?? null, $rowEngine); |
| 1072 |
$statusForView = is_string($row['status_for_view'] ?? '') ? (string)($row['status_for_view'] ?? '') : ''; |
| 1073 |
$typeForView = is_string($row['type_for_view'] ?? '') ? (string)($row['type_for_view'] ?? '') : ''; |
| 1074 |
|
| 1075 |
return $this->fillRedirectRowTemplate([ |
| 1076 |
'{rowid}' => $rowId, '{rowClass}' => $class, |
| 1077 |
'{visitorURL}' => $fullVisitorURL, '{rowURL}' => esc_html($rowUrl), |
| 1078 |
'{url-is-normal}' => $urlIsNormal, '{url-looks-like-regex}' => $urlLooksLikeRegexWarning, |
| 1079 |
'{editBtnHTML}' => $editBtnHTML, '{logsBtnHTML}' => $logsBtnHTML, |
| 1080 |
'{trashBtnHTML}' => $trashBtnHTML, '{deleteBtnHTML}' => $deleteBtnHTML, |
| 1081 |
'{statusBadgeClass}' => $statusBadgeClass, '{codeBadgeClass}' => $codeBadgeClass, |
| 1082 |
'{lastUsedClass}' => $lastUsedClass, |
| 1083 |
'{link}' => $link, '{title}' => esc_attr($title), |
| 1084 |
'{dest}' => esc_attr($destForView), |
| 1085 |
'{destination-exists}' => $destinationExists, |
| 1086 |
'{destination-does-not-exist}' => $destinationDoesNotExist, |
| 1087 |
'{destination-warning-text}' => $destinationWarningText, |
| 1088 |
'{status}' => $statusForView, '{statusTitle}' => $statusTitle, |
| 1089 |
'{engineHTML}' => $engineHTML, '{rowScore}' => '', '{scoreCell}' => $scoreCell, |
| 1090 |
'{type}' => $typeForView, '{rowCode}' => esc_html($codeDisplay), |
| 1091 |
'{hits}' => esc_html((string)$hits), |
| 1092 |
'{logsLink}' => $logslink, '{trashLink}' => $trashlink, |
| 1093 |
'{ajaxTrashLink}' => $ajaxTrashLink, '{trashtitle}' => $trashtitle, |
| 1094 |
'{deletelink}' => $deletelink, |
| 1095 |
'{created_date}' => esc_html((string)wp_date("Y/m/d h:i:s A", abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0))), |
| 1096 |
'{last_used_date}' => esc_html($last), |
| 1097 |
]); |
| 1098 |
} |
| 1099 |
|
| 1100 |
/** |
| 1101 |
* @param array<string, mixed> $row |
| 1102 |
* @param mixed $rowType |
| 1103 |
* @param string $rowFinalDest |
| 1104 |
* @param string $destForView |
| 1105 |
* @param bool $destinationIsMissing |
| 1106 |
* @param array<mixed> $deadDestIds |
| 1107 |
* @return array{exists: string, notExists: string, text: string, destForView: string} |
| 1108 |
*/ |
| 1109 |
private function resolveDestinationWarnings(array $row, $rowType, string $rowFinalDest, string $destForView, bool $destinationIsMissing, array $deadDestIds): array { |
| 1110 |
$exists = ''; |
| 1111 |
$notExists = 'display: none;'; |
| 1112 |
$text = __("This page doesn't exist or is not published so the redirect won't work.", '404-solution'); |
| 1113 |
if ($destinationIsMissing) { |
| 1114 |
$exists = 'display: none;'; |
| 1115 |
$notExists = ''; |
| 1116 |
$text = __('Destination missing. Edit this redirect and choose a destination.', '404-solution'); |
| 1117 |
if (trim((string)$destForView) === '') { |
| 1118 |
$destForView = __('(Destination missing)', '404-solution'); |
| 1119 |
} |
| 1120 |
} |
| 1121 |
if (array_key_exists('published_status', $row) && $row['published_status'] == '0') { |
| 1122 |
$exists = 'display: none;'; |
| 1123 |
$notExists = ''; |
| 1124 |
if (trim((string)$destForView) === '') { |
| 1125 |
$destForView = __('(Destination unavailable)', '404-solution'); |
| 1126 |
} |
| 1127 |
} |
| 1128 |
$rowIdStr = is_scalar($row['id'] ?? '') ? (string) ($row['id'] ?? '') : ''; |
| 1129 |
if (in_array($rowIdStr, $deadDestIds, true)) { |
| 1130 |
$exists = 'display: none;'; |
| 1131 |
$notExists = ''; |
| 1132 |
$text = __('Destination returned 404 recently, redirect suspended until destination is restored.', '404-solution'); |
| 1133 |
} |
| 1134 |
return ['exists' => $exists, 'notExists' => $notExists, 'text' => $text, 'destForView' => $destForView]; |
| 1135 |
} |
| 1136 |
|
| 1137 |
/** |
| 1138 |
* @param array<string, string> $replacements |
| 1139 |
* @return string |
| 1140 |
*/ |
| 1141 |
private function fillRedirectRowTemplate(array $replacements): string { |
| 1142 |
$htmlTemp = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/tableRowPageRedirects.html"); |
| 1143 |
foreach ($replacements as $placeholder => $value) { |
| 1144 |
$htmlTemp = $this->f->str_replace($placeholder, $value, $htmlTemp); |
| 1145 |
} |
| 1146 |
return $this->f->doNormalReplacements($htmlTemp); |
| 1147 |
} |
| 1148 |
|
| 1149 |
/** |
| 1150 |
* @param mixed $rawScore |
| 1151 |
* @param string $rowEngine |
| 1152 |
* @return string |
| 1153 |
*/ |
| 1154 |
private function buildScoreCell($rawScore, string $rowEngine): string { |
| 1155 |
if ($rawScore !== null && $rawScore !== '') { |
| 1156 |
$scoreNum = (float)(is_numeric($rawScore) ? $rawScore : 0); |
| 1157 |
$scorePct = number_format($scoreNum, 0); |
| 1158 |
if ($scoreNum >= 80) { |
| 1159 |
$scoreBadgeClass = 'abj404-score-high'; |
| 1160 |
} elseif ($scoreNum >= 50) { |
| 1161 |
$scoreBadgeClass = 'abj404-score-medium'; |
| 1162 |
} else { |
| 1163 |
$scoreBadgeClass = 'abj404-score-low'; |
| 1164 |
} |
| 1165 |
return '<span class="abj404-score-badge ' . $scoreBadgeClass . '">' . esc_html($scorePct) . '%</span>'; |
| 1166 |
} |
| 1167 |
$noScoreTitle = ($rowEngine !== '') |
| 1168 |
? __('No confidence score for this engine', '404-solution') |
| 1169 |
: __('Manual redirect, no confidence score', '404-solution'); |
| 1170 |
return '<span class="abj404-score-manual" title="' . esc_attr($noScoreTitle) . '">—</span>'; // allow-em-dash: em-dash used as visual placeholder in HTML table cell |
| 1171 |
} |
| 1172 |
|
| 1173 |
/** |
| 1174 |
* @param mixed $rowType |
| 1175 |
* @param string $rowFinalDest |
| 1176 |
* @return array{link: string, title: string} |
| 1177 |
*/ |
| 1178 |
private function resolveRedirectDestLink($rowType, string $rowFinalDest): array { |
| 1179 |
$link = ''; |
| 1180 |
$title = __('Visit', '404-solution') . ' '; |
| 1181 |
if ($rowType == ABJ404_TYPE_EXTERNAL) { |
| 1182 |
if ($rowFinalDest !== '') { |
| 1183 |
$link = $rowFinalDest; |
| 1184 |
$title .= $rowFinalDest; |
| 1185 |
} |
| 1186 |
} else if ($rowType == ABJ404_TYPE_CAT) { |
| 1187 |
if ($rowFinalDest !== '') { |
| 1188 |
$permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($rowFinalDest . '|' . ABJ404_TYPE_CAT, 0); |
| 1189 |
$link = is_string($permalink['link']) ? $permalink['link'] : ''; |
| 1190 |
$title .= __('Category:', '404-solution') . ' ' . (is_string($permalink['title']) ? $permalink['title'] : ''); |
| 1191 |
} |
| 1192 |
} else if ($rowType == ABJ404_TYPE_TAG) { |
| 1193 |
if ($rowFinalDest !== '') { |
| 1194 |
$permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($rowFinalDest . '|' . ABJ404_TYPE_TAG, 0); |
| 1195 |
$link = is_string($permalink['link']) ? $permalink['link'] : ''; |
| 1196 |
$title .= __('Tag:', '404-solution') . ' ' . (is_string($permalink['title']) ? $permalink['title'] : ''); |
| 1197 |
} |
| 1198 |
} else if ($rowType == ABJ404_TYPE_HOME) { |
| 1199 |
$permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($rowFinalDest . '|' . ABJ404_TYPE_HOME, 0); |
| 1200 |
$link = is_string($permalink['link']) ? $permalink['link'] : ''; |
| 1201 |
$title .= __('Home Page:', '404-solution') . ' ' . (is_string($permalink['title']) ? $permalink['title'] : ''); |
| 1202 |
} else if ($rowType == ABJ404_TYPE_POST) { |
| 1203 |
if ($rowFinalDest !== '') { |
| 1204 |
$permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($rowFinalDest . '|' . ABJ404_TYPE_POST, 0); |
| 1205 |
$link = is_string($permalink['link']) ? $permalink['link'] : ''; |
| 1206 |
$title .= is_string($permalink['title']) ? $permalink['title'] : ''; |
| 1207 |
} |
| 1208 |
} else if ($rowType == ABJ404_TYPE_404_DISPLAYED) { |
| 1209 |
$permalink = ABJ_404_Solution_Functions::permalinkInfoToArray($rowFinalDest . '|' . ABJ404_TYPE_404_DISPLAYED, 0); |
| 1210 |
$link = is_string($permalink['link']) ? $permalink['link'] : ''; |
| 1211 |
$title .= is_string($permalink['title']) ? $permalink['title'] : ''; |
| 1212 |
if ($rowFinalDest == '0') { |
| 1213 |
$link = ''; |
| 1214 |
} |
| 1215 |
} else { |
| 1216 |
$this->logger->errorMessage('Unexpected row type while displaying table: ' . $rowType); |
| 1217 |
} |
| 1218 |
return ['link' => $link, 'title' => $title]; |
| 1219 |
} |
| 1220 |
|
| 1221 |
/** |
| 1222 |
* @param array<string, mixed> $tableOptions |
| 1223 |
* @return void |
| 1224 |
*/ |
| 1225 |
function echoAddManualRedirect($tableOptions) { |
| 1226 |
|
| 1227 |
$options = $this->getOptionsWithDefaults(); |
| 1228 |
|
| 1229 |
$url = "?page=" . ABJ404_PP . "&subpage=abj404_redirects"; |
| 1230 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 1231 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 1232 |
if (!($orderby == "url" && $order == "ASC")) { |
| 1233 |
$url .= "&orderby=" . sanitize_text_field($orderby) . "&order=" . sanitize_text_field($order); |
| 1234 |
} |
| 1235 |
$filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 1236 |
if ($filter != 0) { |
| 1237 |
$url .= "&filter=" . $filter; |
| 1238 |
} |
| 1239 |
$link = wp_nonce_url($url, "abj404addRedirect"); |
| 1240 |
|
| 1241 |
$urlPlaceholder = parse_url(get_home_url(), PHP_URL_PATH) . "/example"; |
| 1242 |
if (isset($_POST['url']) && $_POST['url'] != '') { |
| 1243 |
$postedURL = esc_url($_POST['url']); |
| 1244 |
} else { |
| 1245 |
$postedURL = $urlPlaceholder; |
| 1246 |
} |
| 1247 |
|
| 1248 |
$selected301 = ($options['default_redirect'] == '301') ? ' selected ' : ''; |
| 1249 |
$selected302 = ($options['default_redirect'] == '302') ? ' selected ' : ''; |
| 1250 |
$selected307 = ($options['default_redirect'] == '307') ? ' selected ' : ''; |
| 1251 |
$selected308 = ($options['default_redirect'] == '308') ? ' selected ' : ''; |
| 1252 |
$selected410 = ''; |
| 1253 |
$selected451 = ''; |
| 1254 |
$selected0 = ''; |
| 1255 |
|
| 1256 |
// read the html content. |
| 1257 |
$html = ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/addManualRedirectTop.html"); |
| 1258 |
$html .= ABJ_404_Solution_Functions::readFileContents(__DIR__ . |
| 1259 |
"/html/addManualRedirectPageSearchDropdown.html"); |
| 1260 |
|
| 1261 |
$html = $this->f->str_replace('{redirect_to_label}', __('Redirect to', '404-solution'), $html); |
| 1262 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_EMPTY}', |
| 1263 |
__('(Type a page name or an external URL)', '404-solution'), $html); |
| 1264 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_PAGE}', |
| 1265 |
__('(A page has been selected.)', '404-solution'), $html); |
| 1266 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_CUSTOM_STRING}', |
| 1267 |
__('(A custom string has been entered.)', '404-solution'), $html); |
| 1268 |
$html = $this->f->str_replace('{TOOLTIP_POPUP_EXPLANATION_URL}', |
| 1269 |
__('(An external URL will be used.)', '404-solution'), $html); |
| 1270 |
$html = $this->f->str_replace('{REDIRECT_TO_USER_FIELD_WARNING}', '', $html); |
| 1271 |
$html = $this->f->str_replace('{redirectPageTitle}', '', $html); |
| 1272 |
$html = $this->f->str_replace('{pageIDAndType}', '', $html); |
| 1273 |
$html = $this->f->str_replace('{redirectPageTitle}', '', $html); |
| 1274 |
$html = $this->f->str_replace('{data-url}', |
| 1275 |
"admin-ajax.php?action=echoRedirectToPages&includeDefault404Page=true&includeSpecial=true&nonce=" . wp_create_nonce('abj404_ajax'), $html); |
| 1276 |
|
| 1277 |
$html .= ABJ_404_Solution_Functions::readFileContents(__DIR__ . "/html/addManualRedirectBottom.html"); |
| 1278 |
$html = $this->f->str_replace('{addManualRedirectAction}', $link, $html); |
| 1279 |
$html = $this->f->str_replace('{urlPlaceholder}', esc_attr($urlPlaceholder), $html); |
| 1280 |
$html = $this->f->str_replace('{postedURL}', esc_attr($postedURL), $html); |
| 1281 |
$html = $this->f->str_replace('{301selected}', $selected301, $html); |
| 1282 |
$html = $this->f->str_replace('{302selected}', $selected302, $html); |
| 1283 |
$html = $this->f->str_replace('{307selected}', $selected307, $html); |
| 1284 |
$html = $this->f->str_replace('{308selected}', $selected308, $html); |
| 1285 |
$html = $this->f->str_replace('{410selected}', $selected410, $html); |
| 1286 |
$html = $this->f->str_replace('{451selected}', $selected451, $html); |
| 1287 |
$html = $this->f->str_replace('{0selected}', $selected0, $html); |
| 1288 |
|
| 1289 |
// constants and translations. |
| 1290 |
$html = $this->f->doNormalReplacements($html); |
| 1291 |
|
| 1292 |
echo $html; |
| 1293 |
} |
| 1294 |
|
| 1295 |
/** This is used both to add and to edit a redirect. |
| 1296 |
* @param string $destination |
| 1297 |
* @param string $codeselected |
| 1298 |
* @param string $label |
| 1299 |
*/ |
| 1300 |
/** |
| 1301 |
* @param string $destination |
| 1302 |
* @param string $codeselected |
| 1303 |
* @param string $label |
| 1304 |
* @param string|null $source_page |
| 1305 |
* @param string|null $filter |
| 1306 |
* @param string|null $orderby |
| 1307 |
* @param string|null $order |
| 1308 |
* @param string $startDate |
| 1309 |
* @param string $endDate |
| 1310 |
* @return void |
| 1311 |
*/ |
| 1312 |
function echoEditRedirect($destination, $codeselected, $label, $source_page = null, $filter = null, $orderby = null, $order = null, $startDate = '', $endDate = '') { |
| 1313 |
// Redirect type — button grid with hidden input |
| 1314 |
$this->echoRedirectTypeButtonGrid((string)$codeselected); |
| 1315 |
|
| 1316 |
// Advanced Options: Active From/Until + Conditions (collapsed by default) |
| 1317 |
{ |
| 1318 |
$redirectId = 0; |
| 1319 |
if (isset($_GET['id']) && $this->f->regexMatch('[0-9]+', (string)$_GET['id'])) { |
| 1320 |
$redirectId = absint($_GET['id']); |
| 1321 |
} elseif (isset($_POST['id']) && $this->f->regexMatch('[0-9]+', (string)$_POST['id'])) { |
| 1322 |
$redirectId = absint($_POST['id']); |
| 1323 |
} |
| 1324 |
$hasExistingConditions = ($redirectId > 0) && !empty($this->redirectsRepository->getRedirectConditions($redirectId)); |
| 1325 |
$hasAdvancedValues = ($startDate !== '' || $endDate !== '' || $hasExistingConditions); |
| 1326 |
$openAttr = $hasAdvancedValues ? ' open' : ''; |
| 1327 |
echo '<details class="abj404-advanced-options"' . $openAttr . '>'; |
| 1328 |
echo '<summary class="abj404-advanced-options__summary">' . esc_html__('Advanced Options', '404-solution') . '</summary>'; |
| 1329 |
echo '<div class="abj404-advanced-options__body">'; |
| 1330 |
|
| 1331 |
// Active From |
| 1332 |
echo '<div class="abj404-form-group">'; |
| 1333 |
echo '<label class="abj404-form-label" for="redirect_start_date">' . esc_html__('Active From (optional)', '404-solution') . '</label>'; |
| 1334 |
echo '<input type="date" name="redirect_start_date" id="redirect_start_date" class="abj404-form-input" value="' . esc_attr($startDate) . '">'; |
| 1335 |
echo '<p class="abj404-form-help">' . esc_html__('Leave blank to activate immediately', '404-solution') . '</p>'; |
| 1336 |
echo '</div>'; |
| 1337 |
|
| 1338 |
// Active Until |
| 1339 |
echo '<div class="abj404-form-group">'; |
| 1340 |
echo '<label class="abj404-form-label" for="redirect_end_date">' . esc_html__('Active Until (optional)', '404-solution') . '</label>'; |
| 1341 |
echo '<input type="date" name="redirect_end_date" id="redirect_end_date" class="abj404-form-input" value="' . esc_attr($endDate) . '">'; |
| 1342 |
echo '<p class="abj404-form-help">' . esc_html__('Leave blank to never expire', '404-solution') . '</p>'; |
| 1343 |
echo '</div>'; |
| 1344 |
|
| 1345 |
// Conditions |
| 1346 |
$this->echoRedirectConditionsSection(); |
| 1347 |
|
| 1348 |
echo '</div>'; // end abj404-advanced-options__body |
| 1349 |
echo '</details>'; |
| 1350 |
} |
| 1351 |
|
| 1352 |
// Button group |
| 1353 |
echo '<div class="abj404-button-group">'; |
| 1354 |
|
| 1355 |
// Cancel button |
| 1356 |
$cancelUrl = '?page=' . ABJ404_PP; |
| 1357 |
if ($source_page) { |
| 1358 |
$cancelUrl .= '&subpage=' . esc_attr($source_page); |
| 1359 |
} |
| 1360 |
if ($filter !== null) { |
| 1361 |
$cancelUrl .= '&filter=' . esc_attr($filter); |
| 1362 |
} |
| 1363 |
if ($orderby !== null) { |
| 1364 |
$cancelUrl .= '&orderby=' . esc_attr($orderby); |
| 1365 |
} |
| 1366 |
if ($order !== null) { |
| 1367 |
$cancelUrl .= '&order=' . esc_attr($order); |
| 1368 |
} |
| 1369 |
echo '<a href="' . esc_url($cancelUrl) . '" class="abj404-btn abj404-btn-secondary">' . esc_html__('Cancel', '404-solution') . '</a>'; |
| 1370 |
|
| 1371 |
// Submit button |
| 1372 |
echo '<button type="submit" class="abj404-btn abj404-btn-primary">' . esc_html($label) . '</button>'; |
| 1373 |
echo '</div>'; |
| 1374 |
} |
| 1375 |
|
| 1376 |
/** |
| 1377 |
* @param string $currentlySelected |
| 1378 |
* @return string |
| 1379 |
*/ |
| 1380 |
function echoRedirectDestinationOptionsDefaults($currentlySelected) { |
| 1381 |
$content = ""; |
| 1382 |
$content .= "\n" . '<optgroup label="' . __('Special', '404-solution') . '">' . "\n"; |
| 1383 |
|
| 1384 |
$selected = ""; |
| 1385 |
if ($currentlySelected == ABJ404_TYPE_EXTERNAL) { |
| 1386 |
$selected = " selected"; |
| 1387 |
} |
| 1388 |
$content .= "\n<option value=\"" . ABJ404_TYPE_EXTERNAL . "|" . ABJ404_TYPE_EXTERNAL . "\"" . $selected . ">" . |
| 1389 |
__('External Page', '404-solution') . "</option>"; |
| 1390 |
|
| 1391 |
if ($currentlySelected == ABJ404_TYPE_HOME) { |
| 1392 |
$selected = " selected"; |
| 1393 |
} else { |
| 1394 |
$selected = ""; |
| 1395 |
} |
| 1396 |
$content .= "\n<option value=\"" . ABJ404_TYPE_HOME . "|" . ABJ404_TYPE_HOME . "\"" . $selected . ">" . |
| 1397 |
__('Home Page', '404-solution') . "</option>"; |
| 1398 |
|
| 1399 |
$content .= "\n" . '</optgroup>' . "\n"; |
| 1400 |
|
| 1401 |
return $content; |
| 1402 |
} |
| 1403 |
|
| 1404 |
} |
| 1405 |
|