PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / view / View_CapturedURLsTable.php

View_CapturedURLsTable.php in 404 Solution trunk, at includes/view/View_CapturedURLsTable.php

294 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Captured 404 URLs admin page renderer. Owns the AJAX-refreshed table-body
9 * shell and per-row presentation formatting (status badges, last-used dates,
10 * template variable assembly) for the "Captured 404 URLs" admin page
11 * (subpage abj404_captured). Delegates page-chrome rendering to
12 * CapturedPageChromeRenderer, per-row action-button HTML to
13 * CapturedRowActionButtonsRenderer, and the "linked from" source-evidence
14 * panel to CapturedSourceEvidenceRenderer.
15 *
16 * Outside callers (via the View facade __call dispatch):
17 * - includes/ajax/ViewUpdater.php (pagination AJAX -> getCapturedURLSPageTable)
18 * - PluginLogic admin page entry points (echoAdminCapturedURLsPage)
19 */
20 class ABJ_404_Solution_View_CapturedURLsTable extends ABJ_404_Solution_ViewComponent {
21
22 /** @var ABJ_404_Solution_CapturedTableHeaderRenderer|null */
23 private $capturedTableHeaderRenderer = null;
24
25 /** @var ABJ_404_Solution_CapturedPageChromeRenderer|null */
26 private $pageChromeRenderer = null;
27
28 private function tpl(string $name): string {
29 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
30 return rtrim((string)$raw, "\n");
31 }
32
33 /** @return void */
34 public function echoAdminCapturedURLsPage() {
35 echo $this->pageChromeRenderer()->render();
36 }
37
38 private function pageChromeRenderer(): ABJ_404_Solution_CapturedPageChromeRenderer {
39 if ($this->pageChromeRenderer === null) {
40 $this->pageChromeRenderer = new ABJ_404_Solution_CapturedPageChromeRenderer(
41 $this->logic,
42 $this->listTableChrome,
43 $this->f
44 );
45 }
46 return $this->pageChromeRenderer;
47 }
48
49 /** @param array<string, mixed> $tableOptionOverrides Internal per-request read options. */
50 public function getCapturedURLSPageTable(string $sub, array $tableOptionOverrides = array()): string {
51 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
52 $tableOptions = array_replace($tableOptions, $tableOptionOverrides);
53 $rows = $this->viewReadService->getRedirectsForView($sub, $tableOptions);
54 /** @var array<int, array<string, mixed>> $typedRows */
55 $typedRows = array_values(array_filter($rows, 'is_array'));
56 $this->shared->rememberTableDataSignature($sub, $typedRows);
57 $header = ABJ_404_Solution_TableRenderTranslationTracer::traceScope(
58 'captured_header',
59 'Select all|URL|Status|Hits|Created|Last Used',
60 fn(): array => array(
61 'select_all' => esc_attr__('Select all', '404-solution'),
62 'cells' => $this->buildCapturedHeaderCells($tableOptions),
63 )
64 );
65
66 return $this->f->str_replace(
67 array('{select_all_label}', '{header_cells}', '{body_rows}'),
68 array(
69 $header['select_all'],
70 $header['cells'],
71 $this->buildCapturedBodyRows($sub, $tableOptions, $typedRows),
72 ),
73 $this->tpl('viewRedirectsTableCapturedTableShell.html')
74 );
75 }
76
77 /** @param array<string, mixed> $tableOptions */
78 private function buildCapturedHeaderCells(array $tableOptions): string {
79 return $this->capturedTableHeaderRenderer()->render($tableOptions);
80 }
81
82 private function capturedTableHeaderRenderer(): ABJ_404_Solution_CapturedTableHeaderRenderer {
83 if ($this->capturedTableHeaderRenderer === null) {
84 $this->capturedTableHeaderRenderer = new ABJ_404_Solution_CapturedTableHeaderRenderer(
85 $this->f,
86 $this->shared,
87 $this->viewReadService
88 );
89 }
90 return $this->capturedTableHeaderRenderer;
91 }
92
93 /**
94 * @param array<string, mixed> $tableOptions
95 * @param array<int, array<string, mixed>> $rows
96 */
97 private function buildCapturedBodyRows(string $sub, array $tableOptions, array $rows): string {
98 if (count($rows) === 0) {
99 // Same i455 bug class as the redirects table: a pending/errored/
100 // stale-empty staged read must not be shown as "No records" while
101 // the live source count says captured rows exist.
102 return ABJ_404_Solution_TableRenderTranslationTracer::traceScope(
103 'captured_empty_state',
104 'Preparing captured table|No Captured 404 Records To Display',
105 function (): string {
106 $emptyMessage = $this->viewReadService->lastRedirectsViewReadWasIncomplete()
107 ? __('Preparing the captured 404s table. The list is still loading and will appear in a moment.', '404-solution')
108 : __('No Captured 404 Records To Display', '404-solution');
109 return $this->f->str_replace(
110 '{message}',
111 $emptyMessage,
112 $this->tpl('viewRedirectsTableCapturedEmptyRow.html')
113 ) . "\n";
114 }
115 );
116 }
117
118 $sourceEvidenceByUrl = $this->sourceEvidenceByVisibleUrl($rows);
119 $bodyRows = '';
120 $progress = ABJ_404_Solution_AjaxRowLoopProgress::begin('captured_rows', count($rows));
121 foreach ($rows as $row) {
122 $progress->tick($row);
123 $bodyRows .= $this->capturedBodyRow($sub, $tableOptions, $row, $sourceEvidenceByUrl);
124 }
125 $progress->finish();
126 return $bodyRows;
127 }
128
129 /**
130 * @param array<string, mixed> $tableOptions
131 * @param array<string, mixed> $row
132 * @param array<string, array<string, mixed>> $sourceEvidenceByUrl
133 */
134 private function capturedBodyRow(string $sub, array $tableOptions, array $row, array $sourceEvidenceByUrl): string {
135 $hits = is_scalar($row['logshits'] ?? 0) ? (int)($row['logshits'] ?? 0) : 0;
136 $lastUsed = $this->capturedLastUsedPresentation($row);
137 $status = $this->capturedStatusPresentation($row);
138 $btns = $this->capturedActionButtons($sub, $tableOptions, $row);
139 $vars = $this->capturedRowTemplateVars($row, $hits, $lastUsed, $status, $btns, $sourceEvidenceByUrl);
140
141 $tempHtml = $this->f->str_replace(
142 array_keys($vars),
143 array_values($vars),
144 ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . "/html/tableRowCapturedURLs.html")
145 );
146 return $this->f->doNormalReplacements($tempHtml);
147 }
148
149 /**
150 * @param array<string, mixed> $row
151 * @return array{date: string, class: string}
152 */
153 private function capturedLastUsedPresentation(array $row): array {
154 $last_used = is_scalar($row['last_used'] ?? 0) ? (int)($row['last_used'] ?? 0) : 0;
155 if ($last_used != 0) {
156 return array(
157 'date' => ABJ_404_Solution_SiteLocalTimestamp::format("Y/m/d h:i:s A", abs($last_used)),
158 'class' => '',
159 );
160 }
161
162 return array(
163 'date' => __('Never', '404-solution'),
164 'class' => 'abj404-never-used',
165 );
166 }
167
168 /**
169 * @param array<string, mixed> $row
170 * @return array{class: string, text: string, title: string}
171 */
172 private function capturedStatusPresentation(array $row): array {
173 if ($row['status'] == ABJ404_STATUS_IGNORED) {
174 return array(
175 'class' => 'abj404-badge-ignored',
176 'text' => __('Ignored', '404-solution'),
177 'title' => __('Ignored URL - will not be suggested', '404-solution'),
178 );
179 }
180 if ($row['status'] == ABJ404_STATUS_LATER) {
181 return array(
182 'class' => 'abj404-badge-later',
183 'text' => __('Later', '404-solution'),
184 'title' => __('Organize Later', '404-solution'),
185 );
186 }
187
188 return array(
189 'class' => 'abj404-badge-captured',
190 'text' => __('Captured', '404-solution'),
191 'title' => __('Captured 404 URL', '404-solution'),
192 );
193 }
194
195 /**
196 * @param array<string, mixed> $tableOptions
197 * @param array<string, mixed> $row
198 * @return array{edit: string, logs: string, trash: string, delete: string, ignore: string, later: string}
199 */
200 private function capturedActionButtons(string $sub, array $tableOptions, array $row): array {
201 $links = $this->shared->buildTableActionLinks($row, $sub, $tableOptions, true);
202
203 return $this->rowActionButtonsRenderer()->build($row, $tableOptions, array(
204 'editlink' => $this->linkValue($links, 'editlink'),
205 'logslink' => $this->linkValue($links, 'logslink'),
206 'trashlink' => $this->linkValue($links, 'trashlink'),
207 'trashtitle' => $this->linkValue($links, 'trashtitle'),
208 'deletelink' => $this->linkValue($links, 'deletelink'),
209 'ignorelink' => $this->linkValue($links, 'ignorelink'),
210 'ignoretitle' => $this->linkValue($links, 'ignoretitle'),
211 'laterlink' => $this->linkValue($links, 'laterlink'),
212 'latertitle' => $this->linkValue($links, 'latertitle'),
213 ));
214 }
215
216 /** @var ABJ_404_Solution_CapturedRowActionButtonsRenderer|null */
217 private $rowActionButtonsRenderer = null;
218
219 private function rowActionButtonsRenderer(): ABJ_404_Solution_CapturedRowActionButtonsRenderer {
220 if ($this->rowActionButtonsRenderer === null) {
221 $this->rowActionButtonsRenderer = new ABJ_404_Solution_CapturedRowActionButtonsRenderer($this->f);
222 }
223 return $this->rowActionButtonsRenderer;
224 }
225
226 /** @param array<string, mixed> $links */
227 private function linkValue(array $links, string $key): string {
228 return is_scalar($links[$key] ?? '') ? (string)($links[$key] ?? '') : '';
229 }
230
231 /**
232 * @param array<string, mixed> $row
233 * @param array{date: string, class: string} $lastUsed
234 * @param array{class: string, text: string, title: string} $status
235 * @param array{edit: string, logs: string, trash: string, delete: string, ignore: string, later: string} $btns
236 * @param array<string, array<string, mixed>> $sourceEvidenceByUrl
237 * @return array<string, string>
238 */
239 private function capturedRowTemplateVars(array $row, int $hits, array $lastUsed, array $status, array $btns,
240 array $sourceEvidenceByUrl): array {
241 $capturedRowUrl = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : '';
242 $capturedRowId = is_scalar($row['id'] ?? '') ? (string)($row['id'] ?? '') : '';
243 $capturedEngine = is_string($row['engine'] ?? '') ? trim((string)($row['engine'] ?? '')) : '';
244 $capturedEngineHTML = ($capturedEngine !== '') ? '<br><span class="abj404-engine-label">' . esc_html($capturedEngine) . '</span>' : '';
245 $createdTimestamp = is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0;
246 $sourceEvidence = isset($sourceEvidenceByUrl[$capturedRowUrl]) && is_array($sourceEvidenceByUrl[$capturedRowUrl])
247 ? $sourceEvidenceByUrl[$capturedRowUrl] : array();
248 $sourceHtml = $this->sourceEvidenceRenderer()->htmlFor($capturedRowId, $sourceEvidence);
249
250 $vars = array(
251 '{rowid}' => $capturedRowId,
252 '{rowClass}' => '',
253 '{visitorURL}' => esc_url(home_url($capturedRowUrl)),
254 '{url}' => esc_html($capturedRowUrl),
255 '{statusBadgeClass}' => $status['class'],
256 '{statusTitle}' => esc_attr($status['title']),
257 '{status}' => $status['text'],
258 '{engineHTML}' => $capturedEngineHTML,
259 '{hits}' => esc_html((string)$hits),
260 '{created_date}' => esc_html(
261 ABJ_404_Solution_SiteLocalTimestamp::format("Y/m/d h:i:s A", abs($createdTimestamp))),
262 '{last_used_date}' => esc_html($lastUsed['date']),
263 '{lastUsedClass}' => $lastUsed['class'],
264 '{editBtnHTML}' => $btns['edit'],
265 '{logsBtnHTML}' => $btns['logs'],
266 '{trashBtnHTML}' => $btns['trash'],
267 '{deleteBtnHTML}' => $btns['delete'],
268 '{ignoreBtnHTML}' => $btns['ignore'],
269 '{laterBtnHTML}' => $btns['later'],
270 '{internal_sources_trigger}' => $sourceHtml['trigger'],
271 '{internal_sources_panel}' => $sourceHtml['panel'],
272 );
273 return $vars;
274 }
275
276 /**
277 * @param array<int, array<string, mixed>> $rows
278 * @return array<string, array<string, mixed>>
279 */
280 private function sourceEvidenceByVisibleUrl(array $rows): array {
281 return $this->sourceEvidenceRenderer()->evidenceByVisibleUrl($rows);
282 }
283
284 /** @var ABJ_404_Solution_CapturedSourceEvidenceRenderer|null */
285 private $sourceEvidenceRenderer = null;
286
287 private function sourceEvidenceRenderer(): ABJ_404_Solution_CapturedSourceEvidenceRenderer {
288 if ($this->sourceEvidenceRenderer === null) {
289 $this->sourceEvidenceRenderer = new ABJ_404_Solution_CapturedSourceEvidenceRenderer($this->f);
290 }
291 return $this->sourceEvidenceRenderer;
292 }
293 }
294