Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 94 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
PagerRenderer | |
0.00% |
0 / 94 |
|
0.00% |
0 / 5 |
210 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
render | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
6 | |||
buildSelect | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
buildButtonData | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
42 | |||
buildHiddenFields | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * PagerRenderer.php |
4 | * |
5 | * This file is part of the Codex PHP library, which provides a PHP-based interface for creating |
6 | * UI components consistent with the Codex design system. |
7 | * |
8 | * The `PagerRenderer` class leverages the `TemplateRenderer` and `Sanitizer` utilities to ensure the |
9 | * component object is rendered according to Codex design system standards. |
10 | * |
11 | * @category Renderer |
12 | * @package Codex\Renderer |
13 | * @since 0.1.0 |
14 | * @author Doğu Abaris <abaris@null.net> |
15 | * @license https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later |
16 | * @link https://doc.wikimedia.org/codex/main/ Codex Documentation |
17 | */ |
18 | |
19 | namespace Wikimedia\Codex\Renderer; |
20 | |
21 | use InvalidArgumentException; |
22 | use Wikimedia\Codex\Builder\PagerBuilder; |
23 | use Wikimedia\Codex\Component\Pager; |
24 | use Wikimedia\Codex\Contract\ILocalizer; |
25 | use Wikimedia\Codex\Contract\Renderer\IRenderer; |
26 | use Wikimedia\Codex\Contract\Renderer\ITemplateRenderer; |
27 | use Wikimedia\Codex\Traits\AttributeResolver; |
28 | use Wikimedia\Codex\Utility\Codex; |
29 | use Wikimedia\Codex\Utility\Sanitizer; |
30 | |
31 | /** |
32 | * PagerRenderer is responsible for rendering the HTML markup |
33 | * for a Pager component using a Mustache template. |
34 | * |
35 | * This class uses the `TemplateRenderer` and `Sanitizer` utilities to manage |
36 | * the template rendering process, ensuring that the component object's HTML |
37 | * output adheres to the Codex design system's standards. |
38 | * |
39 | * @category Renderer |
40 | * @package Codex\Renderer |
41 | * @since 0.1.0 |
42 | * @author Doğu Abaris <abaris@null.net> |
43 | * @license https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later |
44 | * @link https://doc.wikimedia.org/codex/main/ Codex Documentation |
45 | */ |
46 | class PagerRenderer implements IRenderer { |
47 | |
48 | /** |
49 | * Use the AttributeResolver trait |
50 | */ |
51 | use AttributeResolver; |
52 | |
53 | /** |
54 | * The sanitizer instance used for content sanitization. |
55 | */ |
56 | private Sanitizer $sanitizer; |
57 | |
58 | /** |
59 | * The template renderer instance. |
60 | */ |
61 | private ITemplateRenderer $templateRenderer; |
62 | |
63 | /** |
64 | * The localization instance implementing ILocalizer. |
65 | */ |
66 | private ILocalizer $localizer; |
67 | |
68 | /** |
69 | * The Codex instance for utility methods. |
70 | */ |
71 | private Codex $codex; |
72 | |
73 | /** |
74 | * Array of icon classes for the pager buttons. |
75 | */ |
76 | private array $iconClasses = [ |
77 | "first" => "cdx-table-pager__icon--first", |
78 | "previous" => "cdx-table-pager__icon--previous", |
79 | "next" => "cdx-table-pager__icon--next", |
80 | "last" => "cdx-table-pager__icon--last", |
81 | ]; |
82 | |
83 | /** |
84 | * Constructor to initialize the PagerRenderer with a sanitizer, a template renderer, and a language handler. |
85 | * |
86 | * @since 0.1.0 |
87 | * @param Sanitizer $sanitizer The sanitizer instance used for content sanitization. |
88 | * @param ITemplateRenderer $templateRenderer The template renderer instance used for rendering templates. |
89 | * @param ILocalizer $localizer The localizer instance used for localization and translations. |
90 | */ |
91 | public function __construct( |
92 | Sanitizer $sanitizer, |
93 | ITemplateRenderer $templateRenderer, |
94 | ILocalizer $localizer |
95 | ) { |
96 | $this->sanitizer = $sanitizer; |
97 | $this->templateRenderer = $templateRenderer; |
98 | $this->localizer = $localizer; |
99 | $this->codex = new Codex(); |
100 | } |
101 | |
102 | /** |
103 | * Renders the HTML for a pager component. |
104 | * |
105 | * Uses the provided Pager to generate HTML markup adhering to the Codex design system. |
106 | * |
107 | * @since 0.1.0 |
108 | * @param Pager $component The Pager object to render. |
109 | * @return string The rendered HTML string for the component. |
110 | */ |
111 | public function render( $component ): string { |
112 | if ( !$component instanceof Pager ) { |
113 | throw new InvalidArgumentException( "Expected instance of Pager, got " . get_class( $component ) ); |
114 | } |
115 | |
116 | $selectHtml = $this->buildSelect( $component ); |
117 | |
118 | $buttons = [ |
119 | 'firstButton' => $this->buildButtonData( $component, PagerBuilder::ACTION_FIRST ), |
120 | 'prevButton' => $this->buildButtonData( $component, PagerBuilder::ACTION_PREVIOUS ), |
121 | 'nextButton' => $this->buildButtonData( $component, PagerBuilder::ACTION_NEXT ), |
122 | 'lastButton' => $this->buildButtonData( $component, PagerBuilder::ACTION_LAST ), |
123 | ]; |
124 | |
125 | $hiddenFields = $this->buildHiddenFields( $component ); |
126 | |
127 | $pagerData = [ |
128 | 'id' => $this->sanitizer->sanitizeText( $component->getId() ), |
129 | 'position' => $this->sanitizer->sanitizeText( $component->getPosition() ), |
130 | 'startOrdinal' => $component->getStartOrdinal(), |
131 | 'endOrdinal' => $component->getEndOrdinal(), |
132 | 'totalResults' => $component->getTotalResults(), |
133 | 'isPending' => $component->getEndOrdinal() < $component->getStartOrdinal(), |
134 | 'hasTotalResults' => $component->getTotalResults() > 0, |
135 | 'select' => $selectHtml, |
136 | 'firstButton' => $buttons['firstButton'], |
137 | 'prevButton' => $buttons['prevButton'], |
138 | 'nextButton' => $buttons['nextButton'], |
139 | 'lastButton' => $buttons['lastButton'], |
140 | 'hiddenFields' => $hiddenFields, |
141 | ]; |
142 | |
143 | return $this->templateRenderer->render( 'pager.mustache', $pagerData ); |
144 | } |
145 | |
146 | /** |
147 | * Build the select dropdown data to be passed to the Mustache template. |
148 | * |
149 | * @since 0.1.0 |
150 | * @param Pager $pager |
151 | * @return string The select dropdown data for Mustache. |
152 | */ |
153 | protected function buildSelect( Pager $pager ): string { |
154 | $sizeOptions = $pager->getPaginationSizeOptions(); |
155 | $currentLimit = $pager->getLimit(); |
156 | |
157 | $options = []; |
158 | |
159 | foreach ( $sizeOptions as $size ) { |
160 | $options[] = [ |
161 | 'value' => $this->sanitizer->sanitizeText( (string)$size ), |
162 | 'text' => $this->localizer->msg( |
163 | 'cdx-table-pager-items-per-page-current', [ 'variables' => [ $size ] ] |
164 | ), |
165 | 'selected' => ( $size == $currentLimit ), |
166 | ]; |
167 | } |
168 | |
169 | return $this->codex->select() |
170 | ->setOptions( $options ) |
171 | ->setSelectedOption( (string)$currentLimit ) |
172 | ->setAttributes( [ |
173 | 'name' => 'limit', |
174 | 'onchange' => 'this.form.submit();', |
175 | 'class' => 'cdx-select', |
176 | ] )->build()->getHtml(); |
177 | } |
178 | |
179 | /** |
180 | * Build an individual pagination button. |
181 | * |
182 | * Generates the data array for a single pagination button based on the action. |
183 | * |
184 | * @since 0.1.0 |
185 | * @param Pager $pager The Pager object. |
186 | * @param string $action The action for the button (e.g., PagerBuilder::ACTION_FIRST). |
187 | * @return array The data array for the pagination button. |
188 | */ |
189 | protected function buildButtonData( Pager $pager, string $action ): array { |
190 | $iconClass = $this->iconClasses[$action] ?? ''; |
191 | $dir = ''; |
192 | switch ( $action ) { |
193 | case PagerBuilder::ACTION_FIRST: |
194 | $disabled = $pager->isFirstDisabled(); |
195 | $ariaLabelKey = 'cdx-table-pager-button-first-page'; |
196 | $offset = $pager->getFirstOffset(); |
197 | break; |
198 | case PagerBuilder::ACTION_PREVIOUS: |
199 | $disabled = $pager->isPrevDisabled(); |
200 | $ariaLabelKey = 'cdx-table-pager-button-prev-page'; |
201 | $offset = $pager->getPrevOffset(); |
202 | break; |
203 | case PagerBuilder::ACTION_NEXT: |
204 | $disabled = $pager->isNextDisabled(); |
205 | $ariaLabelKey = 'cdx-table-pager-button-next-page'; |
206 | $offset = $pager->getNextOffset(); |
207 | break; |
208 | case PagerBuilder::ACTION_LAST: |
209 | $disabled = $pager->isLastDisabled(); |
210 | $ariaLabelKey = 'cdx-table-pager-button-last-page'; |
211 | $offset = $pager->getLastOffset(); |
212 | $dir = 'prev'; |
213 | break; |
214 | default: |
215 | throw new InvalidArgumentException( "Unknown action: $action" ); |
216 | } |
217 | |
218 | return [ |
219 | 'isDisabled' => $disabled, |
220 | 'weight' => 'quiet', |
221 | 'iconOnly' => true, |
222 | 'ariaLabelKey' => $ariaLabelKey, |
223 | 'iconClass' => $iconClass, |
224 | 'type' => 'submit', |
225 | 'name' => 'offset', |
226 | 'value' => $this->sanitizer->sanitizeText( (string)$offset ), |
227 | 'dir' => $dir, |
228 | ]; |
229 | } |
230 | |
231 | /** |
232 | * Build hidden fields for the pagination form. |
233 | * |
234 | * This method generates the hidden input fields needed for the pagination form, including offset, direction, |
235 | * and other query parameters. |
236 | * |
237 | * @since 0.1.0 |
238 | * @param Pager $pager The Pager object to render. |
239 | * @return array The generated HTML string for the hidden fields. |
240 | */ |
241 | protected function buildHiddenFields( Pager $pager ): array { |
242 | $callbacks = $pager->getCallbacks(); |
243 | if ( !$callbacks ) { |
244 | return []; |
245 | } |
246 | |
247 | $fields = []; |
248 | foreach ( $callbacks->getValues( 'sort', 'asc', 'desc' ) as $key => $value ) { |
249 | $fields[] = [ |
250 | 'key' => $this->sanitizer->sanitizeText( $key ), |
251 | 'value' => $this->sanitizer->sanitizeText( $value ), |
252 | ]; |
253 | } |
254 | |
255 | return $fields; |
256 | } |
257 | } |