| 1 |
<?php |
| 2 |
|
| 3 |
if (!defined('ABSPATH')) { |
| 4 |
exit; |
| 5 |
} |
| 6 |
|
| 7 |
/** |
| 8 |
* Admin list-table chrome shared by the Page Redirects and Captured 404 URLs |
| 9 |
* admin tables. Owns the widgets that surround the data rows: subsubsub filter |
| 10 |
* row, per-page selector, score-range filter, bulk-action form URL/options, |
| 11 |
* empty-trash form, and the i18n strings for the pagination auto-refresh |
| 12 |
* indicator. Pagination itself is rendered by ABJ_404_Solution_AdminPaginationLinks |
| 13 |
* (single source of truth) and reaches the page over the same AJAX warmup channel. |
| 14 |
*/ |
| 15 |
class ABJ_404_Solution_View_ListTableChrome extends ABJ_404_Solution_ViewComponent { |
| 16 |
|
| 17 |
/** Load a template file from includes/html/ and trim its trailing newline. */ |
| 18 |
private function tpl(string $name): string { |
| 19 |
$raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false); |
| 20 |
return rtrim((string)$raw, "\n"); |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Load a template and substitute an associative array of placeholders. |
| 25 |
* @param array<string,string> $vars |
| 26 |
*/ |
| 27 |
private function fillTpl(string $name, array $vars): string { |
| 28 |
return (string)$this->f->str_replace(array_keys($vars), array_values($vars), $this->tpl($name)); |
| 29 |
} |
| 30 |
|
| 31 |
/** |
| 32 |
* Build the rows-per-page <option> list HTML. |
| 33 |
* |
| 34 |
* @param int|string $currentPerPage |
| 35 |
*/ |
| 36 |
public function buildPerpageOptions($currentPerPage): string { |
| 37 |
$optionTpl = $this->tpl('viewRedirectsTablePerpageOption.html'); |
| 38 |
$html = ''; |
| 39 |
foreach (array(10, 25, 50, 100, 200) as $opt) { |
| 40 |
$selected = ($currentPerPage == $opt) ? ' selected' : ''; |
| 41 |
$html .= $this->f->str_replace( |
| 42 |
array('{value}', '{selected_attr}'), |
| 43 |
array((string)$opt, $selected), |
| 44 |
$optionTpl |
| 45 |
) . "\n"; |
| 46 |
} |
| 47 |
return $html; |
| 48 |
} |
| 49 |
|
| 50 |
/** Build a bulk-action button (the type that posts to the bulk form). */ |
| 51 |
public function buildBulkButton(string $value, string $label): string { |
| 52 |
return $this->f->str_replace( |
| 53 |
array('{value}', '{label}'), |
| 54 |
array(esc_attr($value), $label), |
| 55 |
$this->tpl('viewRedirectsTableBulkButton.html') |
| 56 |
) . "\n"; |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Build the i18n string for the pagination "Refresh available" pill |
| 61 |
* data-* attr. The pill is the only background-refresh surface: the table |
| 62 |
* always renders live from the denorm read, so there is no "refreshing / |
| 63 |
* data refreshed" progress toast (and no started/finished strings). |
| 64 |
* |
| 65 |
* @return array{available:string} |
| 66 |
*/ |
| 67 |
public function paginationRefreshStrings(): array { |
| 68 |
$available = __('Refresh available', '404-solution'); |
| 69 |
return array('available' => $available); |
| 70 |
} |
| 71 |
|
| 72 |
/** Build the Confidence-filter <option> list for the Redirects table. */ |
| 73 |
public function buildScoreRangeOptions(string $currentScoreRange): string { |
| 74 |
$opts = array( |
| 75 |
'all' => __('All', '404-solution'), |
| 76 |
// allow-em-dash: pre-existing translation string with U+2265 in published .po files |
| 77 |
'high' => __('High (≥80%)', '404-solution'), |
| 78 |
// allow-em-dash: pre-existing translation string with U+2013 in published .po files |
| 79 |
'medium' => __('Medium (50–79%)', '404-solution'), |
| 80 |
'low' => __('Low (<50%)', '404-solution'), |
| 81 |
'manual' => __('Manual (no score)', '404-solution'), |
| 82 |
); |
| 83 |
$tpl = $this->tpl('viewRedirectsTableScoreRangeOption.html'); |
| 84 |
$html = ''; |
| 85 |
foreach ($opts as $val => $label) { |
| 86 |
$html .= $this->f->str_replace( |
| 87 |
array('{value}', '{selected_attr}', '{label}'), |
| 88 |
array(esc_attr($val), ($currentScoreRange === $val) ? ' selected' : '', esc_html($label)), |
| 89 |
$tpl |
| 90 |
) . "\n"; |
| 91 |
} |
| 92 |
return $html; |
| 93 |
} |
| 94 |
|
| 95 |
/** |
| 96 |
* Build the bulk-action <option> list for the Redirects table. |
| 97 |
* @param mixed $currentFilter |
| 98 |
*/ |
| 99 |
public function buildRedirectsBulkActionOptions($currentFilter): string { |
| 100 |
$tpl = $this->tpl('viewRedirectsTableBulkActionOption.html'); |
| 101 |
$opts = array(); |
| 102 |
if ($currentFilter != ABJ404_STATUS_AUTO) { $opts[] = array('editRedirect', esc_html__('Edit Redirects', '404-solution')); } |
| 103 |
if ($currentFilter != ABJ404_TRASH_FILTER) { $opts[] = array('bulktrash', esc_html__('Move to Trash', '404-solution')); } |
| 104 |
if ($currentFilter == ABJ404_TRASH_FILTER) { |
| 105 |
$opts[] = array('bulk_trash_restore', esc_html__('Restore Redirects', '404-solution')); |
| 106 |
$opts[] = array('bulk_trash_delete_permanently', esc_html__('Delete Permanently', '404-solution')); |
| 107 |
} |
| 108 |
$html = ''; |
| 109 |
foreach ($opts as $o) { |
| 110 |
$html .= $this->f->str_replace(array('{value}', '{label}'), $o, $tpl) . "\n"; |
| 111 |
} |
| 112 |
return $html; |
| 113 |
} |
| 114 |
|
| 115 |
/** Build the standalone Empty-Trash form shown below the redirects table when viewing the Trash filter. */ |
| 116 |
public function buildEmptyTrashForm(string $sub): string { |
| 117 |
$eturl = wp_nonce_url("?page=" . ABJ404_PP . "&filter=" . ABJ404_TRASH_FILTER . "&subpage=" . $sub, "abj404_bulkProcess"); |
| 118 |
return $this->fillTpl('viewRedirectsTableEmptyTrashForm.html', array( |
| 119 |
'{action_url}' => esc_url($eturl), |
| 120 |
'{confirm_js}' => esc_js(__('Are you sure you want to permanently delete all items in the trash?', '404-solution')), |
| 121 |
'{label}' => esc_html__('Empty Trash', '404-solution'), |
| 122 |
)); |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Build the native-WordPress subsubsub filter row for a list-table page. |
| 127 |
* |
| 128 |
* Counts are emitted as a placeholder character; the real numbers are |
| 129 |
* populated by the pagination AJAX response (see view_updater_pagination.js). |
| 130 |
* |
| 131 |
* @param string $sub Subpage key (abj404_redirects, abj404_captured). |
| 132 |
* @param array<int, array{0:int|string, 1:string}> $items One [filterValue, label] pair per link. |
| 133 |
* @param array<string, mixed> $tableOptions Current table options (for active-link detection). |
| 134 |
*/ |
| 135 |
public function buildSubsubsubFilters(string $sub, array $items, array $tableOptions): string { |
| 136 |
$currentFilter = isset($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 137 |
$itemTpl = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . "/html/listTableSubsubsubItem.html"); |
| 138 |
$itemsHtml = ''; |
| 139 |
$lastIndex = count($items) - 1; |
| 140 |
foreach ($items as $i => $pair) { |
| 141 |
list($filter, $label) = $pair; |
| 142 |
$url = "?page=" . ABJ404_PP . "&subpage=" . $sub; |
| 143 |
if ($filter != 0) { |
| 144 |
$url .= "&filter=" . $filter; |
| 145 |
} |
| 146 |
$isCurrent = ($currentFilter == $filter); |
| 147 |
$classAttr = $isCurrent ? ' class="current"' : ''; |
| 148 |
$separator = ($i < $lastIndex) ? ' |' : ''; |
| 149 |
|
| 150 |
$row = $itemTpl; |
| 151 |
$row = str_replace('{url}', esc_url($url), $row); |
| 152 |
$row = str_replace('{classAttr}', $classAttr, $row); |
| 153 |
$row = str_replace('{filter}', esc_attr((string)$filter), $row); |
| 154 |
$row = str_replace('{label}', esc_html($label), $row); |
| 155 |
$row = str_replace('{count}', '…', $row); |
| 156 |
$row = str_replace('{separator}', $separator, $row); |
| 157 |
$itemsHtml .= $row; |
| 158 |
} |
| 159 |
|
| 160 |
$outer = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . "/html/listTableSubsubsub.html"); |
| 161 |
return str_replace('{items}', $itemsHtml, $outer); |
| 162 |
} |
| 163 |
|
| 164 |
/** |
| 165 |
* @param array<string, mixed> $tableOptions |
| 166 |
*/ |
| 167 |
public function getBulkOperationsFormURL(string $sub, array $tableOptions): string { |
| 168 |
$url = "?page=" . ABJ404_PP . "&subpage=" . $sub; |
| 169 |
$filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0; |
| 170 |
if ($filter != 0) { |
| 171 |
$url .= "&filter=" . $filter; |
| 172 |
} |
| 173 |
$orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url'; |
| 174 |
$order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC'; |
| 175 |
if (!($orderby == "url" && $order == "ASC")) { |
| 176 |
$url .= "&orderby=" . sanitize_text_field($orderby) . "&order=" . sanitize_text_field($order); |
| 177 |
} |
| 178 |
$url = wp_nonce_url($url, 'abj404_bulkProcess'); |
| 179 |
return $url; |
| 180 |
} |
| 181 |
|
| 182 |
} |
| 183 |
|