Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
PagerRenderer
0.00% covered (danger)
0.00%
0 / 94
0.00% covered (danger)
0.00%
0 / 5
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 render
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
6
 buildSelect
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
6
 buildButtonData
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
42
 buildHiddenFields
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
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
19namespace Wikimedia\Codex\Renderer;
20
21use InvalidArgumentException;
22use Wikimedia\Codex\Builder\PagerBuilder;
23use Wikimedia\Codex\Component\Pager;
24use Wikimedia\Codex\Contract\ILocalizer;
25use Wikimedia\Codex\Contract\Renderer\IRenderer;
26use Wikimedia\Codex\Contract\Renderer\ITemplateRenderer;
27use Wikimedia\Codex\Traits\AttributeResolver;
28use Wikimedia\Codex\Utility\Codex;
29use 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 */
46class 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}