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