Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
33.64% |
36 / 107 |
|
41.67% |
5 / 12 |
CRAP | |
0.00% |
0 / 1 |
HTMLCheckMatrix | |
33.96% |
36 / 106 |
|
41.67% |
5 / 12 |
326.90 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
validate | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
6 | |||
getInputHTML | |
0.00% |
0 / 48 |
|
0.00% |
0 / 1 |
72 | |||
getInputOOUI | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
getOneCheckboxHTML | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isTagForcedOff | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
isTagForcedOn | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
loadDataFromRequest | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getDefault | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
filterDataForSubmit | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
5 | |||
getOOUIModules | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
shouldInfuseOOUI | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\HTMLForm\Field; |
4 | |
5 | use MediaWiki\Html\Html; |
6 | use MediaWiki\HTMLForm\HTMLFormField; |
7 | use MediaWiki\HTMLForm\HTMLFormFieldRequiredOptionsException; |
8 | use MediaWiki\HTMLForm\HTMLNestedFilterable; |
9 | use MediaWiki\Request\WebRequest; |
10 | use MediaWiki\Xml\Xml; |
11 | |
12 | /** |
13 | * A checkbox matrix |
14 | * Operates similarly to HTMLMultiSelectField, but instead of using an array of |
15 | * options, uses an array of rows and an array of columns to dynamically |
16 | * construct a matrix of options. The tags used to identify a particular cell |
17 | * are of the form "columnName-rowName" |
18 | * |
19 | * Options: |
20 | * - columns |
21 | * - Required associative array mapping column labels (as HTML) to their tags. |
22 | * - rows |
23 | * - Required associative array mapping row labels (as HTML) to their tags. |
24 | * - force-options-on |
25 | * - Array of column-row tags to be displayed as enabled but unavailable to change. |
26 | * - force-options-off |
27 | * - Array of column-row tags to be displayed as disabled but unavailable to change. |
28 | * - tooltips |
29 | * - Optional associative array mapping row labels to tooltips (as text, will be escaped). |
30 | * - tooltips-html |
31 | * - Optional associative array mapping row labels to tooltips (as HTML). |
32 | * Only used by OOUI form fields. Takes precedence when supported, so to support both |
33 | * OOUI and non-OOUI forms, you can set both. |
34 | * - tooltip-class |
35 | * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question. |
36 | * Not used by OOUI form fields. |
37 | * |
38 | * @stable to extend |
39 | */ |
40 | class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { |
41 | private const REQUIRED_PARAMS = [ |
42 | // Required by underlying HTMLFormField |
43 | 'fieldname', |
44 | // Required by HTMLCheckMatrix |
45 | 'rows', |
46 | 'columns' |
47 | ]; |
48 | |
49 | /** |
50 | * @stable to call |
51 | * @inheritDoc |
52 | */ |
53 | public function __construct( $params ) { |
54 | $missing = array_diff( self::REQUIRED_PARAMS, array_keys( $params ) ); |
55 | if ( $missing ) { |
56 | throw new HTMLFormFieldRequiredOptionsException( $this, $missing ); |
57 | } |
58 | |
59 | // The label should always be on a separate line above the options |
60 | $params['vertical-label'] = true; |
61 | parent::__construct( $params ); |
62 | } |
63 | |
64 | public function validate( $value, $alldata ) { |
65 | $rows = $this->mParams['rows']; |
66 | $columns = $this->mParams['columns']; |
67 | |
68 | // Make sure user-defined validation callback is run |
69 | $p = parent::validate( $value, $alldata ); |
70 | if ( $p !== true ) { |
71 | return $p; |
72 | } |
73 | |
74 | // Make sure submitted value is an array |
75 | if ( !is_array( $value ) ) { |
76 | return false; |
77 | } |
78 | |
79 | // If all options are valid, array_intersect of the valid options |
80 | // and the provided options will return the provided options. |
81 | $validOptions = []; |
82 | foreach ( $rows as $rowTag ) { |
83 | foreach ( $columns as $columnTag ) { |
84 | $validOptions[] = $columnTag . '-' . $rowTag; |
85 | } |
86 | } |
87 | $validValues = array_intersect( $value, $validOptions ); |
88 | if ( count( $validValues ) == count( $value ) ) { |
89 | return true; |
90 | } else { |
91 | return $this->msg( 'htmlform-select-badoption' ); |
92 | } |
93 | } |
94 | |
95 | /** |
96 | * Build a table containing a matrix of checkbox options. |
97 | * The value of each option is a combination of the row tag and column tag. |
98 | * mParams['rows'] is an array with row labels as keys and row tags as values. |
99 | * mParams['columns'] is an array with column labels as keys and column tags as values. |
100 | * |
101 | * @param array $value Array of the options that should be checked |
102 | * |
103 | * @return string |
104 | */ |
105 | public function getInputHTML( $value ) { |
106 | $html = ''; |
107 | $tableContents = ''; |
108 | $rows = $this->mParams['rows']; |
109 | $columns = $this->mParams['columns']; |
110 | |
111 | $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] ); |
112 | |
113 | // Build the column headers |
114 | $headerContents = Html::rawElement( 'td', [], "\u{00A0}" ); |
115 | foreach ( $columns as $columnLabel => $columnTag ) { |
116 | $headerContents .= Html::rawElement( 'th', [], $columnLabel ); |
117 | } |
118 | $thead = Html::rawElement( 'tr', [], "\n$headerContents\n" ); |
119 | $tableContents .= Html::rawElement( 'thead', [], "\n$thead\n" ); |
120 | |
121 | $tooltipClass = 'mw-icon-question'; |
122 | if ( isset( $this->mParams['tooltip-class'] ) ) { |
123 | $tooltipClass = $this->mParams['tooltip-class']; |
124 | } |
125 | |
126 | // Build the options matrix |
127 | foreach ( $rows as $rowLabel => $rowTag ) { |
128 | // Append tooltip if configured |
129 | if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) { |
130 | $tooltipAttribs = [ |
131 | 'class' => "mw-htmlform-tooltip $tooltipClass", |
132 | 'title' => $this->mParams['tooltips'][$rowLabel], |
133 | 'aria-label' => $this->mParams['tooltips'][$rowLabel] |
134 | ]; |
135 | $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' ); |
136 | } |
137 | $rowContents = Html::rawElement( 'td', [], $rowLabel ); |
138 | foreach ( $columns as $columnTag ) { |
139 | $thisTag = "$columnTag-$rowTag"; |
140 | // Construct the checkbox |
141 | $thisAttribs = [ |
142 | 'id' => "{$this->mID}-$thisTag", |
143 | 'value' => $thisTag, |
144 | ]; |
145 | $checked = in_array( $thisTag, (array)$value, true ); |
146 | if ( $this->isTagForcedOff( $thisTag ) ) { |
147 | $checked = false; |
148 | $thisAttribs['disabled'] = 1; |
149 | $thisAttribs['class'] = 'checkmatrix-forced checkmatrix-forced-off'; |
150 | } elseif ( $this->isTagForcedOn( $thisTag ) ) { |
151 | $checked = true; |
152 | $thisAttribs['disabled'] = 1; |
153 | $thisAttribs['class'] = 'checkmatrix-forced checkmatrix-forced-on'; |
154 | } |
155 | |
156 | $checkbox = $this->getOneCheckboxHTML( $checked, $attribs + $thisAttribs ); |
157 | |
158 | $rowContents .= Html::rawElement( |
159 | 'td', |
160 | [], |
161 | $checkbox |
162 | ); |
163 | } |
164 | $tableContents .= Html::rawElement( 'tr', [], "\n$rowContents\n" ); |
165 | } |
166 | |
167 | // Put it all in a table |
168 | $html .= Html::rawElement( 'table', |
169 | [ 'class' => 'mw-htmlform-matrix' ], |
170 | Html::rawElement( 'tbody', [], "\n$tableContents\n" ) ) . "\n"; |
171 | |
172 | return $html; |
173 | } |
174 | |
175 | public function getInputOOUI( $value ) { |
176 | $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] ); |
177 | |
178 | return new \MediaWiki\Widget\CheckMatrixWidget( |
179 | [ |
180 | 'name' => $this->mName, |
181 | 'infusable' => true, |
182 | 'id' => $this->mID, |
183 | 'rows' => $this->mParams['rows'], |
184 | 'columns' => $this->mParams['columns'], |
185 | 'tooltips' => $this->mParams['tooltips'] ?? [], |
186 | 'tooltips-html' => $this->mParams['tooltips-html'] ?? [], |
187 | 'forcedOff' => $this->mParams['force-options-off'] ?? [], |
188 | 'forcedOn' => $this->mParams['force-options-on'] ?? [], |
189 | 'values' => $value, |
190 | ] + \OOUI\Element::configFromHtmlAttributes( $attribs ) |
191 | ); |
192 | } |
193 | |
194 | protected function getOneCheckboxHTML( $checked, $attribs ) { |
195 | return Xml::check( "{$this->mName}[]", $checked, $attribs ); |
196 | } |
197 | |
198 | protected function isTagForcedOff( $tag ) { |
199 | return isset( $this->mParams['force-options-off'] ) |
200 | && in_array( $tag, $this->mParams['force-options-off'] ); |
201 | } |
202 | |
203 | protected function isTagForcedOn( $tag ) { |
204 | return isset( $this->mParams['force-options-on'] ) |
205 | && in_array( $tag, $this->mParams['force-options-on'] ); |
206 | } |
207 | |
208 | /** |
209 | * @param WebRequest $request |
210 | * |
211 | * @return array |
212 | */ |
213 | public function loadDataFromRequest( $request ) { |
214 | if ( $this->isSubmitAttempt( $request ) ) { |
215 | // Checkboxes are just not added to the request arrays if they're not checked, |
216 | // so it's perfectly possible for there not to be an entry at all |
217 | return $request->getArray( $this->mName, [] ); |
218 | } else { |
219 | // That's ok, the user has not yet submitted the form, so show the defaults |
220 | return $this->getDefault(); |
221 | } |
222 | } |
223 | |
224 | public function getDefault() { |
225 | return $this->mDefault ?? []; |
226 | } |
227 | |
228 | public function filterDataForSubmit( $data ) { |
229 | $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] ); |
230 | $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] ); |
231 | $res = []; |
232 | foreach ( $columns as $column ) { |
233 | foreach ( $rows as $row ) { |
234 | // Make sure option hasn't been forced |
235 | $thisTag = "$column-$row"; |
236 | if ( $this->isTagForcedOff( $thisTag ) ) { |
237 | $res[$thisTag] = false; |
238 | } elseif ( $this->isTagForcedOn( $thisTag ) ) { |
239 | $res[$thisTag] = true; |
240 | } else { |
241 | $res[$thisTag] = in_array( $thisTag, $data ); |
242 | } |
243 | } |
244 | } |
245 | |
246 | return $res; |
247 | } |
248 | |
249 | protected function getOOUIModules() { |
250 | return [ 'mediawiki.widgets.CheckMatrixWidget' ]; |
251 | } |
252 | |
253 | protected function shouldInfuseOOUI() { |
254 | return true; |
255 | } |
256 | } |
257 | |
258 | /** @deprecated class alias since 1.42 */ |
259 | class_alias( HTMLCheckMatrix::class, 'HTMLCheckMatrix' ); |