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

AdminPaginationLinks.php in 404 Solution 4.3.0, at includes/admin/AdminPaginationLinks.php

221 lines 11.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 * Builds WordPress-style pagination controls for admin list pages.
9 */
10 class ABJ_404_Solution_AdminPaginationLinks {
11
12 /** @var ABJ_404_Solution_Functions */
13 private $f;
14 /** @var ABJ_404_Solution_PluginLogic */
15 private $logic;
16 /** @var ABJ_404_Solution_View_Shared */
17 private $shared;
18 /** @var ABJ_404_Solution_ViewReadServiceInterface */
19 private $viewReadService;
20
21 /** @param ABJ_404_Solution_ViewReadServiceInterface $viewReadService */
22 public function __construct(ABJ_404_Solution_Functions $functions,
23 ABJ_404_Solution_PluginLogic $pluginLogic, ABJ_404_Solution_View_Shared $shared,
24 $viewReadService) {
25 $this->f = $functions;
26 $this->logic = $pluginLogic;
27 $this->shared = $shared;
28 $this->viewReadService = $viewReadService;
29 }
30
31 public function render(string $sub): string {
32 $state = $this->paginationState($sub);
33 return $this->renderTemplate($sub, $state);
34 }
35
36 /**
37 * @return array{tableOptions: array<string, mixed>, logsid: string, orderby: string, order: string,
38 * filter: string, paged: int, perpage: int, totalPages: int, numRecords: int, filterText: string,
39 * urls: array{first: string, previous: string, next: string, last: string}}
40 */
41 private function paginationState(string $sub): array {
42 $tableOptions = $this->logic->settingsUpdate()->getTableOptions($sub);
43 $logsid = array_key_exists('logsid', $tableOptions) && is_scalar($tableOptions['logsid']) ? $tableOptions['logsid'] : 0;
44 $orderby = array_key_exists('orderby', $tableOptions) && is_string($tableOptions['orderby']) ? $tableOptions['orderby'] : 'url';
45 $order = array_key_exists('order', $tableOptions) && is_string($tableOptions['order']) ? $tableOptions['order'] : 'ASC';
46 $filter = array_key_exists('filter', $tableOptions) && is_scalar($tableOptions['filter']) ? $tableOptions['filter'] : 0;
47
48 $logsidForUrl = $this->scalarToString($logsid);
49 $filterForUrl = (int)$filter;
50 $url = $this->baseUrl($sub, $logsidForUrl, $orderby, $order, $filterForUrl);
51 $numRecords = (int)(($sub == 'abj404_logs')
52 ? $this->viewReadService->getLogsCount((int)$logsid)
53 : $this->viewReadService->getRedirectsForViewCount($sub, $tableOptions));
54
55 $perpage = absint(array_key_exists('perpage', $tableOptions) && is_scalar($tableOptions['perpage']) ? $tableOptions['perpage'] : ABJ404_OPTION_MIN_PERPAGE);
56 if ($perpage == 0) {
57 $perpage = ABJ404_OPTION_MIN_PERPAGE;
58 }
59 $paged = absint(array_key_exists('paged', $tableOptions) && is_scalar($tableOptions['paged']) ? $tableOptions['paged'] : 1);
60 if ($paged == 0) {
61 $paged = 1;
62 }
63
64 $totalPages = (int)ceil($numRecords / $perpage);
65 if ($totalPages == 0) {
66 $totalPages = 1;
67 }
68
69 $urls = $this->navigationUrls($url, $paged, $totalPages);
70 $filterText = array_key_exists('filterText', $tableOptions) && is_string($tableOptions['filterText']) ? $tableOptions['filterText'] : '';
71 if ($filterText != '') {
72 $urls = $this->appendFilterText($urls, $filterText);
73 }
74
75 return array(
76 'tableOptions' => $tableOptions,
77 'logsid' => $logsidForUrl,
78 'orderby' => $orderby,
79 'order' => $order,
80 'filter' => (string)$filterForUrl,
81 'paged' => $paged,
82 'perpage' => $perpage,
83 'totalPages' => $totalPages,
84 'numRecords' => $numRecords,
85 'filterText' => $filterText,
86 'urls' => $urls,
87 );
88 }
89
90 private function baseUrl(string $sub, string $logsid, string $orderby, string $order, int $filter): string {
91 $url = '?page=' . ABJ404_PP;
92 if ($sub !== '') {
93 $url .= '&subpage=' . rawurlencode((string)$sub);
94 }
95 if ($sub == 'abj404_logs') {
96 $url .= '&id=' . $logsid;
97 }
98 $url .= '&orderby=' . sanitize_text_field($orderby);
99 $url .= '&order=' . sanitize_text_field($order);
100 // Emit the signed filter value: the trash (-1) and handled (-2) views
101 // are negative sentinels. absint() would fold -1 -> 1 (Manual) and
102 // -2 -> 2 (Auto), so paginating within Trash/Handled would silently jump
103 // to a different tab. $filter is already an int from paginationState().
104 $url .= '&filter=' . (int)$filter;
105 return $url;
106 }
107
108 /**
109 * @return array{first: string, previous: string, next: string, last: string}
110 */
111 private function navigationUrls(string $url, int $paged, int $totalPages): array {
112 $prevurl = ($paged == 1) ? $url : $url . '&paged=' . ($paged - 1);
113 if ($paged + 1 > $totalPages) {
114 $nexturl = ($paged == 1) ? $url : $url . '&paged=' . $paged;
115 $lasturl = ($paged == 1) ? $url : $url . '&paged=' . $paged;
116 } else {
117 $nexturl = $url . '&paged=' . ($paged + 1);
118 $lasturl = $url . '&paged=' . $totalPages;
119 }
120
121 return array(
122 'first' => $url,
123 'previous' => $prevurl,
124 'next' => $nexturl,
125 'last' => $lasturl,
126 );
127 }
128
129 /**
130 * @param array{first: string, previous: string, next: string, last: string} $urls
131 * @return array{first: string, previous: string, next: string, last: string}
132 */
133 private function appendFilterText(array $urls, string $filterText): array {
134 $encoded = rawurlencode($filterText);
135 foreach ($urls as $key => $url) {
136 $urls[$key] = $url . '&filterText=' . $encoded;
137 }
138 return $urls;
139 }
140
141 /**
142 * @param array{tableOptions: array<string, mixed>, logsid: string, orderby: string, order: string,
143 * filter: string, paged: int, perpage: int, totalPages: int, numRecords: int, filterText: string,
144 * urls: array{first: string, previous: string, next: string, last: string}} $state
145 */
146 private function renderTemplate(string $sub, array $state): string {
147 $tableOptions = $state['tableOptions'];
148 $logsid = $state['logsid'];
149 $orderby = $state['orderby'];
150 $order = $state['order'];
151 $filter = $state['filter'];
152 $paged = $state['paged'];
153 $totalPages = $state['totalPages'];
154 $numRecords = $state['numRecords'];
155 $filterText = $state['filterText'];
156 $urls = $state['urls'];
157 // Match the WordPress core WP_List_Table::pagination() text shape:
158 // "{N} items" outer count and "{X} of {Y}" inline indicator. The
159 // earlier "1 - 25 of 487 redirects" / "Page 25 of 487" wording made
160 // the strip too wide to fit on the same tablenav row as bulk actions.
161 $currentlyShowingText = sprintf(
162 /* translators: %s is the total record count, already number-formatted for the current locale */
163 _n('%s item', '%s items', $numRecords, '404-solution'),
164 number_format_i18n($numRecords)
165 );
166 $currentPageText = sprintf(
167 /* translators: %1$d is current page number, %2$d is total page count */
168 esc_html__('%1$d of %2$d', '404-solution'),
169 $paged,
170 $totalPages
171 );
172
173 $html = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . '/../html/paginationLinks.html');
174 $html = $this->f->str_replace('{TEXT_BEFORE_LINKS}', $currentlyShowingText, $html);
175 $html = $this->f->str_replace('{BTN_FIRST_PAGE}', $this->pageButton($paged <= 1, $urls['first'], esc_attr__('Go to first page', '404-solution'), '&laquo;'), $html);
176 $html = $this->f->str_replace('{BTN_PREV_PAGE}', $this->pageButton($paged <= 1, $urls['previous'], esc_attr__('Go to previous page', '404-solution'), '&lsaquo;'), $html);
177 $html = $this->f->str_replace('{TEXT_CURRENT_PAGE}', $currentPageText, $html);
178 $html = $this->f->str_replace('{BTN_NEXT_PAGE}', $this->pageButton($paged >= $totalPages, $urls['next'], esc_attr__('Go to next page', '404-solution'), '&rsaquo;'), $html);
179 $html = $this->f->str_replace('{BTN_LAST_PAGE}', $this->pageButton($paged >= $totalPages, $urls['last'], esc_attr__('Go to last page', '404-solution'), '&raquo;'), $html);
180 $html = $this->f->str_replace('{filterText}', esc_attr($filterText), $html);
181 $html = $this->f->str_replace('{data-pagination-ajax-url}', esc_attr(admin_url('admin-ajax.php')), $html);
182 $html = $this->f->str_replace('{data-pagination-ajax-action}', esc_attr('ajaxUpdatePaginationLinks'), $html);
183 $html = $this->f->str_replace('{data-pagination-ajax-subpage}', esc_attr($sub), $html);
184 $html = $this->f->str_replace('{data-pagination-ajax-nonce}', esc_attr(wp_create_nonce('abj404_updatePaginationLink')), $html);
185 $html = $this->f->str_replace('{data-lazy-backfill-ajax-url}', esc_attr(admin_url('admin-ajax.php')), $html);
186 $html = $this->f->str_replace('{data-lazy-backfill-nonce}', esc_attr(wp_create_nonce('abj404_runLazyBackfill')), $html);
187 $html = $this->f->str_replace('{data-pagination-inflight-nonce}', esc_attr(wp_create_nonce('abj404_fetchInflightStage')), $html);
188 $html = $this->f->str_replace('{data-pagination-current-signature}', esc_attr($this->shared->getCurrentTableDataSignature($sub)), $html);
189 $html = $this->f->str_replace('{data-pagination-current-orderby}', esc_attr($orderby), $html);
190 $html = $this->f->str_replace('{data-pagination-current-order}', esc_attr($order), $html);
191 $html = $this->f->str_replace('{data-pagination-current-filter}', esc_attr($filter), $html);
192 $html = $this->f->str_replace('{data-pagination-current-paged}', esc_attr((string)$paged), $html);
193 $rawScoreRange = $tableOptions['score_range'] ?? 'all';
194 $scoreRangeForAttr = is_string($rawScoreRange) ? $rawScoreRange : 'all';
195 $html = $this->f->str_replace('{data-pagination-current-score-range}', esc_attr($scoreRangeForAttr), $html);
196 $html = $this->f->str_replace('{data-pagination-current-logsid}', esc_attr($logsid), $html);
197 $autoRefresh = (($sub === 'abj404_redirects' || $sub === 'abj404_captured' || $sub === 'abj404_logs') ? '1' : '0');
198 $html = $this->f->str_replace('{data-pagination-auto-refresh}', esc_attr($autoRefresh), $html);
199 $html = $this->f->str_replace('{data-pagination-refresh-available-text}', esc_attr(__('Refresh available', '404-solution')), $html);
200
201 return $this->f->doNormalReplacements($html);
202 }
203
204 private function pageButton(bool $disabled, string $href, string $label, string $glyph): string {
205 if ($disabled) {
206 return $this->f->str_replace(array('{label}', '{glyph}'), array($label, $glyph), $this->tpl('viewLogsPaginationBtnDisabled.html'));
207 }
208 return $this->f->str_replace(array('{href}', '{label}', '{glyph}'), array(esc_url($href), $label, $glyph), $this->tpl('viewLogsPaginationBtnLink.html'));
209 }
210
211 /** @param bool|float|int|string $value */
212 private function scalarToString($value): string {
213 return (string)$value;
214 }
215
216 private function tpl(string $name): string {
217 $raw = ABJ_404_Solution_FileSystemService::readFileContents(__DIR__ . '/../html/' . $name, false);
218 return rtrim((string)$raw, "\n");
219 }
220 }
221