These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | /** |
||
4 | * A checkbox matrix |
||
5 | * Operates similarly to HTMLMultiSelectField, but instead of using an array of |
||
6 | * options, uses an array of rows and an array of columns to dynamically |
||
7 | * construct a matrix of options. The tags used to identify a particular cell |
||
8 | * are of the form "columnName-rowName" |
||
9 | * |
||
10 | * Options: |
||
11 | * - columns |
||
12 | * - Required list of columns in the matrix. |
||
13 | * - rows |
||
14 | * - Required list of rows in the matrix. |
||
15 | * - force-options-on |
||
16 | * - Accepts array of column-row tags to be displayed as enabled but unavailable to change |
||
17 | * - force-options-off |
||
18 | * - Accepts array of column-row tags to be displayed as disabled but unavailable to change. |
||
19 | * - tooltips |
||
20 | * - Optional array mapping row label to tooltip content |
||
21 | * - tooltip-class |
||
22 | * - Optional CSS class used on tooltip container span. Defaults to mw-icon-question. |
||
23 | */ |
||
24 | class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable { |
||
25 | static private $requiredParams = [ |
||
26 | // Required by underlying HTMLFormField |
||
27 | 'fieldname', |
||
28 | // Required by HTMLCheckMatrix |
||
29 | 'rows', |
||
30 | 'columns' |
||
31 | ]; |
||
32 | |||
33 | public function __construct( $params ) { |
||
34 | $missing = array_diff( self::$requiredParams, array_keys( $params ) ); |
||
35 | if ( $missing ) { |
||
36 | throw new HTMLFormFieldRequiredOptionsException( $this, $missing ); |
||
37 | } |
||
38 | parent::__construct( $params ); |
||
39 | } |
||
40 | |||
41 | function validate( $value, $alldata ) { |
||
42 | $rows = $this->mParams['rows']; |
||
43 | $columns = $this->mParams['columns']; |
||
44 | |||
45 | // Make sure user-defined validation callback is run |
||
46 | $p = parent::validate( $value, $alldata ); |
||
47 | if ( $p !== true ) { |
||
48 | return $p; |
||
49 | } |
||
50 | |||
51 | // Make sure submitted value is an array |
||
52 | if ( !is_array( $value ) ) { |
||
53 | return false; |
||
54 | } |
||
55 | |||
56 | // If all options are valid, array_intersect of the valid options |
||
57 | // and the provided options will return the provided options. |
||
58 | $validOptions = []; |
||
59 | foreach ( $rows as $rowTag ) { |
||
60 | foreach ( $columns as $columnTag ) { |
||
61 | $validOptions[] = $columnTag . '-' . $rowTag; |
||
62 | } |
||
63 | } |
||
64 | $validValues = array_intersect( $value, $validOptions ); |
||
65 | View Code Duplication | if ( count( $validValues ) == count( $value ) ) { |
|
66 | return true; |
||
67 | } else { |
||
68 | return $this->msg( 'htmlform-select-badoption' )->parse(); |
||
69 | } |
||
70 | } |
||
71 | |||
72 | /** |
||
73 | * Build a table containing a matrix of checkbox options. |
||
74 | * The value of each option is a combination of the row tag and column tag. |
||
75 | * mParams['rows'] is an array with row labels as keys and row tags as values. |
||
76 | * mParams['columns'] is an array with column labels as keys and column tags as values. |
||
77 | * |
||
78 | * @param array $value Array of the options that should be checked |
||
79 | * |
||
80 | * @return string |
||
81 | */ |
||
82 | function getInputHTML( $value ) { |
||
83 | $html = ''; |
||
84 | $tableContents = ''; |
||
85 | $rows = $this->mParams['rows']; |
||
86 | $columns = $this->mParams['columns']; |
||
87 | |||
88 | $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] ); |
||
89 | |||
90 | // Build the column headers |
||
91 | $headerContents = Html::rawElement( 'td', [], ' ' ); |
||
92 | foreach ( $columns as $columnLabel => $columnTag ) { |
||
93 | $headerContents .= Html::rawElement( 'td', [], $columnLabel ); |
||
94 | } |
||
95 | $tableContents .= Html::rawElement( 'tr', [], "\n$headerContents\n" ); |
||
96 | |||
97 | $tooltipClass = 'mw-icon-question'; |
||
98 | if ( isset( $this->mParams['tooltip-class'] ) ) { |
||
99 | $tooltipClass = $this->mParams['tooltip-class']; |
||
100 | } |
||
101 | |||
102 | // Build the options matrix |
||
103 | foreach ( $rows as $rowLabel => $rowTag ) { |
||
104 | // Append tooltip if configured |
||
105 | if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) { |
||
106 | $tooltipAttribs = [ |
||
107 | 'class' => "mw-htmlform-tooltip $tooltipClass", |
||
108 | 'title' => $this->mParams['tooltips'][$rowLabel], |
||
109 | ]; |
||
110 | $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' ); |
||
111 | } |
||
112 | $rowContents = Html::rawElement( 'td', [], $rowLabel ); |
||
113 | foreach ( $columns as $columnTag ) { |
||
114 | $thisTag = "$columnTag-$rowTag"; |
||
115 | // Construct the checkbox |
||
116 | $thisAttribs = [ |
||
117 | 'id' => "{$this->mID}-$thisTag", |
||
118 | 'value' => $thisTag, |
||
119 | ]; |
||
120 | $checked = in_array( $thisTag, (array)$value, true ); |
||
121 | if ( $this->isTagForcedOff( $thisTag ) ) { |
||
122 | $checked = false; |
||
123 | $thisAttribs['disabled'] = 1; |
||
124 | } elseif ( $this->isTagForcedOn( $thisTag ) ) { |
||
125 | $checked = true; |
||
126 | $thisAttribs['disabled'] = 1; |
||
127 | } |
||
128 | |||
129 | $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs ); |
||
130 | |||
131 | $rowContents .= Html::rawElement( |
||
132 | 'td', |
||
133 | [], |
||
134 | $checkbox |
||
0 ignored issues
–
show
|
|||
135 | ); |
||
136 | } |
||
137 | $tableContents .= Html::rawElement( 'tr', [], "\n$rowContents\n" ); |
||
138 | } |
||
139 | |||
140 | // Put it all in a table |
||
141 | $html .= Html::rawElement( 'table', |
||
142 | [ 'class' => 'mw-htmlform-matrix' ], |
||
143 | Html::rawElement( 'tbody', [], "\n$tableContents\n" ) ) . "\n"; |
||
144 | |||
145 | return $html; |
||
146 | } |
||
147 | |||
148 | protected function getOneCheckbox( $checked, $attribs ) { |
||
149 | if ( $this->mParent instanceof OOUIHTMLForm ) { |
||
150 | return new OOUI\CheckboxInputWidget( [ |
||
151 | 'name' => "{$this->mName}[]", |
||
152 | 'selected' => $checked, |
||
153 | ] + OOUI\Element::configFromHtmlAttributes( |
||
154 | $attribs |
||
155 | ) ); |
||
156 | } else { |
||
157 | $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs ); |
||
158 | if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) { |
||
159 | $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) . |
||
160 | $checkbox . |
||
161 | Html::element( 'label', [ 'for' => $attribs['id'] ] ) . |
||
162 | Html::closeElement( 'div' ); |
||
163 | } |
||
164 | return $checkbox; |
||
165 | } |
||
166 | } |
||
167 | |||
168 | protected function isTagForcedOff( $tag ) { |
||
169 | return isset( $this->mParams['force-options-off'] ) |
||
170 | && in_array( $tag, $this->mParams['force-options-off'] ); |
||
171 | } |
||
172 | |||
173 | protected function isTagForcedOn( $tag ) { |
||
174 | return isset( $this->mParams['force-options-on'] ) |
||
175 | && in_array( $tag, $this->mParams['force-options-on'] ); |
||
176 | } |
||
177 | |||
178 | /** |
||
179 | * Get the complete table row for the input, including help text, |
||
180 | * labels, and whatever. |
||
181 | * We override this function since the label should always be on a separate |
||
182 | * line above the options in the case of a checkbox matrix, i.e. it's always |
||
183 | * a "vertical-label". |
||
184 | * |
||
185 | * @param string $value The value to set the input to |
||
186 | * |
||
187 | * @return string Complete HTML table row |
||
188 | */ |
||
189 | function getTableRow( $value ) { |
||
190 | list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); |
||
191 | $inputHtml = $this->getInputHTML( $value ); |
||
192 | $fieldType = get_class( $this ); |
||
193 | $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() ); |
||
194 | $cellAttributes = [ 'colspan' => 2 ]; |
||
195 | |||
196 | $hideClass = ''; |
||
197 | $hideAttributes = []; |
||
198 | if ( $this->mHideIf ) { |
||
199 | $hideAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf ); |
||
200 | $hideClass = 'mw-htmlform-hide-if'; |
||
201 | } |
||
202 | |||
203 | $label = $this->getLabelHtml( $cellAttributes ); |
||
204 | |||
205 | $field = Html::rawElement( |
||
206 | 'td', |
||
207 | [ 'class' => 'mw-input' ] + $cellAttributes, |
||
208 | $inputHtml . "\n$errors" |
||
209 | ); |
||
210 | |||
211 | $html = Html::rawElement( 'tr', |
||
212 | [ 'class' => "mw-htmlform-vertical-label $hideClass" ] + $hideAttributes, |
||
213 | $label ); |
||
214 | $html .= Html::rawElement( 'tr', |
||
215 | [ 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $hideClass" ] + |
||
216 | $hideAttributes, |
||
217 | $field ); |
||
218 | |||
219 | return $html . $helptext; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * @param WebRequest $request |
||
224 | * |
||
225 | * @return array |
||
226 | */ |
||
227 | function loadDataFromRequest( $request ) { |
||
228 | if ( $this->isSubmitAttempt( $request ) ) { |
||
229 | // Checkboxes are just not added to the request arrays if they're not checked, |
||
230 | // so it's perfectly possible for there not to be an entry at all |
||
231 | return $request->getArray( $this->mName, [] ); |
||
232 | } else { |
||
233 | // That's ok, the user has not yet submitted the form, so show the defaults |
||
234 | return $this->getDefault(); |
||
235 | } |
||
236 | } |
||
237 | |||
238 | function getDefault() { |
||
239 | if ( isset( $this->mDefault ) ) { |
||
240 | return $this->mDefault; |
||
241 | } else { |
||
242 | return []; |
||
243 | } |
||
244 | } |
||
245 | |||
246 | function filterDataForSubmit( $data ) { |
||
247 | $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] ); |
||
248 | $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] ); |
||
249 | $res = []; |
||
250 | foreach ( $columns as $column ) { |
||
251 | foreach ( $rows as $row ) { |
||
252 | // Make sure option hasn't been forced |
||
253 | $thisTag = "$column-$row"; |
||
254 | if ( $this->isTagForcedOff( $thisTag ) ) { |
||
255 | $res[$thisTag] = false; |
||
256 | } elseif ( $this->isTagForcedOn( $thisTag ) ) { |
||
257 | $res[$thisTag] = true; |
||
258 | } else { |
||
259 | $res[$thisTag] = in_array( $thisTag, $data ); |
||
260 | } |
||
261 | } |
||
262 | } |
||
263 | |||
264 | return $res; |
||
265 | } |
||
266 | } |
||
267 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.