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 / AdminStatusFilterTabs.php

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

228 lines 9.2 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 redirect and captured URL status filter links.
9 */
10 class ABJ_404_Solution_AdminStatusFilterTabs {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $f;
14 /** @var ABJ_404_Solution_PluginLogic */
15 private $logic;
16 /** @var ABJ_404_Solution_Logging */
17 private $logger;
18 /** @var ABJ_404_Solution_ViewReadServiceInterface */
19 private $viewReadService;
20 /** @var ABJ_404_Solution_View_ListTableChrome */
21 private $listTableChrome;
22
23 /** @param ABJ_404_Solution_ViewReadServiceInterface $viewReadService */
24 public function __construct(ABJ_404_Solution_Functions $functions, ABJ_404_Solution_PluginLogic $pluginLogic,
25 ABJ_404_Solution_Logging $logging, $viewReadService,
26 ABJ_404_Solution_View_ListTableChrome $listTableChrome) {
27 $this->f = $functions;
28 $this->logic = $pluginLogic;
29 $this->logger = $logging;
30 $this->viewReadService = $viewReadService;
31 $this->listTableChrome = $listTableChrome;
32 }
33
34 /**
35 * @param string $sub
36 * @param array<string, mixed> $tableOptions
37 */
38 public function render(string $sub, array $tableOptions): string {
39 if (empty($tableOptions)) {
40 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
41 }
42
43 return $this->tpl('viewLogsTabFiltersClearBar.html')
44 . $this->renderList($sub)
45 . $this->tpl('viewLogsTabFiltersClearBarClose.html');
46 }
47
48 public function renderList(string $sub): string {
49 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
50 $filter = isset($tableOptions['filter']) ? intval(is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0) : 0;
51 $url = $this->baseUrl($sub, $tableOptions);
52 $filterState = $this->filterState($sub);
53 $types = $filterState['types'];
54 $counts = $filterState['counts'];
55 $countsIncomplete = !empty($counts['_incomplete']);
56
57 $html = $this->f->str_replace(
58 '{placeholder_attr}',
59 $countsIncomplete ? ' data-tab-counts-placeholder="1"' : '',
60 $this->tpl('viewLogsTabFiltersListOpen.html')
61 );
62 if ($sub != 'abj404_captured') {
63 $html .= $this->filterItem('', $url, ($filter == 0) ? ' class="current"' : '', __('All', '404-solution'), $this->displayCount($counts['all'] ?? 0, $countsIncomplete));
64 }
65 foreach ($types as $type) {
66 $item = $this->typedFilterItem($sub, $url, $filter, (int)$type, $counts, $countsIncomplete);
67 if ($item !== '') {
68 $html .= $item;
69 }
70 }
71
72 $trashurl = $url . '&filter=' . ABJ404_TRASH_FILTER;
73 $trashClass = (($tableOptions['filter'] ?? 0) == ABJ404_TRASH_FILTER) ? ' class="current"' : '';
74 $html .= $this->filterItem(' | ', $trashurl, $trashClass, __('Trash', '404-solution'), $this->displayCount($counts['trash'] ?? 0, $countsIncomplete));
75 $html .= $this->tpl('viewLogsTabFiltersListClose.html');
76 $html .= $this->tpl('viewLogsTabFiltersFormGap.html');
77
78 return $html . $this->f->str_replace(
79 '{action_url}',
80 $this->listTableChrome->getBulkOperationsFormURL($sub, $tableOptions),
81 $this->tpl('viewLogsTabFiltersBulkForm.html')
82 );
83 }
84
85 /** @param array<string, mixed> $tableOptions */
86 private function baseUrl(string $sub, array $tableOptions): string {
87 $orderby = isset($tableOptions['orderby']) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url';
88 $order = isset($tableOptions['order']) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC';
89 $url = '?page=' . ABJ404_PP;
90 if ($sub == 'abj404_captured') {
91 $url .= '&subpage=abj404_captured';
92 } else if ($sub == 'abj404_redirects') {
93 $url .= '&subpage=abj404_redirects';
94 } else {
95 $this->logger->errorMessage('Unexpected sub page: ' . $sub);
96 }
97 $url .= '&orderby=' . sanitize_text_field($orderby);
98 $url .= '&order=' . sanitize_text_field($order);
99
100 return $url;
101 }
102
103 /**
104 * @return array{types: array<int, int>, counts: array<string, int>}
105 */
106 private function filterState(string $sub): array {
107 global $abj404_redirect_types;
108 global $abj404_captured_types;
109
110 if ($sub == 'abj404_redirects') {
111 $types = $this->numericTypes($abj404_redirect_types, array(ABJ404_STATUS_MANUAL, ABJ404_STATUS_AUTO, ABJ404_STATUS_REGEX));
112 return array('types' => $types, 'counts' => $this->viewReadService->getRedirectStatusCounts());
113 }
114 if ($sub == 'abj404_captured') {
115 $types = $this->numericTypes($abj404_captured_types, array(ABJ404_STATUS_CAPTURED, ABJ404_STATUS_IGNORED, ABJ404_STATUS_LATER));
116 return array('types' => $types, 'counts' => $this->viewReadService->getCapturedStatusCounts());
117 }
118
119 $this->logger->debugMessage('Unexpected sub type for tab filter: ' . $sub);
120 return array('types' => array(ABJ404_STATUS_CAPTURED, ABJ404_STATUS_IGNORED, ABJ404_STATUS_LATER), 'counts' => array());
121 }
122
123 /**
124 * @param mixed $candidate
125 * @param array<int, int> $defaults
126 * @return array<int, int>
127 */
128 private function numericTypes($candidate, array $defaults): array {
129 if (!is_array($candidate)) {
130 return $defaults;
131 }
132 // One pass: validate and convert together. Two passes (validate
133 // everything, then convert everything) left the conversion loop's own
134 // guards provably dead -- it could only ever see values the first loop
135 // had already accepted -- so its `return $defaults` was unreachable
136 // code standing in for a check that had already happened. Appending to
137 // $ints also reindexes, which is what the array_values() call the
138 // first pass needed was for.
139 $ints = array();
140 foreach ($candidate as $value) {
141 if (is_int($value)) {
142 $ints[] = $value;
143 continue;
144 }
145 if (is_string($value) && ctype_digit($value)) {
146 $ints[] = intval($value);
147 continue;
148 }
149 return $defaults;
150 }
151 if (empty($ints)) {
152 return $defaults;
153 }
154 return $ints;
155 }
156
157 /** @param array<string, int> $counts */
158 private function typedFilterItem(string $sub, string $url, int $filter, int $type, array $counts, bool $countsIncomplete): string {
159 if ($type == ABJ404_STATUS_REGEX) {
160 return '';
161 }
162 $definition = $this->filterDefinition($type, $counts);
163 if ($definition === null) {
164 $this->logger->errorMessage('Unrecognized redirect type in View: ' . esc_html((string)$type));
165 $definition = array('title' => __('Unknown', '404-solution'), 'count' => 0);
166 }
167
168 $prefix = ($sub != 'abj404_captured' || $type != ABJ404_STATUS_CAPTURED) ? ' | ' : '';
169 $class = ($filter == $type) ? ' class="current"' : '';
170 return $this->filterItem(
171 $prefix,
172 $url . '&filter=' . $type,
173 $class,
174 $definition['title'],
175 $this->displayCount($definition['count'], $countsIncomplete)
176 );
177 }
178
179 /**
180 * @param array<string, int> $counts
181 * @return array{title: string, count: int}|null
182 */
183 private function filterDefinition(int $type, array $counts): ?array {
184 $definitions = array(
185 ABJ404_STATUS_MANUAL => array(
186 'title' => __('Manual Redirects', '404-solution'),
187 'count' => intval($counts['manual'] ?? 0) + intval($counts['regex'] ?? 0),
188 ),
189 ABJ404_STATUS_AUTO => array(
190 'title' => __('Automatic Redirects', '404-solution'),
191 'count' => intval($counts['auto'] ?? 0),
192 ),
193 ABJ404_STATUS_CAPTURED => array(
194 'title' => 'Captured URLs',
195 'count' => intval($counts['captured'] ?? 0),
196 ),
197 ABJ404_STATUS_IGNORED => array(
198 'title' => 'Ignored 404s',
199 'count' => intval($counts['ignored'] ?? 0),
200 ),
201 ABJ404_STATUS_LATER => array(
202 'title' => 'Organize Later',
203 'count' => intval($counts['later'] ?? 0),
204 ),
205 );
206 return $definitions[$type] ?? null;
207 }
208
209 /** @param int|string $count */
210 private function filterItem(string $prefix, string $url, string $class, string $title, $count): string {
211 return $this->f->str_replace(
212 array('{prefix}', '{url}', '{class_attr}', '{title}', '{count}'),
213 array($prefix, esc_url($url), $class, $title, esc_html((string)$count)),
214 $this->tpl('viewLogsTabFilterItem.html')
215 );
216 }
217
218 /** @param mixed $count */
219 private function displayCount($count, bool $incomplete): string {
220 return $incomplete ? '-' : (string)intval(is_scalar($count) ? $count : 0);
221 }
222
223 private function tpl(string $name): string {
224 $raw = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . '/../html/' . $name, false);
225 return rtrim((string)$raw, "\n");
226 }
227 }
228