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