Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
47.15% |
58 / 123 |
|
12.50% |
1 / 8 |
CRAP | |
0.00% |
0 / 1 |
HTMLRadioField | |
47.54% |
58 / 122 |
|
12.50% |
1 / 8 |
179.83 | |
0.00% |
0 / 1 |
__construct | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
validate | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
getInputHTML | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getInputOOUI | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
42 | |||
getInputCodex | |
100.00% |
53 / 53 |
|
100.00% |
1 / 1 |
6 | |||
formatOptions | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
getOptionDescriptions | |
33.33% |
3 / 9 |
|
0.00% |
0 / 1 |
12.41 | |||
needsLabel | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\HTMLForm\Field; |
4 | |
5 | use InvalidArgumentException; |
6 | use MediaWiki\Html\Html; |
7 | use MediaWiki\HTMLForm\HTMLFormField; |
8 | use MediaWiki\Parser\Sanitizer; |
9 | use MediaWiki\Xml\Xml; |
10 | |
11 | /** |
12 | * Radio checkbox fields. |
13 | * |
14 | * @stable to extend |
15 | */ |
16 | class HTMLRadioField extends HTMLFormField { |
17 | /** |
18 | * @stable to call |
19 | * @param array $params |
20 | * In addition to the usual HTMLFormField parameters, this can take the following fields: |
21 | * - 'flatlist': If given, the options will be displayed on a single line (wrapping to following |
22 | * lines if necessary), rather than each one on a line of its own. This is desirable mostly |
23 | * for very short lists of concisely labelled options. |
24 | * - 'option-descriptions': Associative array mapping values to raw HTML descriptions. These |
25 | * descriptions are displayed below their respective options. Note that the key-value |
26 | * relationship is reversed compared to 'options' and friends. Only supported by the Codex |
27 | * display format; if this is set when another display format is used, an exception is thrown. |
28 | * - 'option-descriptions-messages': Associative array mapping values to message keys. |
29 | * Overwrites 'option-descriptions'. |
30 | * - 'option-descriptions-messages-parse': If true, parse the messages in |
31 | * 'option-descriptions-messages'. |
32 | */ |
33 | public function __construct( $params ) { |
34 | parent::__construct( $params ); |
35 | |
36 | if ( isset( $params['flatlist'] ) ) { |
37 | $this->mClass .= ' mw-htmlform-flatlist'; |
38 | } |
39 | } |
40 | |
41 | public function validate( $value, $alldata ) { |
42 | $p = parent::validate( $value, $alldata ); |
43 | |
44 | if ( $p !== true ) { |
45 | return $p; |
46 | } |
47 | |
48 | if ( !is_string( $value ) && !is_int( $value ) ) { |
49 | return $this->msg( 'htmlform-required' ); |
50 | } |
51 | |
52 | $validOptions = HTMLFormField::flattenOptions( $this->getOptions() ); |
53 | |
54 | if ( in_array( strval( $value ), $validOptions, true ) ) { |
55 | return true; |
56 | } else { |
57 | return $this->msg( 'htmlform-select-badoption' ); |
58 | } |
59 | } |
60 | |
61 | /** |
62 | * This returns a block of all the radio options, in one cell. |
63 | * @see includes/HTMLFormField#getInputHTML() |
64 | * |
65 | * @param string $value |
66 | * |
67 | * @return string |
68 | */ |
69 | public function getInputHTML( $value ) { |
70 | if ( |
71 | isset( $this->mParams['option-descriptions'] ) || |
72 | isset( $this->mParams['option-descriptions-messages'] ) ) { |
73 | throw new InvalidArgumentException( |
74 | "Non-Codex HTMLForms do not support the 'option-descriptions' parameter for radio buttons" |
75 | ); |
76 | } |
77 | |
78 | $html = $this->formatOptions( $this->getOptions(), strval( $value ) ); |
79 | |
80 | return $html; |
81 | } |
82 | |
83 | public function getInputOOUI( $value ) { |
84 | if ( |
85 | isset( $this->mParams['option-descriptions'] ) || |
86 | isset( $this->mParams['option-descriptions-messages'] ) ) { |
87 | throw new InvalidArgumentException( |
88 | "Non-Codex HTMLForms do not support the 'option-descriptions' parameter for radio buttons" |
89 | ); |
90 | } |
91 | |
92 | $options = []; |
93 | foreach ( $this->getOptions() as $label => $data ) { |
94 | if ( is_int( $label ) ) { |
95 | $label = strval( $label ); |
96 | } |
97 | $options[] = [ |
98 | 'data' => $data, |
99 | // @phan-suppress-next-line SecurityCheck-XSS Labels are raw when not from message |
100 | 'label' => $this->mOptionsLabelsNotFromMessage ? new \OOUI\HtmlSnippet( $label ) : $label, |
101 | ]; |
102 | } |
103 | |
104 | return new \OOUI\RadioSelectInputWidget( [ |
105 | 'name' => $this->mName, |
106 | 'id' => $this->mID, |
107 | 'value' => $value, |
108 | 'options' => $options, |
109 | ] + \OOUI\Element::configFromHtmlAttributes( |
110 | $this->getAttributes( [ 'disabled', 'tabindex' ] ) |
111 | ) ); |
112 | } |
113 | |
114 | public function getInputCodex( $value, $hasErrors ) { |
115 | $optionDescriptions = $this->getOptionDescriptions(); |
116 | $html = ''; |
117 | |
118 | // Iterate over an array of options and return the HTML markup. |
119 | foreach ( $this->getOptions() as $label => $radioValue ) { |
120 | $description = $optionDescriptions[$radioValue] ?? ''; |
121 | $descriptionID = Sanitizer::escapeIdForAttribute( |
122 | $this->mID . "-$radioValue-description" |
123 | ); |
124 | |
125 | // Attributes for the radio input. |
126 | $radioInputClasses = [ 'cdx-radio__input' ]; |
127 | $radioInputAttribs = [ |
128 | 'id' => Sanitizer::escapeIdForAttribute( $this->mID . "-$radioValue" ), |
129 | 'type' => 'radio', |
130 | 'name' => $this->mName, |
131 | 'class' => $radioInputClasses, |
132 | 'value' => $radioValue |
133 | ]; |
134 | $radioInputAttribs += $this->getAttributes( [ 'disabled', 'tabindex' ] ); |
135 | if ( $description ) { |
136 | $radioInputAttribs['aria-describedby'] = $descriptionID; |
137 | } |
138 | |
139 | // Set the selected value as "checked". |
140 | if ( $radioValue === $value ) { |
141 | $radioInputAttribs['checked'] = true; |
142 | } |
143 | |
144 | // Attributes for the radio icon. |
145 | $radioIconClasses = [ 'cdx-radio__icon' ]; |
146 | $radioIconAttribs = [ |
147 | 'class' => $radioIconClasses, |
148 | ]; |
149 | |
150 | // Attributes for the radio label. |
151 | $radioLabelClasses = [ 'cdx-label__label' ]; |
152 | $radioLabelAttribs = [ |
153 | 'class' => $radioLabelClasses, |
154 | 'for' => $radioInputAttribs['id'] |
155 | ]; |
156 | |
157 | // HTML markup for radio input, radio icon, and radio label elements. |
158 | $radioInput = Html::element( 'input', $radioInputAttribs ); |
159 | $radioIcon = Html::element( 'span', $radioIconAttribs ); |
160 | $radioLabel = $this->mOptionsLabelsNotFromMessage |
161 | ? Html::rawElement( 'label', $radioLabelAttribs, $label ) |
162 | : Html::element( 'label', $radioLabelAttribs, $label ); |
163 | |
164 | $radioDescription = ''; |
165 | if ( isset( $optionDescriptions[$radioValue] ) ) { |
166 | $radioDescription = Html::rawElement( |
167 | 'span', |
168 | [ 'id' => $descriptionID, 'class' => 'cdx-label__description' ], |
169 | $optionDescriptions[$radioValue] |
170 | ); |
171 | } |
172 | $radioLabelWrapper = Html::rawElement( |
173 | 'div', |
174 | [ 'class' => 'cdx-radio__label cdx-label' ], |
175 | $radioLabel . $radioDescription |
176 | ); |
177 | |
178 | // HTML markup for CSS-only Codex Radio. |
179 | $radio = Html::rawElement( |
180 | 'div', |
181 | [ 'class' => 'cdx-radio' ], |
182 | $radioInput . $radioIcon . $radioLabelWrapper |
183 | ); |
184 | |
185 | // Append the Codex Radio HTML markup to the initialized empty string variable. |
186 | $html .= $radio; |
187 | } |
188 | |
189 | return $html; |
190 | } |
191 | |
192 | public function formatOptions( $options, $value ) { |
193 | $html = ''; |
194 | |
195 | $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] ); |
196 | $elementFunc = [ Html::class, $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ]; |
197 | |
198 | # @todo Should this produce an unordered list perhaps? |
199 | foreach ( $options as $label => $info ) { |
200 | if ( is_array( $info ) ) { |
201 | $html .= Html::rawElement( 'h1', [], $label ) . "\n"; |
202 | $html .= $this->formatOptions( $info, $value ); |
203 | } else { |
204 | $id = Sanitizer::escapeIdForAttribute( $this->mID . "-$info" ); |
205 | $classes = [ 'mw-htmlform-flatlist-item' ]; |
206 | $radio = Xml::radio( |
207 | $this->mName, $info, $info === $value, $attribs + [ 'id' => $id ] |
208 | ); |
209 | $radio .= "\u{00A0}" . call_user_func( $elementFunc, 'label', [ 'for' => $id ], $label ); |
210 | |
211 | $html .= ' ' . Html::rawElement( |
212 | 'div', |
213 | [ 'class' => $classes ], |
214 | $radio |
215 | ); |
216 | } |
217 | } |
218 | |
219 | return $html; |
220 | } |
221 | |
222 | /** |
223 | * Fetch the array of option descriptions from the field's parameters. This checks |
224 | * 'option-descriptions-messages' first, then 'option-descriptions'. |
225 | * |
226 | * @return array|null Array mapping option values to raw HTML descriptions |
227 | */ |
228 | protected function getOptionDescriptions() { |
229 | if ( array_key_exists( 'option-descriptions-messages', $this->mParams ) ) { |
230 | $needsParse = $this->mParams['option-descriptions-messages-parse'] ?? false; |
231 | $optionDescriptions = []; |
232 | foreach ( $this->mParams['option-descriptions-messages'] as $value => $msgKey ) { |
233 | $msg = $this->msg( $msgKey ); |
234 | $optionDescriptions[$value] = $needsParse ? $msg->parse() : $msg->escaped(); |
235 | } |
236 | return $optionDescriptions; |
237 | } elseif ( array_key_exists( 'option-descriptions', $this->mParams ) ) { |
238 | return $this->mParams['option-descriptions']; |
239 | } |
240 | } |
241 | |
242 | protected function needsLabel() { |
243 | return false; |
244 | } |
245 | } |
246 | |
247 | /** @deprecated class alias since 1.42 */ |
248 | class_alias( HTMLRadioField::class, 'HTMLRadioField' ); |