Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 34
CRAP
0.00% covered (danger)
0.00%
0 / 1
Table
0.00% covered (danger)
0.00%
0 / 53
0.00% covered (danger)
0.00%
0 / 34
1482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 oppositeSort
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 getUseRowHeaders
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getShowVerticalBorders
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPaginationPosition
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTotalRows
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getFooter
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHeaderContent
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCaption
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getHideCaption
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getColumns
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getData
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentSortColumn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentSortDirection
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getAttributes
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setId
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCaption
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setHideCaption
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setColumns
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setUseRowHeaders
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setShowVerticalBorders
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setSort
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPaginate
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setTotalRows
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setPaginationPosition
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setAttributes
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 setPager
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setFooter
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setHeaderContent
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCurrentSortColumn
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setCurrentSortDirection
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2declare( strict_types = 1 );
3
4/**
5 * Table.php
6 *
7 * This file is part of the Codex design system, the official design system for Wikimedia projects.
8 * It contains the definition and implementation of the `Table` class, responsible for managing
9 * the behavior and properties of the corresponding component.
10 *
11 * @category Component
12 * @package  Codex\Component
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\Component;
20
21use Wikimedia\Codex\Contract\Component;
22use Wikimedia\Codex\Renderer\TableRenderer;
23
24/**
25 * Table
26 *
27 * @category Component
28 * @package  Codex\Component
29 * @since    0.1.0
30 * @author   Doğu Abaris <abaris@null.net>
31 * @license  https://www.gnu.org/copyleft/gpl.html GPL-2.0-or-later
32 * @link     https://doc.wikimedia.org/codex/main/ Codex Documentation
33 */
34class Table extends Component {
35    private string $id = '';
36
37    /**
38     * Sort direction for ascending order.
39     */
40    public const SORT_ASCENDING = 'asc';
41
42    /**
43     * Sort direction for descending order.
44     */
45    public const SORT_DESCENDING = 'desc';
46
47    public function __construct(
48        TableRenderer $renderer,
49        private string $caption,
50        private bool $hideCaption,
51        private array $columns,
52        private array $data,
53        private bool $useRowHeaders,
54        private string|HtmlSnippet|null $headerContent,
55        private array $sort,
56        private ?string $currentSortColumn,
57        private string $currentSortDirection,
58        private bool $showVerticalBorders,
59        private bool $paginate,
60        private int $totalRows,
61        private string $paginationPosition,
62        private ?Pager $pager,
63        private string|HtmlSnippet|null $footer,
64        private array $attributes
65    ) {
66        parent::__construct( $renderer );
67    }
68
69    /**
70     * Get the HTML ID for the table.
71     *
72     * This method returns the HTML `id` attribute value for the table element.
73     *
74     * @since 0.1.0
75     * @return string The ID for the table.
76     */
77    public function getId(): string {
78        return $this->id;
79    }
80
81    /**
82     * Get the opposite sort direction.
83     *
84     * This method returns the opposite of the current sort direction.
85     *
86     * @since 0.1.0
87     * @param string $direction The current sort direction ('asc' or 'desc').
88     * @return string The opposite sort direction.
89     */
90    public function oppositeSort( string $direction ): string {
91        return $direction === self::SORT_ASCENDING ? self::SORT_DESCENDING : self::SORT_ASCENDING;
92    }
93
94    /**
95     * Get whether row headers are used.
96     *
97     * This method returns a boolean value indicating whether the first column of the table is treated as row headers.
98     * Row headers are useful for accessibility and provide additional context for each row.
99     *
100     * @since 0.1.0
101     * @return bool True if row headers are used, false otherwise.
102     */
103    public function getUseRowHeaders(): bool {
104        return $this->useRowHeaders;
105    }
106
107    /**
108     * Check if vertical borders are shown between columns.
109     *
110     * This method returns a boolean value indicating whether vertical borders are displayed between columns in the
111     * table.
112     *
113     * @since 0.1.0
114     * @return bool True if vertical borders are displayed, false otherwise.
115     */
116    public function getShowVerticalBorders(): bool {
117        return $this->showVerticalBorders;
118    }
119
120    /**
121     * Get the Pager instance for the table.
122     *
123     * This method returns the Pager instance if it is set, which provides pagination controls for the table.
124     *
125     * @since 0.1.0
126     * @return Pager|null Returns the Pager instance or null if not set.
127     */
128    public function getPager(): ?Pager {
129        return $this->pager;
130    }
131
132    /**
133     * Get the position of the pagination controls.
134     *
135     * This method returns the position of the pagination controls, which can be 'top', 'bottom', or 'both'.
136     *
137     * @since 0.1.0
138     * @return string The position of the pagination controls ('top', 'bottom', or 'both').
139     */
140    public function getPaginationPosition(): string {
141        return $this->paginationPosition;
142    }
143
144    /**
145     * Get the total number of rows.
146     *
147     * This method returns the total number of rows in the table, which is used for pagination and display purposes.
148     *
149     * @since 0.1.0
150     * @return int The total number of rows in the table.
151     */
152    public function getTotalRows(): int {
153        return $this->totalRows;
154    }
155
156    /**
157     * Get the footer content for the table.
158     *
159     * This method returns the footer content if it is set, which can contain additional information or actions related
160     * to the table.
161     *
162     * @since 0.1.0
163     * @return string|HtmlSnippet|null The footer content or null if not set.
164     */
165    public function getFooter(): string|HtmlSnippet|null {
166        return $this->footer;
167    }
168
169    /**
170     * Get the header content for the table.
171     *
172     * This method returns the custom content for the table's header if it is set, such as actions or additional text.
173     *
174     * @since 0.1.0
175     * @return string|HtmlSnippet|null The header content or null if not set.
176     */
177    public function getHeaderContent(): string|HtmlSnippet|null {
178        return $this->headerContent;
179    }
180
181    /**
182     * Get the caption for the table.
183     *
184     * This method returns the caption text that provides a description of the table's contents and purpose.
185     *
186     * @since 0.1.0
187     * @return string The caption text for the table.
188     */
189    public function getCaption(): string {
190        return $this->caption;
191    }
192
193    /**
194     * Check if the caption is hidden.
195     *
196     * This method returns a boolean value indicating whether the caption is visually hidden but still accessible to
197     * screen readers.
198     *
199     * @since 0.1.0
200     * @return bool True if the caption is hidden, false otherwise.
201     */
202    public function getHideCaption(): bool {
203        return $this->hideCaption;
204    }
205
206    /**
207     * Get the columns for the table.
208     *
209     * This method returns an array of columns defined for the table, where each column is an associative array
210     * containing column attributes.
211     *
212     * @since 0.1.0
213     * @return array The array of columns defined for the table.
214     */
215    public function getColumns(): array {
216        return $this->columns;
217    }
218
219    /**
220     * Get the data for the table.
221     *
222     * This method returns the array of data to be displayed in the table, where each row is an associative array with
223     * keys matching column IDs.
224     *
225     * @since 0.1.0
226     * @return array The array of data for the table.
227     */
228    public function getData(): array {
229        return $this->data;
230    }
231
232    /**
233     * Get the current sort column.
234     *
235     * This method returns the ID of the column currently used for sorting the table data.
236     *
237     * @since 0.1.0
238     * @return ?string The ID of the column used for sorting.
239     */
240    public function getCurrentSortColumn(): ?string {
241        return $this->currentSortColumn;
242    }
243
244    /**
245     * Get the current sort direction.
246     *
247     * This method returns the current sort direction, which can be either 'asc' for ascending or 'desc' for descending.
248     *
249     * @since 0.1.0
250     * @return string The current sort direction ('asc' or 'desc').
251     */
252    public function getCurrentSortDirection(): string {
253        return $this->currentSortDirection;
254    }
255
256    /**
257     * Get additional HTML attributes for the table element.
258     *
259     * This method returns an associative array of custom HTML attributes applied to the `<table>` element.
260     *
261     * @since 0.1.0
262     * @return array The additional attributes as an array.
263     */
264    public function getAttributes(): array {
265        return $this->attributes;
266    }
267
268    /**
269     * Set the Table HTML ID attribute.
270     *
271     * @deprecated Use setAttributes() to set the ID
272     * @since 0.1.0
273     * @param string $id The ID for the Table element.
274     * @return $this
275     */
276    public function setId( string $id ): self {
277        $this->id = $id;
278
279        return $this;
280    }
281
282    /**
283     * Set the caption for the table.
284     *
285     * The caption provides a description of the table's contents and purpose. It is essential for accessibility
286     * as it helps screen readers convey the context of the table to users. To visually hide the caption while
287     * keeping it accessible, use the `setHideCaption()` method.
288     *
289     * Example usage:
290     *
291     *     $table->setCaption('Article List');
292     *
293     * @since 0.1.0
294     * @param string $caption The caption text to be displayed above the table.
295     * @return $this Returns the Table instance for method chaining.
296     */
297    public function setCaption( string $caption ): self {
298        $this->caption = $caption;
299
300        return $this;
301    }
302
303    /**
304     * Set whether to hide the caption.
305     *
306     * If set to true, the caption will be visually hidden but still accessible to screen readers.
307     *
308     * @since 0.1.0
309     * @param bool $hideCaption Indicates if the caption should be visually hidden.
310     * @return $this Returns the Table instance for method chaining.
311     */
312    public function setHideCaption( bool $hideCaption ): self {
313        $this->hideCaption = $hideCaption;
314
315        return $this;
316    }
317
318    /**
319     * Set the columns for the table.
320     *
321     * Each column is defined by an associative array with attributes such as 'id', 'label', 'sortable', etc.
322     * The 'label' can be a string (plain text) or an HtmlSnippet object (raw HTML).
323     *
324     * Example usage:
325     *
326     *     $table->setColumns([
327     *         ['id' => 'title', 'label' => 'Title', 'sortable' => true],
328     *         ['id' => 'creation_date', 'label' => 'Creation Date', 'sortable' => false]
329     *     ]);
330     *
331     * @since 0.1.0
332     * @param array $columns An array of columns, where each column is an associative array containing column
333     *                       attributes.
334     * @return $this Returns the Table instance for method chaining.
335     */
336    public function setColumns( array $columns ): self {
337        $this->columns = $columns;
338
339        return $this;
340    }
341
342    /**
343     * Set the data for the table.
344     *
345     * The data array should correspond to the columns defined. Each row is an associative array where keys match
346     * column IDs. The values can be strings (plain text) or HtmlSnippet objects (raw HTML).
347     *
348     * Example usage:
349     *
350     *     $table->setData([
351     *         ['title' => 'Mercury', 'creation_date' => '2024-01-01'],
352     *         ['title' => 'Venus', 'creation_date' => '2024-01-02'],
353     *     ]);
354     *
355     * @since 0.1.0
356     * @param array $data An array of data to be displayed in the table, where each row is an associative array with
357     *                    keys matching column IDs.
358     * @return $this Returns the Table instance for method chaining.
359     */
360    public function setData( array $data ): self {
361        $this->data = $data;
362
363        return $this;
364    }
365
366    /**
367     * Set whether to use row headers.
368     *
369     * If enabled, the first column of the table will be treated as row headers. This is useful for accessibility
370     * and to provide additional context for each row.
371     *
372     * @since 0.1.0
373     * @param bool $useRowHeaders Indicates if row headers should be used.
374     * @return $this Returns the Table instance for method chaining.
375     */
376    public function setUseRowHeaders( bool $useRowHeaders ): self {
377        $this->useRowHeaders = $useRowHeaders;
378
379        return $this;
380    }
381
382    /**
383     * Set whether to show vertical borders between columns.
384     *
385     * Vertical borders can help distinguish between columns, especially in tables with many columns.
386     *
387     * @since 0.1.0
388     * @param bool $showVerticalBorders Indicates if vertical borders should be displayed between columns.
389     * @return $this Returns the Table instance for method chaining.
390     */
391    public function setShowVerticalBorders( bool $showVerticalBorders ): self {
392        $this->showVerticalBorders = $showVerticalBorders;
393
394        return $this;
395    }
396
397    /**
398     * Set the sort order for the table.
399     *
400     * This method defines the initial sort order for the table. The array should contain
401     * column IDs as keys and sort directions ('asc' or 'desc') as values.
402     *
403     * Example usage:
404     *
405     *     $table->setSort([
406     *         'column1' => 'asc',
407     *         'column2' => 'desc'
408     *     ]);
409     *
410     * @since 0.1.0
411     * @param array $sort An associative array of column IDs and their respective sort directions ('asc' or 'desc').
412     * @return $this Returns the Table instance for method chaining.
413     */
414    public function setSort( array $sort ): self {
415        $this->sort = $sort;
416
417        return $this;
418    }
419
420    /**
421     * Set whether the table should be paginated.
422     *
423     * If enabled, pagination controls will be added to the table, allowing users to navigate through multiple pages of
424     * data.
425     *
426     * @since 0.1.0
427     * @param bool $paginate Indicates if the table should be paginated.
428     * @return $this Returns the Table instance for method chaining.
429     */
430    public function setPaginate( bool $paginate ): self {
431        $this->paginate = $paginate;
432
433        return $this;
434    }
435
436    /**
437     * Set the total number of rows in the table.
438     *
439     * This value is used in conjunction with pagination to calculate the total number of pages and to display the
440     * current range of rows.
441     *
442     * @since 0.1.0
443     * @param int $totalRows The total number of rows in the table.
444     * @return $this Returns the Table instance for method chaining.
445     */
446    public function setTotalRows( int $totalRows ): self {
447        $this->totalRows = $totalRows;
448
449        return $this;
450    }
451
452    /**
453     * Set the position of the pagination controls.
454     *
455     * The pagination controls can be displayed at the top, bottom, or both top and bottom of the table.
456     *
457     * @since 0.1.0
458     * @param string $paginationPosition The position of the pagination controls ('top', 'bottom', 'both').
459     * @return $this Returns the Table instance for method chaining.
460     */
461    public function setPaginationPosition( string $paginationPosition ): self {
462        $this->paginationPosition = $paginationPosition;
463
464        return $this;
465    }
466
467    /**
468     * Set additional HTML attributes for the table element.
469     *
470     * This method allows custom HTML attributes to be added to the `<table>` element, such as `id`, `class`,
471     * or `data-*` attributes. These attributes are automatically escaped to prevent XSS vulnerabilities.
472     *
473     * Example usage:
474     *
475     *     $table->setAttributes(['class' => 'custom-table-class', 'data-info' => 'additional-info']);
476     *
477     * @since 0.1.0
478     * @param array $attributes An associative array of HTML attributes to be added to the `<table>` element.
479     *
480     * @return $this Returns the Table instance for method chaining.
481     */
482    public function setAttributes( array $attributes ): self {
483        foreach ( $attributes as $key => $value ) {
484            $this->attributes[$key] = $value;
485        }
486        return $this;
487    }
488
489    /**
490     * Set the Pager instance for the table.
491     *
492     * The Pager instance provides pagination controls for the table. If set, pagination controls will be rendered
493     * according to the settings.
494     *
495     * @since 0.1.0
496     * @param Pager $pager The Pager instance.
497     * @return $this Returns the Table instance for method chaining.
498     */
499    public function setPager( Pager $pager ): self {
500        $this->pager = $pager;
501
502        return $this;
503    }
504
505    /**
506     * Set the footer content for the table.
507     *
508     * The footer is an optional section that can contain additional information or actions related to the table.
509     *
510     * @since 0.1.0
511     * @param string|HtmlSnippet $footer The footer content.
512     * @return $this Returns the Table instance for method chaining.
513     */
514    public function setFooter( string|HtmlSnippet $footer ): self {
515        $this->footer = $footer;
516
517        return $this;
518    }
519
520    /**
521     * Set the header content for the table.
522     *
523     * This method allows custom content to be added to the table's header, such as actions or additional text.
524     *
525     * Example usage:
526     *
527     *     $table->setHeaderContent('Custom Actions');
528     *
529     * @since 0.1.0
530     * @param string|HtmlSnippet $headerContent The content to be displayed in the table header.
531     * @return $this Returns the Table instance for method chaining.
532     */
533    public function setHeaderContent( string|HtmlSnippet $headerContent ): self {
534        $this->headerContent = $headerContent;
535
536        return $this;
537    }
538
539    /**
540     * Set the current sort column.
541     *
542     * This method specifies which column is currently being used for sorting the table data.
543     * The column with this ID will be marked as sorted in the table header.
544     *
545     * Example usage:
546     *
547     *     $table->setCurrentSortColumn('title');
548     *
549     * @since 0.1.0
550     * @param string $currentSortColumn The ID of the column used for sorting.
551     *
552     * @return $this Returns the Table instance for method chaining.
553     */
554    public function setCurrentSortColumn( string $currentSortColumn ): self {
555        $this->currentSortColumn = $currentSortColumn;
556
557        return $this;
558    }
559
560    /**
561     * Set the current sort direction.
562     *
563     * This method specifies the direction for sorting the table data. Acceptable values are 'asc' for ascending
564     * and 'desc' for descending. The method validates these values to ensure they are correct.
565     *
566     * Example usage:
567     *
568     *     $table->setCurrentSortDirection('asc');
569     *
570     * @since 0.1.0
571     * @param string $currentSortDirection The sort direction ('asc' or 'desc').
572     *
573     * @return $this Returns the Table instance for method chaining.
574     */
575    public function setCurrentSortDirection( string $currentSortDirection ): self {
576        if ( $currentSortDirection === self::SORT_ASCENDING || $currentSortDirection === self::SORT_DESCENDING ) {
577            $this->currentSortDirection = $currentSortDirection;
578        }
579
580        return $this;
581    }
582}