PluginProbe
404 Solution / 4.3.3
404 Solution v4.3.3
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 / CapturedTableHeaderRenderer.php

CapturedTableHeaderRenderer.php in 404 Solution 4.3.3, at includes/view/CapturedTableHeaderRenderer.php

162 lines 6.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 // allow-no-test-found: exercised by ViewTableRenderingTest
8
9 /**
10 * Renders the header cells for the Captured 404 URLs admin table.
11 */
12 class ABJ_404_Solution_CapturedTableHeaderRenderer {
13
14 /** @var ABJ_404_Solution_Functions */
15 private $f;
16
17 /** @var ABJ_404_Solution_View_Shared */
18 private $shared;
19
20 /** @var ABJ_404_Solution_ViewReadServiceInterface */
21 private $viewReadService;
22
23 /**
24 * @param ABJ_404_Solution_Functions $functions
25 * @param ABJ_404_Solution_View_Shared $shared
26 * @param ABJ_404_Solution_ViewReadServiceInterface $viewReadService
27 */
28 public function __construct($functions, ABJ_404_Solution_View_Shared $shared, $viewReadService) {
29 $this->f = $functions;
30 $this->shared = $shared;
31 $this->viewReadService = $viewReadService;
32 }
33
34 /**
35 * @param array<string, mixed> $tableOptions
36 * @return string
37 */
38 public function render(array $tableOptions): string {
39 $columns = array(
40 array('title' => __('URL', '404-solution'), 'orderby' => 'url'),
41 array('title' => __('Status', '404-solution'), 'orderby' => 'status'),
42 array('title' => __('Hits', '404-solution'), 'orderby' => 'logshits'),
43 array('title' => __('Created', '404-solution'), 'orderby' => 'timestamp', 'class' => 'hide-on-tablet'),
44 array('title' => __('Last Used', '404-solution'), 'orderby' => 'last_used'),
45 );
46
47 $headerCells = '';
48 foreach ($columns as $col) {
49 $headerCells .= $this->capturedHeaderCell($tableOptions, $col) . "\n";
50 }
51 return $headerCells;
52 }
53
54 /**
55 * @param array<string, mixed> $tableOptions
56 * @param array<string, mixed> $col
57 */
58 private function capturedHeaderCell(array $tableOptions, array $col): string {
59 $rawFilter = $tableOptions['filter'] ?? 0;
60 $currentFilter = is_scalar($rawFilter) ? $rawFilter : 0;
61 $rawOrderby = $col['orderby'] ?? '';
62 $orderby = is_scalar($rawOrderby) ? (string)$rawOrderby : '';
63
64 // Post-upgrade window: a URL sort whose narrow url_sort_key cannot be
65 // served index-ordered yet would filesort the captured majority (a
66 // max_statement_time risk on large sites). The query already serves the
67 // safe timestamp-DESC default (ViewQueryBuilder::resolveEffectiveSort), so
68 // render this header non-sortable with a progress tooltip rather than a
69 // clickable sort link that would silently not take effect. Self-heals:
70 // once the sort key is index-ready the header is sortable again with no
71 // user action. Shares the readiness + message with the Page Redirects
72 // renderer via View_Shared::pendingSortTooltipText.
73 $pendingTooltipBody = $this->shared->pendingSortTooltipText($orderby, $this->viewReadService);
74 if ($pendingTooltipBody !== '') {
75 return $this->capturedPendingHeaderCell($col, $pendingTooltipBody);
76 }
77
78 $sortUrl = "?page=" . ABJ404_PP . "&subpage=abj404_captured&filter=" . $currentFilter;
79 $rawFilterText = $tableOptions['filterText'] ?? '';
80 if (is_scalar($rawFilterText) && (string)$rawFilterText !== '') {
81 $sortUrl .= "&filterText=" . rawurlencode((string)$rawFilterText);
82 }
83 $sortUrl .= "&orderby=" . $orderby;
84 $sortState = $this->shared->getHeaderSortState($tableOptions, $orderby, false);
85 $sortUrl .= "&order=" . $sortState['nextOrder'];
86
87 $rawExtraClass = $col['class'] ?? '';
88 $extraClass = is_scalar($rawExtraClass) && $rawExtraClass !== ''
89 ? ' ' . esc_attr((string)$rawExtraClass) : '';
90 $rawThClass = $sortState['thClass'] ?? '';
91 $sortClass = trim((is_scalar($rawThClass) ? (string)$rawThClass : '') . $extraClass);
92 $classAttr = $sortClass ? ' class="' . trim($sortClass) . '"' : '';
93
94 $tooltipHtml = '';
95 if (isset($col['title_attr_html']) && !empty($col['title_attr_html'])) {
96 $tooltipHtml = $this->f->str_replace(
97 array('{more_info_label}', '{tooltip_body}'),
98 array(esc_attr__('More info', '404-solution'), $this->columnScalar($col, 'title_attr_html')),
99 $this->tpl('viewRedirectsTableHeaderTooltip.html')
100 );
101 }
102
103 return $this->f->str_replace(
104 array('{class_attr}', '{sort_url}', '{title}', '{sort_indicator}', '{tooltip_html}'),
105 array($classAttr, esc_url($sortUrl), esc_html($this->columnScalar($col, 'title')),
106 $this->sortStateScalar($sortState, 'indicator'), $tooltipHtml),
107 $this->tpl('viewRedirectsTableCapturedSortableHeader.html')
108 );
109 }
110
111 /**
112 * Render a non-sortable captured-tab header (plain title + progress tooltip,
113 * no sort link) for a column whose narrow sort key is not index-ready yet.
114 *
115 * @param array<string, mixed> $col
116 * @param string $tooltipBody The pending-sort progress message.
117 */
118 private function capturedPendingHeaderCell(array $col, string $tooltipBody): string {
119 $rawClass = $col['class'] ?? '';
120 $classValue = is_scalar($rawClass) ? (string)$rawClass : '';
121 $classAttr = $classValue !== '' ? ' class="' . esc_attr($classValue) . '"' : '';
122 $rawTitle = $col['title'] ?? '';
123 $titleValue = is_scalar($rawTitle) ? (string)$rawTitle : '';
124 $tooltipHtml = $this->f->str_replace(
125 array('{more_info_label}', '{tooltip_body}'),
126 array(esc_attr__('More info', '404-solution'), esc_html($tooltipBody)),
127 $this->tpl('viewRedirectsTableHeaderTooltip.html')
128 );
129 $tooltipHtml = $this->tagPendingSortTooltip($tooltipHtml, $this->columnScalar($col, 'orderby'));
130 return $this->f->str_replace(
131 array('{class_attr}', '{title}', '{tooltip_html}'),
132 array($classAttr, esc_html($titleValue), $tooltipHtml),
133 $this->tpl('viewRedirectsTableCapturedPendingHeader.html')
134 );
135 }
136
137 private function tagPendingSortTooltip(string $tooltipHtml, string $orderby): string {
138 return str_replace(
139 'class="abj404-header-tooltip lefty-tooltip"',
140 'class="abj404-header-tooltip lefty-tooltip" data-abj404-pending-sort="' . esc_attr($orderby) . '"',
141 $tooltipHtml
142 );
143 }
144
145 private function tpl(string $name): string {
146 $raw = ABJ_404_Solution_FileSystemService::readFileContents(dirname(__DIR__) . '/html/' . $name, false);
147 return rtrim((string)$raw, "\n");
148 }
149
150 /** @param array<string, mixed> $column */
151 private function columnScalar(array $column, string $key): string {
152 $value = $column[$key] ?? '';
153 return is_scalar($value) ? (string)$value : '';
154 }
155
156 /** @param array<string, mixed> $sortState */
157 private function sortStateScalar(array $sortState, string $key): string {
158 $value = $sortState[$key] ?? '';
159 return is_scalar($value) ? (string)$value : '';
160 }
161 }
162