| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Renders the Captured 404 URLs admin page wrapper/chrome: the subsubsub |
| 9 |
* filter row, subtitle, empty-trash button, bulk-action buttons, bulk-form |
| 10 |
* action/nonce, and the warmup placeholder that surrounds the AJAX-refreshed |
| 11 |
* table body. Extracted from View_CapturedURLsTable because page-chrome |
| 12 |
* rendering (page-level wrapper, filters, bulk actions) is a distinct |
| 13 |
* responsibility from table-body-row rendering. |
| 14 |
*/ |
| 15 |
class ABJ_404_Solution_CapturedPageChromeRenderer { |
| 16 |
|
| 17 |
/** @var ABJ_404_Solution_PluginLogic */ |
| 18 |
private $logic; |
| 19 |
|
| 20 |
/** @var ABJ_404_Solution_View_ListTableChrome */ |
| 21 |
private $listTableChrome; |
| 22 |
|
| 23 |
/** @var ABJ_404_Solution_Functions */ |
| 24 |
private $f; |
| 25 |
|
| 26 |
public function __construct( |
| 27 |
ABJ_404_Solution_PluginLogic $logic, |
| 28 |
ABJ_404_Solution_View_ListTableChrome $listTableChrome, |
| 29 |
ABJ_404_Solution_Functions $functions |
| 30 |
) { |
| 31 |
$this->logic = $logic; |
| 32 |
$this->listTableChrome = $listTableChrome; |
| 33 |
$this->f = $functions; |
| 34 |
} |
| 35 |
|
| 36 |
private function tpl(string $name): string { |
| 37 |
$raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false); |
| 38 |
return rtrim((string)$raw, "\n"); |
| 39 |
} |
| 40 |
|
| 41 |
/** @param array<string,string> $vars */ |
| 42 |
private function fillTpl(string $name, array $vars): string { |
| 43 |
return (string)$this->f->str_replace(array_keys($vars), array_values($vars), $this->tpl($name)); |
| 44 |
} |
| 45 |
|
| 46 |
public function render(): string { |
| 47 |
$sub = 'abj404_captured'; |
| 48 |
|
| 49 |
$tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub); |
| 50 |
$isSimpleMode = abj_service('settings_mode_preference')->getMode() === 'simple'; |
| 51 |
|
| 52 |
$subsubsubHtml = $this->buildSubsubsubHtml($sub, $isSimpleMode, $tableOptions); |
| 53 |
$display = $this->readDisplayOptions($tableOptions); |
| 54 |
|
| 55 |
$paginationNonce = wp_create_nonce('abj404_updatePaginationLink'); |
| 56 |
$lazyBackfillNonce = wp_create_nonce('abj404_runLazyBackfill'); |
| 57 |
|
| 58 |
$subtitleHtml = $this->buildSubtitleHtml($isSimpleMode); |
| 59 |
$emptyTrashHtml = $this->buildEmptyTrashButtonHtml($display['currentFilter']); |
| 60 |
$bulkButtons = $this->buildBulkButtonsHtml($isSimpleMode, $display['currentFilter']); |
| 61 |
|
| 62 |
// Bulk form action URL + nonce field |
| 63 |
$formAction = $this->listTableChrome->getBulkOperationsFormURL($sub, $tableOptions); |
| 64 |
ob_start(); |
| 65 |
wp_nonce_field('abj404_bulkProcess'); |
| 66 |
$nonceField = (string)ob_get_clean(); |
| 67 |
|
| 68 |
$warmup = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . "/html/tableWarmupPlaceholder.html"); |
| 69 |
|
| 70 |
$refresh = $this->listTableChrome->paginationRefreshStrings(); |
| 71 |
|
| 72 |
return $this->fillTpl('viewRedirectsTableCapturedPageWrapper.html', array( |
| 73 |
'{captured_title}' => __('Captured 404 URLs', '404-solution'), |
| 74 |
'{subtitle}' => $subtitleHtml, |
| 75 |
'{subsubsub}' => $subsubsubHtml, |
| 76 |
'{data-pagination-ajax-url}' => esc_attr(admin_url('admin-ajax.php')), |
| 77 |
'{data-lazy-backfill-ajax-url}' => esc_attr(admin_url('admin-ajax.php')), |
| 78 |
'{data-lazy-backfill-nonce}' => esc_attr($lazyBackfillNonce), |
| 79 |
'{data-pagination-ajax-subpage}' => esc_attr($sub), |
| 80 |
'{data-pagination-ajax-nonce}' => esc_attr($paginationNonce), |
| 81 |
'{data-pagination-current-orderby}' => esc_attr($display['currentOrderBy']), |
| 82 |
'{data-pagination-current-order}' => esc_attr($display['currentOrder']), |
| 83 |
'{data-pagination-current-filter}' => esc_attr((string)$display['currentFilter']), |
| 84 |
'{data-pagination-current-paged}' => esc_attr((string)$display['currentPaged']), |
| 85 |
'{data-pagination-current-score-range}' => esc_attr($display['currentScoreRange']), |
| 86 |
'{data-pagination-auto-refresh}' => esc_attr('1'), |
| 87 |
'{data-pagination-refresh-available-text}' => esc_attr($refresh['available']), |
| 88 |
'{search_placeholder}' => esc_attr__('Type to filter URLs... (press Enter)', '404-solution'), |
| 89 |
'{filter_text}' => esc_attr($display['filterText']), |
| 90 |
'{rows_per_page_label}' => esc_html__('Rows per page:', '404-solution'), |
| 91 |
'{perpage_options}' => $this->listTableChrome->buildPerpageOptions($display['perPage']), |
| 92 |
'{empty_trash_button}' => $emptyTrashHtml, |
| 93 |
'{selected_label}' => __('selected', '404-solution'), |
| 94 |
'{bulk_buttons}' => $bulkButtons, |
| 95 |
'{clear_label}' => __('Clear', '404-solution'), |
| 96 |
'{form_action}' => esc_url($formAction), |
| 97 |
'{nonce_field}' => $nonceField, |
| 98 |
'{warmup_placeholder}' => $warmup, |
| 99 |
'{loading_badge_text}' => esc_html__('Loading...', '404-solution'), |
| 100 |
)); |
| 101 |
} |
| 102 |
|
| 103 |
/** @param array<string, mixed> $tableOptions */ |
| 104 |
private function buildSubsubsubHtml(string $sub, bool $isSimpleMode, array $tableOptions): string { |
| 105 |
// Filter row (native WP subsubsub). Counts are placeholders, populated via AJAX. |
| 106 |
if ($isSimpleMode) { |
| 107 |
$items = array( |
| 108 |
array(ABJ404_STATUS_CAPTURED, __('Needs Review', '404-solution')), |
| 109 |
array(ABJ404_HANDLED_FILTER, __('Handled', '404-solution')), |
| 110 |
); |
| 111 |
} else { |
| 112 |
$items = array( |
| 113 |
array(0, __('All', '404-solution')), |
| 114 |
array(ABJ404_STATUS_CAPTURED, __('Captured', '404-solution')), |
| 115 |
array(ABJ404_STATUS_IGNORED, __('Ignored', '404-solution')), |
| 116 |
array(ABJ404_STATUS_LATER, __('Later', '404-solution')), |
| 117 |
array(ABJ404_TRASH_FILTER, __('Trash', '404-solution')), |
| 118 |
); |
| 119 |
} |
| 120 |
return $this->listTableChrome->buildSubsubsubFilters($sub, $items, $tableOptions); |
| 121 |
} |
| 122 |
|
| 123 |
/** |
| 124 |
* Reads the raw table options into typed, defaulted display values. |
| 125 |
* |
| 126 |
* @param array<string, mixed> $tableOptions |
| 127 |
* @return array{filterText: string, perPage: int, currentFilter: bool|float|int|string, currentOrderBy: string, currentOrder: string, currentPaged: int, currentScoreRange: string} |
| 128 |
*/ |
| 129 |
private function readDisplayOptions(array $tableOptions): array { |
| 130 |
$rawPerPage = $tableOptions['perpage'] ?? 25; |
| 131 |
$rawFilter = $tableOptions['filter'] ?? 0; |
| 132 |
$rawOrderBy = $tableOptions['orderby'] ?? ''; |
| 133 |
$rawOrder = $tableOptions['order'] ?? ''; |
| 134 |
$rawPaged = $tableOptions['paged'] ?? 1; |
| 135 |
$currentPaged = is_scalar($rawPaged) ? intval($rawPaged) : 1; |
| 136 |
if ($currentPaged < 1) { |
| 137 |
$currentPaged = 1; |
| 138 |
} |
| 139 |
$rawScoreRange = $tableOptions['score_range'] ?? 'all'; |
| 140 |
|
| 141 |
return array( |
| 142 |
'filterText' => is_string($tableOptions['filterText'] ?? '') ? (string)($tableOptions['filterText'] ?? '') : '', |
| 143 |
'perPage' => is_scalar($rawPerPage) ? (int)$rawPerPage : 25, |
| 144 |
'currentFilter' => is_scalar($rawFilter) ? $rawFilter : 0, |
| 145 |
'currentOrderBy' => is_string($rawOrderBy) ? $rawOrderBy : 'url', |
| 146 |
'currentOrder' => is_string($rawOrder) ? $rawOrder : 'ASC', |
| 147 |
'currentPaged' => $currentPaged, |
| 148 |
'currentScoreRange' => is_string($rawScoreRange) ? $rawScoreRange : 'all', |
| 149 |
); |
| 150 |
} |
| 151 |
|
| 152 |
private function buildSubtitleHtml(bool $isSimpleMode): string { |
| 153 |
if (!$isSimpleMode) { |
| 154 |
return ''; |
| 155 |
} |
| 156 |
return $this->f->str_replace( |
| 157 |
'{text}', |
| 158 |
esc_html__('Broken links visitors tried to reach. Create Redirect for important ones, Dismiss the rest.', '404-solution'), |
| 159 |
$this->tpl('viewRedirectsTableSubtitle.html') |
| 160 |
) . "\n"; |
| 161 |
} |
| 162 |
|
| 163 |
/** @param bool|float|int|string $currentFilter */ |
| 164 |
private function buildEmptyTrashButtonHtml($currentFilter): string { |
| 165 |
if ($currentFilter != ABJ404_TRASH_FILTER) { |
| 166 |
return ''; |
| 167 |
} |
| 168 |
$eturl = wp_nonce_url("?page=" . ABJ404_PP . "&subpage=abj404_captured&filter=" . ABJ404_TRASH_FILTER, 'abj404_bulkProcess'); |
| 169 |
return $this->fillTpl('viewRedirectsTableEmptyTrashButton.html', array( |
| 170 |
'{href}' => esc_url($eturl . '&abj404action=emptyCapturedTrash'), |
| 171 |
'{confirm_js}' => esc_js(__('Are you sure you want to permanently delete all items in trash?', '404-solution')), |
| 172 |
'{label}' => esc_html__('Empty Trash', '404-solution'), |
| 173 |
)) . "\n"; |
| 174 |
} |
| 175 |
|
| 176 |
/** @param bool|float|int|string $currentFilter */ |
| 177 |
private function buildBulkButtonsHtml(bool $isSimpleMode, $currentFilter): string { |
| 178 |
if ($isSimpleMode) { |
| 179 |
return $this->listTableChrome->buildBulkButton('bulkignore', __('Dismiss', '404-solution')) |
| 180 |
. $this->listTableChrome->buildBulkButton('editRedirect', __('Create Redirect', '404-solution')); |
| 181 |
} |
| 182 |
|
| 183 |
$bulkButtons = ''; |
| 184 |
if ($currentFilter != ABJ404_STATUS_CAPTURED) { $bulkButtons .= $this->listTableChrome->buildBulkButton('bulkcaptured', __('Mark Captured', '404-solution')); } |
| 185 |
if ($currentFilter != ABJ404_STATUS_IGNORED) { $bulkButtons .= $this->listTableChrome->buildBulkButton('bulkignore', __('Mark Ignored', '404-solution')); } |
| 186 |
if ($currentFilter != ABJ404_STATUS_LATER) { $bulkButtons .= $this->listTableChrome->buildBulkButton('bulklater', __('Organize Later', '404-solution')); } |
| 187 |
if ($currentFilter != ABJ404_TRASH_FILTER) { $bulkButtons .= $this->listTableChrome->buildBulkButton('bulktrash', __('Move to Trash', '404-solution')); } |
| 188 |
$bulkButtons .= $this->listTableChrome->buildBulkButton('editRedirect', __('Create Redirect', '404-solution')); |
| 189 |
return $bulkButtons; |
| 190 |
} |
| 191 |
} |
| 192 |
|