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 / view / View_RedirectsTable.php

View_RedirectsTable.php in 404 Solution 4.3.0, at includes/view/View_RedirectsTable.php

258 lines 10.6 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 require_once __DIR__ . '/../redirects/RedirectDeadDestinationStore.php';
8 require_once __DIR__ . '/../admin/RedirectsTableColumns.php';
9 require_once __DIR__ . '/../admin/RedirectDestinationWarningPolicy.php';
10 require_once __DIR__ . '/../admin/RedirectDestinationLinkResolver.php';
11 require_once __DIR__ . '/../admin/RedirectRowActionsPresenter.php';
12 require_once __DIR__ . '/../admin/RedirectRowPresenter.php';
13 require_once __DIR__ . '/../admin/RedirectAddModalPresenter.php';
14 require_once __DIR__ . '/../admin/RedirectsTablePagePresenter.php';
15
16 /**
17 * Page Redirects admin page renderer. Owns the page wrapper, the table shell,
18 * and the modern Add Redirect modal. Row presentation, columns, destination
19 * warnings, and destination link resolution live in admin presenter/policy
20 * classes.
21 *
22 * Outside callers (via the View facade __call dispatch):
23 * - includes/ajax/ViewUpdater.php (pagination AJAX -> getAdminRedirectsPageTable)
24 * - PluginLogic admin page entry points (echoAdminRedirectsPage)
25 * - tests/BugProof_ReleaseReadinessTest greps buildRedirectsColumnDefs source
26 */
27 class ABJ_404_Solution_View_RedirectsTable extends ABJ_404_Solution_ViewComponent {
28
29 /** @var ABJ_404_Solution_RedirectsTableColumns|null */
30 private $redirectsTableColumns = null;
31
32 /** @var ABJ_404_Solution_RedirectDestinationWarningPolicy|null */
33 private $destinationWarningPolicy = null;
34
35 /** @var ABJ_404_Solution_RedirectDestinationLinkResolver|null */
36 private $destinationLinkResolver = null;
37
38 /** @var ABJ_404_Solution_RedirectRowPresenter|null */
39 private $redirectRowPresenter = null;
40
41 /** @var ABJ_404_Solution_RedirectAddModalPresenter|null */
42 private $redirectAddModalPresenter = null;
43
44 /** @var ABJ_404_Solution_RedirectsTablePagePresenter|null */
45 private $redirectsTablePagePresenter = null;
46
47 /** Load a template file from includes/html/ and trim its trailing newline. */
48 private function tpl(string $name): string {
49 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
50 return rtrim((string)$raw, "\n");
51 }
52
53 /**
54 * @return void
55 */
56 public function echoAdminRedirectsPage() {
57 echo $this->pagePresenter()->render();
58 }
59
60 /**
61 * Echo the modern Add Redirect modal.
62 *
63 * @param array<string, mixed> $tableOptions
64 * @return void
65 */
66 public function echoAddRedirectModal($tableOptions) {
67 echo $this->addModalPresenter()->render($tableOptions);
68 }
69
70 /**
71 * Compute (and remember) the table-data signature for the redirects or
72 * captured tab WITHOUT rendering the table HTML, status counts, or
73 * pagination.
74 *
75 * This is the renderless, READ-ONLY path the background detect-only refresh
76 * uses (report.md Finding 3, report3.md Finding 2). It is not metadata-only:
77 * it runs the SAME one-page read the full render runs -- getTableOptions ->
78 * getRedirectsForView -> rememberTableDataSignature -- so the signature it
79 * returns is byte-identical to the one a full render would stamp (same rows,
80 * same live resolution). What it skips is all the foreground work an idle
81 * poll has no use for: the HTML build, the status-count aggregates, the two
82 * pagination-link builds, AND the live denorm write-back (suppressed via the
83 * _abj404_suppress_denorm_writeback flag below) -- a change-detection probe
84 * must observe, never mutate. The full render still persists, so the stored
85 * denorm values stay fresh. Both tabs read through getRedirectsForView, so
86 * this one method serves both subpages.
87 *
88 * @param string $sub 'abj404_redirects' or 'abj404_captured'.
89 * @return string The remembered signature for $sub.
90 */
91 public function computeTableDataSignature($sub) {
92 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
93 $tableOptions['_abj404_suppress_denorm_writeback'] = true;
94 $rows = $this->viewReadService->getRedirectsForView($sub, $tableOptions);
95 /** @var array<int, array<string, mixed>> $typedRows */
96 $typedRows = array_values(array_filter($rows, 'is_array'));
97 $this->shared->rememberTableDataSignature($sub, $typedRows);
98 return $this->shared->getCurrentTableDataSignature($sub);
99 }
100
101 /**
102 * @param string $sub
103 * @return string
104 */
105 public function getAdminRedirectsPageTable($sub) {
106 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
107 $columns = $this->buildRedirectsColumnDefs($tableOptions);
108
109 $headerColumns = $this->logs->getTableColumns($sub, $columns);
110
111 $deadDestIds = (new ABJ_404_Solution_RedirectDeadDestinationStore())->getIds();
112
113 $rows = $this->viewReadService->getRedirectsForView($sub, $tableOptions);
114 /** @var array<int, array<string, mixed>> $typedRedirectRows */
115 $typedRedirectRows = array_values(array_filter($rows, 'is_array'));
116 $this->shared->rememberTableDataSignature($sub, $typedRedirectRows);
117 $displayed = 0;
118 $y = 1;
119 $bodyRows = '';
120 foreach ($typedRedirectRows as $row) {
121 $bodyRows .= $this->buildRedirectRowHTML($row, $sub, $tableOptions, $deadDestIds, $y);
122 $y = ($y === 0) ? 1 : 0;
123 $displayed++;
124 }
125 if ($displayed == 0) {
126 // The single-table denorm read (denorm Step 3b) is always complete
127 // and serveable, so zero displayed rows is a genuinely empty
128 // listing. There is no "still preparing" state to distinguish.
129 $bodyRows .= $this->f->str_replace(
130 array('{title}', '{help}'),
131 array(
132 __('No Redirect Records To Display', '404-solution'),
133 __('Redirects will appear here once created.', '404-solution'),
134 ),
135 $this->tpl('viewRedirectsTableRedirectsEmptyState.html')
136 );
137 }
138
139 return $this->f->str_replace(
140 array('{header_columns}', '{body_rows}'),
141 array($headerColumns, $bodyRows),
142 $this->tpl('viewRedirectsTableRedirectsTableShell.html')
143 );
144 }
145
146 /**
147 * @param array<string, mixed> $tableOptions
148 * @return array<string, array<string, string>>
149 */
150 public function buildRedirectsColumnDefs(array $tableOptions): array {
151 return $this->columns()->build($tableOptions);
152 }
153
154 /**
155 * @param array<string, mixed> $row
156 * @param string $sub
157 * @param array<string, mixed> $tableOptions
158 * @param array<mixed> $deadDestIds
159 * @param int $y
160 * @return string
161 */
162 public function buildRedirectRowHTML(array $row, string $sub, array $tableOptions, array $deadDestIds, int $y): string {
163 return $this->rowPresenter()->render($row, $sub, $tableOptions, $deadDestIds, $y);
164 }
165
166 /**
167 * @param ABJ_404_Solution_RedirectDestinationWarningContext $ctx
168 * @return array{exists: string, notExists: string, text: string, destForView: string}
169 */
170 public function resolveDestinationWarnings(ABJ_404_Solution_RedirectDestinationWarningContext $ctx): array {
171 return $this->warningPolicy()->resolve($ctx->row, $ctx->rowType, $ctx->rowFinalDest,
172 $ctx->destForView, $ctx->destinationIsMissing, $ctx->deadDestIds);
173 }
174
175 /**
176 * @param array<string, string> $replacements
177 * @return string
178 */
179 public function fillRedirectRowTemplate(array $replacements): string {
180 return $this->rowPresenter()->fillRedirectRowTemplate($replacements);
181 }
182
183 /**
184 * @param mixed $rawScore
185 * @param string $rowEngine
186 * @return string
187 */
188 public function buildScoreCell($rawScore, string $rowEngine): string {
189 return $this->rowPresenter()->buildScoreCell($rawScore, $rowEngine);
190 }
191
192 /**
193 * @param mixed $rowType
194 * @param string $rowFinalDest
195 * @return array{link: string, title: string}
196 */
197 public function resolveRedirectDestLink($rowType, string $rowFinalDest): array {
198 return $this->linkResolver()->resolve($rowType, $rowFinalDest);
199 }
200
201 private function columns(): ABJ_404_Solution_RedirectsTableColumns {
202 if (!($this->redirectsTableColumns instanceof ABJ_404_Solution_RedirectsTableColumns)) {
203 $this->redirectsTableColumns = new ABJ_404_Solution_RedirectsTableColumns();
204 }
205 return $this->redirectsTableColumns;
206 }
207
208 private function warningPolicy(): ABJ_404_Solution_RedirectDestinationWarningPolicy {
209 if (!($this->destinationWarningPolicy instanceof ABJ_404_Solution_RedirectDestinationWarningPolicy)) {
210 $this->destinationWarningPolicy = new ABJ_404_Solution_RedirectDestinationWarningPolicy();
211 }
212 return $this->destinationWarningPolicy;
213 }
214
215 private function linkResolver(): ABJ_404_Solution_RedirectDestinationLinkResolver {
216 if (!($this->destinationLinkResolver instanceof ABJ_404_Solution_RedirectDestinationLinkResolver)) {
217 $this->destinationLinkResolver = new ABJ_404_Solution_RedirectDestinationLinkResolver($this->logger);
218 }
219 return $this->destinationLinkResolver;
220 }
221
222 private function rowPresenter(): ABJ_404_Solution_RedirectRowPresenter {
223 if (!($this->redirectRowPresenter instanceof ABJ_404_Solution_RedirectRowPresenter)) {
224 $this->redirectRowPresenter = new ABJ_404_Solution_RedirectRowPresenter(
225 $this->f,
226 $this->warningPolicy(),
227 $this->linkResolver(),
228 new ABJ_404_Solution_RedirectRowActionsPresenter($this->f, $this->shared)
229 );
230 }
231 return $this->redirectRowPresenter;
232 }
233
234 private function addModalPresenter(): ABJ_404_Solution_RedirectAddModalPresenter {
235 if (!($this->redirectAddModalPresenter instanceof ABJ_404_Solution_RedirectAddModalPresenter)) {
236 $this->redirectAddModalPresenter = new ABJ_404_Solution_RedirectAddModalPresenter(
237 $this->f,
238 $this->optionsPresenter,
239 $this->redirectTypeUI,
240 $this->redirectConditions
241 );
242 }
243 return $this->redirectAddModalPresenter;
244 }
245
246 private function pagePresenter(): ABJ_404_Solution_RedirectsTablePagePresenter {
247 if (!($this->redirectsTablePagePresenter instanceof ABJ_404_Solution_RedirectsTablePagePresenter)) {
248 $this->redirectsTablePagePresenter = new ABJ_404_Solution_RedirectsTablePagePresenter(
249 $this->f,
250 $this->logic,
251 $this->listTableChrome,
252 $this->addModalPresenter()
253 );
254 }
255 return $this->redirectsTablePagePresenter;
256 }
257 }
258