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