Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
CheckMatrixWidget
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 8
342
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
12
 getTableRow
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
30
 getCellTag
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 isTagChecked
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 isTagDisabled
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 getTooltip
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getJavaScriptClassName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getConfig
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Widget;
4
5use OOUI\CheckboxInputWidget;
6use OOUI\FieldLayout;
7use OOUI\HtmlSnippet;
8use OOUI\Tag;
9use OOUI\Widget;
10
11/**
12 * Check matrix widget. Displays a matrix of checkboxes for given options
13 *
14 * @copyright 2018 MediaWiki Widgets Team and others; see AUTHORS.txt
15 * @license MIT
16 */
17class CheckMatrixWidget extends Widget {
18    /** @var string|null */
19    protected $name;
20    /** @var string|null */
21    protected $id;
22    /** @var array */
23    protected $columns;
24    /** @var array */
25    protected $rows;
26    /** @var array */
27    protected $tooltips;
28    /** @var array */
29    protected $tooltipsHtml;
30    /** @var array */
31    protected $values;
32    /** @var array */
33    protected $forcedOn;
34    /** @var array */
35    protected $forcedOff;
36
37    /**
38     * Operates similarly to MultiSelectWidget, but instead of using an array of
39     * options, uses an array of rows and an array of columns to dynamically
40     * construct a matrix of options. The tags used to identify a particular cell
41     * are of the form "columnName-rowName"
42     *
43     * @param array $config Configuration array with the following options:
44     *   - columns
45     *     - Required associative array mapping column labels (as HTML) to their tags.
46     *   - rows
47     *     - Required associative array mapping row labels (as HTML) to their tags.
48     *   - force-options-on
49     *     - Array of column-row tags to be displayed as enabled but unavailable to change.
50     *   - force-options-off
51     *     - Array of column-row tags to be displayed as disabled but unavailable to change.
52     *   - tooltips
53     *     - Optional associative array mapping row labels to tooltips (as text, will be escaped).
54     *   - tooltips-html
55     *     - Optional associative array mapping row labels to tooltips (as HTML). Takes precedence
56     *       over text tooltips.
57     */
58    public function __construct( array $config = [] ) {
59        // Configuration initialization
60
61        parent::__construct( $config );
62
63        $this->name = $config['name'] ?? null;
64        $this->id = $config['id'] ?? null;
65
66        // Properties
67        $this->rows = $config['rows'] ?? [];
68        $this->columns = $config['columns'] ?? [];
69        $this->tooltips = $config['tooltips'] ?? [];
70        $this->tooltipsHtml = $config['tooltips-html'] ?? [];
71
72        $this->values = $config['values'] ?? [];
73
74        $this->forcedOn = $config['forcedOn'] ?? [];
75        $this->forcedOff = $config['forcedOff'] ?? [];
76
77        // Build the table
78        $table = new Tag( 'table' );
79        $table->addClasses( [ 'mw-htmlform-matrix mw-widget-checkMatrixWidget-matrix' ] );
80        $thead = new Tag( 'thead' );
81        $table->appendContent( $thead );
82        $tr = new Tag( 'tr' );
83
84        // Build the header
85        $tr->appendContent( $this->getCellTag( "\u{00A0}" ) );
86        foreach ( $this->columns as $columnLabel => $columnTag ) {
87            $tr->appendContent(
88                $this->getCellTag( new HtmlSnippet( $columnLabel ), 'th' )
89            );
90        }
91        $thead->appendContent( $tr );
92
93        // Build the options matrix
94        $tbody = new Tag( 'tbody' );
95        $table->appendContent( $tbody );
96        foreach ( $this->rows as $rowLabel => $rowTag ) {
97            $tbody->appendContent(
98                $this->getTableRow( $rowLabel, $rowTag )
99            );
100        }
101
102        // Initialization
103        $this->addClasses( [ 'mw-widget-checkMatrixWidget' ] );
104        $this->appendContent( $table );
105    }
106
107    /**
108     * Get a formatted table row for the option, with
109     * a checkbox widget.
110     *
111     * @param string $label Row label (as HTML)
112     * @param string $tag Row tag name
113     *
114     * @return Tag The resulting table row
115     */
116    private function getTableRow( $label, $tag ) {
117        $row = new Tag( 'tr' );
118        $tooltip = $this->getTooltip( $label );
119        $labelFieldConfig = $tooltip ? [ 'help' => $tooltip ] : [];
120        // Build label cell
121        $labelField = new FieldLayout(
122            new Widget(), // Empty widget, since we don't have the checkboxes here
123            [
124                'label' => new HtmlSnippet( $label ),
125                'align' => 'inline',
126            ] + $labelFieldConfig
127        );
128        $row->appendContent( $this->getCellTag( $labelField ) );
129
130        // Build checkbox column cells
131        foreach ( $this->columns as $columnTag ) {
132            $thisTag = "$columnTag-$tag";
133
134            // Construct a checkbox
135            $checkbox = new CheckboxInputWidget( [
136                'value' => $thisTag,
137                'name' => $this->name ? "{$this->name}[]" : null,
138                'id' => $this->id ? "{$this->id}-$thisTag" : null,
139                'selected' => $this->isTagChecked( $thisTag ),
140                'disabled' => $this->isTagDisabled( $thisTag ),
141            ] );
142
143            $row->appendContent( $this->getCellTag( $checkbox ) );
144        }
145        return $row;
146    }
147
148    /**
149     * Get an individual cell tag with requested content
150     *
151     * @param mixed $content Content for the <td> cell
152     * @param string $tagElement
153     * @return Tag Resulting cell
154     */
155    private function getCellTag( $content, $tagElement = 'td' ) {
156        $cell = new Tag( $tagElement );
157        $cell->appendContent( $content );
158        return $cell;
159    }
160
161    /**
162     * Check whether the given tag's checkbox should
163     * be checked
164     *
165     * @param string $tagName
166     * @return bool Tag should be checked
167     */
168    private function isTagChecked( $tagName ) {
169        // If the tag is in the value list
170        return in_array( $tagName, (array)$this->values, true ) ||
171            // Or if the tag is forced on
172            in_array( $tagName, (array)$this->forcedOn, true );
173    }
174
175    /**
176     * Check whether the given tag's checkbox should
177     * be disabled
178     *
179     * @param string $tagName
180     * @return bool Tag should be disabled
181     */
182    private function isTagDisabled( $tagName ) {
183        return (
184            // If the entire widget is disabled
185            $this->isDisabled() ||
186            // If the tag is 'forced on' or 'forced off'
187            in_array( $tagName, (array)$this->forcedOn, true ) ||
188            in_array( $tagName, (array)$this->forcedOff, true )
189        );
190    }
191
192    /**
193     * Get the tooltip help associated with this row
194     *
195     * @param string $label Label name
196     *
197     * @return string Tooltip. Null if none is available.
198     */
199    private function getTooltip( $label ) {
200        if ( isset( $this->tooltipsHtml[ $label ] ) ) {
201            return new HtmlSnippet( $this->tooltipsHtml[ $label ] );
202        } else {
203            return $this->tooltips[ $label ] ?? null;
204        }
205    }
206
207    protected function getJavaScriptClassName() {
208        return 'mw.widgets.CheckMatrixWidget';
209    }
210
211    public function getConfig( &$config ) {
212        $config += [
213            'name' => $this->name,
214            'id' => $this->id,
215            'rows' => $this->rows,
216            'columns' => $this->columns,
217            'tooltips' => $this->tooltips,
218            'tooltipsHtml' => $this->tooltipsHtml,
219            'forcedOff' => $this->forcedOff,
220            'forcedOn' => $this->forcedOn,
221            'values' => $this->values,
222        ];
223        return parent::getConfig( $config );
224    }
225}