PluginProbe
404 Solution / 4.3.0
404 Solution v4.3.0
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 4.3.0, at includes/admin/AdminLogsTable.php

245 lines 10.7 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 foreach ($rows as $row) {
90 $rendered = $this->renderLogRow($row);
91 $bodyRows .= $rendered['row_html'] . $rendered['trace_html'];
92 $displayed++;
93 }
94
95 $this->logger->debugMessage($displayed . ' log records displayed on the page.');
96 if ($displayed === 0) {
97 $bodyRows .= $this->f->str_replace('{message}', __('No Results To Display', '404-solution'), $this->tpl('viewLogsEmptyRow.html'));
98 }
99
100 return $bodyRows;
101 }
102
103 /**
104 * @param array<string, mixed> $row
105 * @return array{row_html: string, trace_html: string}
106 */
107 private function renderLogRow(array $row): array {
108 $logId = is_scalar($row['log_id'] ?? '') ? (string)($row['log_id'] ?? '') : '';
109 $rawTrace = isset($row['pipeline_trace']) && is_string($row['pipeline_trace']) ? $row['pipeline_trace'] : null;
110 $traceSteps = ABJ_404_Solution_LogsRepository::decompressPipelineTrace($rawTrace);
111 $hasTrace = is_array($traceSteps) && !empty($traceSteps);
112
113 $rowHtml = $this->f->str_replace(
114 array('{trace_toggle}', '{url_cell}', '{ip_cell}', '{referrer_cell}', '{action_cell}', '{date_cell}'),
115 array(
116 $this->renderTraceToggle($logId, $hasTrace),
117 $this->renderUrlCell($row),
118 $this->renderIpCell($row),
119 $this->renderReferrerCell($row),
120 $this->renderActionCell($row),
121 $this->renderDateCell($row),
122 ),
123 $this->tpl('viewLogsTableRow.html')
124 );
125
126 return array(
127 'row_html' => $rowHtml,
128 'trace_html' => ($hasTrace && $logId !== '') ? $this->renderTraceDetailRow($logId, $traceSteps) : '',
129 );
130 }
131
132 private function renderTraceToggle(string $logId, bool $hasTrace): string {
133 if (!$hasTrace) {
134 return $this->f->str_replace('{title}', esc_attr__('No trace available', '404-solution'), $this->tpl('viewLogsTraceToggleDisabled.html'));
135 }
136 return $this->f->str_replace(
137 array('{row_id}', '{title}'),
138 array(esc_attr($logId), esc_attr__('Show pipeline trace', '404-solution')),
139 $this->tpl('viewLogsTraceToggleEnabled.html')
140 );
141 }
142
143 /** @param array<string, mixed> $row */
144 private function renderUrlCell(array $row): string {
145 $url = is_string($row['url'] ?? '') ? (string)($row['url'] ?? '') : '';
146 $urlDetail = is_string($row['url_detail'] ?? '') ? (string)($row['url_detail'] ?? '') : '';
147 $detailSpan = '';
148 if ($urlDetail !== '' && trim($urlDetail) !== '') {
149 $detailSpan = $this->f->str_replace('{detail}', esc_html(trim($urlDetail)), $this->tpl('viewLogsUrlDetailSpan.html'));
150 }
151 return $this->f->str_replace(
152 array('{full_url}', '{url_attr}', '{url_text}', '{url_detail_span}'),
153 array(esc_url(home_url($url)), esc_attr($url), esc_html($url), $detailSpan),
154 $this->tpl('viewLogsUrlCell.html')
155 );
156 }
157
158 /** @param array<string, mixed> $row */
159 private function renderIpCell(array $row): string {
160 $remoteHost = is_string($row['remote_host'] ?? '') ? (string)($row['remote_host'] ?? '') : '';
161 return esc_html($remoteHost);
162 }
163
164 /** @param array<string, mixed> $row */
165 private function renderReferrerCell(array $row): string {
166 $referrer = is_string($row['referrer'] ?? '') ? (string)($row['referrer'] ?? '') : '';
167 if ($referrer === '') {
168 return $this->tpl('viewLogsReferrerMuted.html');
169 }
170 return $this->f->str_replace(
171 array('{referrer_url}', '{referrer_attr}', '{referrer_text}'),
172 array(esc_url($referrer), esc_attr($referrer), esc_html($referrer)),
173 $this->tpl('viewLogsReferrerLink.html')
174 );
175 }
176
177 /** @param array<string, mixed> $row */
178 private function renderActionCell(array $row): string {
179 $action = trim(is_string($row['action'] ?? '') ? (string)($row['action'] ?? '') : '');
180 $engineVal = is_string($row['engine'] ?? '') ? (string)($row['engine'] ?? '') : '';
181 if ($action === '' || $action == '404' || $action == 'http://404') {
182 $actionCell = $this->f->str_replace('{label}', __('404', '404-solution'), $this->tpl('viewLogsActionBadge404.html'));
183 } else {
184 $actionCell = $this->f->str_replace(
185 array('{label}', '{action_url}', '{action_attr}', '{action_text}'),
186 array(__('Redirect', '404-solution'), esc_url($action), esc_attr($action), esc_html($action)),
187 $this->tpl('viewLogsActionBadgeRedirect.html')
188 );
189 }
190 if ($engineVal !== '') {
191 $actionCell .= $this->f->str_replace(
192 array('{engine_title}', '{engine_text}'),
193 array(esc_attr__('Engine', '404-solution'), esc_html($engineVal)),
194 $this->tpl('viewLogsEngineLabel.html')
195 );
196 }
197 return $actionCell;
198 }
199
200 /** @param array<string, mixed> $row */
201 private function renderDateCell(array $row): string {
202 $timeToDisplay = abs(is_scalar($row['timestamp'] ?? 0) ? intval($row['timestamp'] ?? 0) : 0);
203 $rowUsername = is_string($row['username'] ?? '') ? (string)($row['username'] ?? '') : '';
204 $usernamePart = '';
205 if ($rowUsername !== '') {
206 $usernamePart = $this->f->str_replace('{username}', esc_html($rowUsername), $this->tpl('viewLogsUsernameLabel.html'));
207 }
208 return $this->f->str_replace(
209 array('{date_part}', '{time_part}', '{username_part}'),
210 array((string)wp_date('Y/m/d', $timeToDisplay), (string)wp_date('h:i:s A', $timeToDisplay), $usernamePart),
211 $this->tpl('viewLogsDateCell.html')
212 );
213 }
214
215 /** @param array<int, array<string, string>> $traceSteps */
216 private function renderTraceDetailRow(string $logId, array $traceSteps): string {
217 $stepsHtml = '';
218 foreach ($traceSteps as $step) {
219 $detail = ($step['detail'] !== '')
220 ? $this->f->str_replace('{detail_text}', esc_html($step['detail']), $this->tpl('viewLogsTraceStepDetail.html'))
221 : '';
222 $stepsHtml .= $this->f->str_replace(
223 array('{step_name}', '{outcome_class}', '{outcome}', '{detail}'),
224 array(
225 esc_html($this->traceLabels->translate($step['step'])),
226 $this->traceLabels->outcomeClass($step['outcome']),
227 esc_html($this->traceLabels->translate($step['outcome'])),
228 $detail,
229 ),
230 $this->tpl('viewLogsTraceStep.html')
231 );
232 }
233 return $this->f->str_replace(
234 array('{row_id}', '{trace_steps}'),
235 array(esc_attr($logId), $stepsHtml),
236 $this->tpl('viewLogsTraceDetailRow.html')
237 );
238 }
239
240 private function tpl(string $name): string {
241 $raw = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . '/../html/' . $name, false);
242 return rtrim((string)$raw, "\n");
243 }
244 }
245