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 / admin / AdminLogsTable.php

AdminLogsTable.php in 404 Solution trunk, at includes/admin/AdminLogsTable.php

249 lines 10.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 * Renders the Redirect Logs list table from log repository rows.
9 */
10 class ABJ_404_Solution_AdminLogsTable {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $f;
14 /** @var ABJ_404_Solution_View_Shared */
15 private $shared;
16 /** @var ABJ_404_Solution_Logging */
17 private $logger;
18 /** @var ABJ_404_Solution_LogsRepositoryInterface */
19 private $logsRepository;
20 /** @var ABJ_404_Solution_AdminTraceLabels */
21 private $traceLabels;
22
23 public function __construct(ABJ_404_Solution_Functions $functions, ABJ_404_Solution_View_Shared $shared,
24 ABJ_404_Solution_Logging $logging, ABJ_404_Solution_LogsRepositoryInterface $logsRepository,
25 ABJ_404_Solution_AdminTraceLabels $traceLabels) {
26 $this->f = $functions;
27 $this->shared = $shared;
28 $this->logger = $logging;
29 $this->logsRepository = $logsRepository;
30 $this->traceLabels = $traceLabels;
31 }
32
33 /** @param array<string, mixed> $tableOptions */
34 public function render(string $sub, array $tableOptions): string {
35 $headerCells = $this->renderHeaderCells($tableOptions);
36 $rows = $this->logsRepository->getLogRecords($tableOptions);
37 /** @var array<int, array<string, mixed>> $typedLogRows */
38 $typedLogRows = array_values(array_filter($rows, 'is_array'));
39 $this->shared->rememberTableDataSignature($sub, $typedLogRows);
40
41 $body = $this->renderBodyRows($typedLogRows);
42 return $this->f->str_replace(
43 array('{header_cells}', '{body_rows}', '{trace_script}'),
44 array($headerCells, $body, $this->tpl('viewLogsTraceToggleScript.html')),
45 $this->tpl('viewLogsTableShell.html')
46 );
47 }
48
49 /** @param array<string, mixed> $tableOptions */
50 private function renderHeaderCells(array $tableOptions): string {
51 $columns = array(
52 'url' => array('title' => __('URL', '404-solution'), 'orderby' => 'url'),
53 'host' => array('title' => __('IP Address', '404-solution'), 'orderby' => ''),
54 'refer' => array('title' => __('Referrer', '404-solution'), 'orderby' => ''),
55 'dest' => array('title' => __('Action', '404-solution'), 'orderby' => ''),
56 'timestamp' => array('title' => __('Date', '404-solution'), 'orderby' => 'timestamp'),
57 );
58
59 $plainHeaderTpl = $this->tpl('viewLogsTableHeaderCellPlain.html');
60 $sortHeaderTpl = $this->tpl('viewLogsTableHeaderCellSortable.html');
61 $headerCells = '';
62 foreach ($columns as $col) {
63 $orderbyCol = $col['orderby'];
64 if ($orderbyCol === '') {
65 $headerCells .= $this->f->str_replace('{title}', esc_html($col['title']), $plainHeaderTpl);
66 continue;
67 }
68
69 $sortUrl = '?page=' . ABJ404_PP . '&subpage=abj404_logs&orderby=' . $orderbyCol;
70 $sortState = $this->shared->getHeaderSortState($tableOptions, $orderbyCol, false);
71 $sortUrl .= '&order=' . $sortState['nextOrder'];
72 $sortClass = trim($sortState['thClass']);
73 $sortClassAttr = ($sortClass !== '') ? ' class="' . $sortClass . '"' : '';
74
75 $headerCells .= $this->f->str_replace(
76 array('{class_attr}', '{sort_url}', '{title}', '{sort_indicator}'),
77 array($sortClassAttr, esc_url($sortUrl), esc_html($col['title']), $sortState['indicator']),
78 $sortHeaderTpl
79 );
80 }
81
82 return $headerCells;
83 }
84
85 /** @param array<int, array<string, mixed>> $rows */
86 private function renderBodyRows(array $rows): string {
87 $displayed = 0;
88 $bodyRows = '';
89 $progress = ABJ_404_Solution_AjaxRowLoopProgress::begin('logs_rows', count($rows));
90 foreach ($rows as $row) {
91 $progress->tick($row);
92 $rendered = $this->renderLogRow($row);
93 $bodyRows .= $rendered['row_html'] . $rendered['trace_html'];
94 $displayed++;
95 }
96 $progress->finish();
97
98 $this->logger->debugMessage($displayed . ' log records displayed on the page.');
99 if ($displayed === 0) {
100 $bodyRows .= $this->f->str_replace('{message}', __('No Results To Display', '404-solution'), $this->tpl('viewLogsEmptyRow.html'));
101 }
102
103 return $bodyRows;
104 }
105
106 /**
107 * @param array<string, mixed> $row
108 * @return array{row_html: string, trace_html: string}
109 */
110 private function renderLogRow(array $row): array {
111 $logId = is_scalar($row['log_id'] ?? '') ? (string)($row['log_id'] ?? '') : '';
112 $rawTrace = isset($row['pipeline_trace']) && is_string($row['pipeline_trace']) ? $row['pipeline_trace'] : null;
113 $traceSteps = ABJ_404_Solution_LogsRepository::decompressPipelineTrace($rawTrace);
114 $hasTrace = is_array($traceSteps) && !empty($traceSteps);
115
116 $rowHtml = $this->f->str_replace(
117 array('{trace_toggle}', '{url_cell}', '{ip_cell}', '{referrer_cell}', '{action_cell}', '{date_cell}'),
118 array(
119 $this->renderTraceToggle($logId, $hasTrace),
120 $this->renderUrlCell($row),
121 $this->renderIpCell($row),
122 $this->renderReferrerCell($row),
123 $this->renderActionCell($row),
124 $this->renderDateCell($row),
125 ),
126 $this->tpl('viewLogsTableRow.html')
127 );
128
129 return array(
130 'row_html' => $rowHtml,
131 'trace_html' => ($hasTrace && $logId !== '') ? $this->renderTraceDetailRow($logId, $traceSteps) : '',
132 );
133 }
134
135 private function renderTraceToggle(string $logId, bool $hasTrace): string {
136 if (!$hasTrace) {
137 return $this->f->str_replace('{title}', esc_attr__('No trace available', '404-solution'), $this->tpl('viewLogsTraceToggleDisabled.html'));
138 }
139 return $this->f->str_replace(
140 array('{row_id}', '{title}'),
141 array(esc_attr($logId), esc_attr__('Show pipeline trace', '404-solution')),
142 $this->tpl('viewLogsTraceToggleEnabled.html')
143 );
144 }
145
146 /** @param array<string, mixed> $row */
147 private function renderUrlCell(array $row): string {
148 $url = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : '';
149 $urlDetail = is_string($row['url_detail'] ?? '') ? (string)($row['url_detail'] ?? '') : '';
150 $detailSpan = '';
151 if ($urlDetail !== '' && trim($urlDetail) !== '') {
152 $detailSpan = $this->f->str_replace('{detail}', esc_html(trim($urlDetail)), $this->tpl('viewLogsUrlDetailSpan.html'));
153 }
154 return $this->f->str_replace(
155 array('{full_url}', '{url_attr}', '{url_text}', '{url_detail_span}'),
156 array(esc_url(home_url($url)), esc_attr($url), esc_html($url), $detailSpan),
157 $this->tpl('viewLogsUrlCell.html')
158 );
159 }
160
161 /** @param array<string, mixed> $row */
162 private function renderIpCell(array $row): string {
163 $remoteHost = is_string($row['remote_host'] ?? '') ? (string)($row['remote_host'] ?? '') : '';
164 return esc_html($remoteHost);
165 }
166
167 /** @param array<string, mixed> $row */
168 private function renderReferrerCell(array $row): string {
169 $referrer = is_string($row['referrer'] ?? '') ? (string)($row['referrer'] ?? '') : '';
170 if ($referrer === '') {
171 return $this->tpl('viewLogsReferrerMuted.html');
172 }
173 return $this->f->str_replace(
174 array('{referrer_url}', '{referrer_attr}', '{referrer_text}'),
175 array(esc_url($referrer), esc_attr($referrer), esc_html($referrer)),
176 $this->tpl('viewLogsReferrerLink.html')
177 );
178 }
179
180 /** @param array<string, mixed> $row */
181 private function renderActionCell(array $row): string {
182 $action = trim(is_string($row['action'] ?? '') ? (string)($row['action'] ?? '') : '');
183 $engineVal = is_string($row['engine'] ?? '') ? (string)($row['engine'] ?? '') : '';
184 if ($action === '' || $action == '404' || $action == 'http://404') {
185 $actionCell = $this->f->str_replace('{label}', __('404', '404-solution'), $this->tpl('viewLogsActionBadge404.html'));
186 } else {
187 $actionCell = $this->f->str_replace(
188 array('{label}', '{action_url}', '{action_attr}', '{action_text}'),
189 array(__('Redirect', '404-solution'), esc_url($action), esc_attr($action), esc_html($action)),
190 $this->tpl('viewLogsActionBadgeRedirect.html')
191 );
192 }
193 if ($engineVal !== '') {
194 $actionCell .= $this->f->str_replace(
195 array('{engine_title}', '{engine_text}'),
196 array(esc_attr__('Engine', '404-solution'), esc_html($engineVal)),
197 $this->tpl('viewLogsEngineLabel.html')
198 );
199 }
200 return $actionCell;
201 }
202
203 /** @param array<string, mixed> $row */
204 private function renderDateCell(array $row): string {
205 $timeToDisplay = abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0);
206 $rowUsername = is_string($row['username'] ?? '') ? (string)($row['username'] ?? '') : '';
207 $usernamePart = '';
208 if ($rowUsername !== '') {
209 $usernamePart = $this->f->str_replace('{username}', esc_html($rowUsername), $this->tpl('viewLogsUsernameLabel.html'));
210 }
211 return $this->f->str_replace(
212 array('{date_part}', '{time_part}', '{username_part}'),
213 array(ABJ_404_Solution_SiteLocalTimestamp::format('Y/m/d', $timeToDisplay),
214 ABJ_404_Solution_SiteLocalTimestamp::format('h:i:s A', $timeToDisplay), $usernamePart),
215 $this->tpl('viewLogsDateCell.html')
216 );
217 }
218
219 /** @param array<int, array<string, string>> $traceSteps */
220 private function renderTraceDetailRow(string $logId, array $traceSteps): string {
221 $stepsHtml = '';
222 foreach ($traceSteps as $step) {
223 $detail = ($step['detail'] !== '')
224 ? $this->f->str_replace('{detail_text}', esc_html($step['detail']), $this->tpl('viewLogsTraceStepDetail.html'))
225 : '';
226 $stepsHtml .= $this->f->str_replace(
227 array('{step_name}', '{outcome_class}', '{outcome}', '{detail}'),
228 array(
229 esc_html($this->traceLabels->translate($step['step'])),
230 $this->traceLabels->outcomeClass($step['outcome']),
231 esc_html($this->traceLabels->translate($step['outcome'])),
232 $detail,
233 ),
234 $this->tpl('viewLogsTraceStep.html')
235 );
236 }
237 return $this->f->str_replace(
238 array('{row_id}', '{trace_steps}'),
239 array(esc_attr($logId), $stepsHtml),
240 $this->tpl('viewLogsTraceDetailRow.html')
241 );
242 }
243
244 private function tpl(string $name): string {
245 $raw = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . '/../html/' . $name, false);
246 return rtrim((string)$raw, "\n");
247 }
248 }
249