Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 448 |
|
0.00% |
0 / 17 |
CRAP | |
0.00% |
0 / 1 |
InputBox | |
0.00% |
0 / 448 |
|
0.00% |
0 / 17 |
11130 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
render | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
110 | |||
getEditActionArgs | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getLinebreakClasses | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
getSearchForm | |
0.00% |
0 / 101 |
|
0.00% |
0 / 1 |
600 | |||
getSearchForm2 | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
72 | |||
getCreateForm | |
0.00% |
0 / 64 |
|
0.00% |
0 / 1 |
272 | |||
getMoveForm | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
20 | |||
getCommentForm | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
90 | |||
extractOptions | |
0.00% |
0 / 60 |
|
0.00% |
0 / 1 |
272 | |||
isValidColor | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
buildTextBox | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
buildCheckboxInput | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
buildSubmitInput | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
bgColorStyle | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
shouldUseVE | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
languageConvert | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * Classes for InputBox extension |
4 | * |
5 | * @file |
6 | * @ingroup Extensions |
7 | */ |
8 | |
9 | namespace MediaWiki\Extension\InputBox; |
10 | |
11 | use ExtensionRegistry; |
12 | use MediaWiki\Config\Config; |
13 | use MediaWiki\Html\Html; |
14 | use MediaWiki\MainConfigNames; |
15 | use MediaWiki\Parser\Sanitizer; |
16 | use MediaWiki\SpecialPage\SpecialPage; |
17 | use Parser; |
18 | |
19 | /** |
20 | * InputBox class |
21 | */ |
22 | class InputBox { |
23 | |
24 | /* Fields */ |
25 | |
26 | /** @var Config */ |
27 | private $config; |
28 | /** @var Parser */ |
29 | private $mParser; |
30 | /** @var string */ |
31 | private $mType = ''; |
32 | /** @var int */ |
33 | private $mWidth = 50; |
34 | /** @var ?string */ |
35 | private $mPreload = null; |
36 | /** @var ?array */ |
37 | private $mPreloadparams = null; |
38 | /** @var ?string */ |
39 | private $mEditIntro = null; |
40 | /** @var ?string */ |
41 | private $mUseVE = null; |
42 | /** @var ?string */ |
43 | private $mUseDT = null; |
44 | /** @var ?string */ |
45 | private $mSummary = null; |
46 | /** @var ?string */ |
47 | private $mNosummary = null; |
48 | /** @var ?string */ |
49 | private $mMinor = null; |
50 | /** @var string */ |
51 | private $mPage = ''; |
52 | /** @var string */ |
53 | private $mBR = 'yes'; |
54 | /** @var string */ |
55 | private $mDefaultText = ''; |
56 | /** @var string */ |
57 | private $mPlaceholderText = ''; |
58 | /** @var string */ |
59 | private $mBGColor = 'transparent'; |
60 | /** @var string */ |
61 | private $mButtonLabel = ''; |
62 | /** @var string */ |
63 | private $mSearchButtonLabel = ''; |
64 | /** @var string */ |
65 | private $mFullTextButton = ''; |
66 | /** @var string */ |
67 | private $mLabelText = ''; |
68 | /** @var ?string */ |
69 | private $mHidden = ''; |
70 | /** @var string */ |
71 | private $mNamespaces = ''; |
72 | /** @var string */ |
73 | private $mID = ''; |
74 | /** @var ?string */ |
75 | private $mInline = null; |
76 | /** @var string */ |
77 | private $mPrefix = ''; |
78 | /** @var string */ |
79 | private $mDir = ''; |
80 | /** @var string */ |
81 | private $mSearchFilter = ''; |
82 | /** @var string */ |
83 | private $mTour = ''; |
84 | /** @var string */ |
85 | private $mTextBoxAriaLabel = ''; |
86 | |
87 | /* Functions */ |
88 | |
89 | /** |
90 | * @param Config $config |
91 | * @param Parser $parser |
92 | */ |
93 | public function __construct( |
94 | Config $config, |
95 | $parser |
96 | ) { |
97 | $this->config = $config; |
98 | $this->mParser = $parser; |
99 | // Default value for dir taken from the page language (bug 37018) |
100 | $this->mDir = $this->mParser->getTargetLanguage()->getDir(); |
101 | // Split caches by language, to make sure visitors do not see a cached |
102 | // version in a random language (since labels are in the user language) |
103 | $this->mParser->getOptions()->getUserLangObj(); |
104 | $this->mParser->getOutput()->addModuleStyles( [ |
105 | 'ext.inputBox.styles', |
106 | 'mediawiki.ui.input', |
107 | 'mediawiki.ui.checkbox', |
108 | 'mediawiki.ui.button', |
109 | ] ); |
110 | } |
111 | |
112 | public function render() { |
113 | // Handle various types |
114 | switch ( $this->mType ) { |
115 | case 'create': |
116 | case 'comment': |
117 | return $this->getCreateForm(); |
118 | case 'move': |
119 | return $this->getMoveForm(); |
120 | case 'commenttitle': |
121 | return $this->getCommentForm(); |
122 | case 'search': |
123 | return $this->getSearchForm( 'search' ); |
124 | case 'fulltext': |
125 | return $this->getSearchForm( 'fulltext' ); |
126 | case 'search2': |
127 | return $this->getSearchForm2(); |
128 | default: |
129 | $key = $this->mType === '' ? 'inputbox-error-no-type' : 'inputbox-error-bad-type'; |
130 | return Html::rawElement( 'div', [], |
131 | Html::element( 'strong', |
132 | [ 'class' => 'error' ], |
133 | wfMessage( $key, $this->mType )->text() |
134 | ) |
135 | ); |
136 | } |
137 | } |
138 | |
139 | /** |
140 | * Returns the action name and value to use in inputboxes which redirects to edit pages. |
141 | * Decides, if the link should redirect to VE edit page (veaction=edit) or to wikitext editor |
142 | * (action=edit). |
143 | * |
144 | * @return array Array with name and value data |
145 | */ |
146 | private function getEditActionArgs() { |
147 | // default is wikitext editor |
148 | $args = [ |
149 | 'name' => 'action', |
150 | 'value' => 'edit', |
151 | ]; |
152 | // check, if VE is installed and VE editor is requested |
153 | if ( $this->shouldUseVE() ) { |
154 | $args = [ |
155 | 'name' => 'veaction', |
156 | 'value' => 'edit', |
157 | ]; |
158 | } |
159 | return $args; |
160 | } |
161 | |
162 | /** |
163 | * Get common classes, that could be added and depend on, if |
164 | * a line break between a button and an input field is added or not. |
165 | * |
166 | * @return string |
167 | */ |
168 | private function getLinebreakClasses() { |
169 | return strtolower( $this->mBR ) === '<br />' ? 'mw-inputbox-input ' : ''; |
170 | } |
171 | |
172 | /** |
173 | * Generate search form |
174 | * @param string $type |
175 | * @return string HTML |
176 | */ |
177 | public function getSearchForm( $type ) { |
178 | // Use button label fallbacks |
179 | if ( !$this->mButtonLabel ) { |
180 | $this->mButtonLabel = wfMessage( 'inputbox-tryexact' )->text(); |
181 | } |
182 | if ( !$this->mSearchButtonLabel ) { |
183 | $this->mSearchButtonLabel = wfMessage( 'inputbox-searchfulltext' )->text(); |
184 | } |
185 | if ( $this->mID !== '' ) { |
186 | $idArray = [ 'id' => Sanitizer::escapeIdForAttribute( $this->mID ) ]; |
187 | } else { |
188 | $idArray = []; |
189 | } |
190 | // We need a unqiue id to link <label> to checkboxes, but also |
191 | // want multiple <inputbox>'s to not be invalid html |
192 | $idRandStr = Sanitizer::escapeIdForAttribute( '-' . $this->mID . wfRandom() ); |
193 | |
194 | // Build HTML |
195 | $htmlOut = Html::openElement( 'div', |
196 | [ |
197 | 'class' => 'mw-inputbox-centered', |
198 | 'style' => $this->bgColorStyle(), |
199 | ] |
200 | ); |
201 | $htmlOut .= Html::openElement( 'form', |
202 | [ |
203 | 'name' => 'searchbox', |
204 | 'class' => 'searchbox', |
205 | 'action' => SpecialPage::getTitleFor( 'Search' )->getLocalUrl(), |
206 | ] + $idArray |
207 | ); |
208 | |
209 | $htmlOut .= $this->buildTextBox( [ |
210 | // enable SearchSuggest with mw-searchInput class |
211 | 'class' => $this->getLinebreakClasses() . 'mw-searchInput searchboxInput', |
212 | 'name' => 'search', |
213 | 'type' => $this->mHidden ? 'hidden' : 'text', |
214 | 'value' => $this->mDefaultText, |
215 | 'placeholder' => $this->mPlaceholderText, |
216 | 'size' => $this->mWidth, |
217 | 'dir' => $this->mDir |
218 | ] ); |
219 | |
220 | if ( $this->mPrefix !== '' ) { |
221 | $htmlOut .= Html::hidden( 'prefix', $this->mPrefix ); |
222 | } |
223 | |
224 | if ( $this->mSearchFilter !== '' ) { |
225 | $htmlOut .= Html::hidden( 'searchfilter', $this->mSearchFilter ); |
226 | } |
227 | |
228 | if ( $this->mTour !== '' ) { |
229 | $htmlOut .= Html::hidden( 'tour', $this->mTour ); |
230 | } |
231 | |
232 | $htmlOut .= $this->mBR; |
233 | |
234 | // Determine namespace checkboxes |
235 | $namespacesArray = explode( ',', $this->mNamespaces ); |
236 | if ( $this->mNamespaces ) { |
237 | $contLang = $this->mParser->getContentLanguage(); |
238 | $namespaces = $contLang->getNamespaces(); |
239 | $nsAliases = array_merge( |
240 | $contLang->getNamespaceAliases(), |
241 | $this->config->get( MainConfigNames::NamespaceAliases ) |
242 | ); |
243 | $showNamespaces = []; |
244 | $checkedNS = []; |
245 | // Check for valid namespaces |
246 | foreach ( $namespacesArray as $userNS ) { |
247 | // no whitespace |
248 | $userNS = trim( $userNS ); |
249 | |
250 | // Namespace needs to be checked if flagged with "**" |
251 | if ( strpos( $userNS, '**' ) ) { |
252 | $userNS = str_replace( '**', '', $userNS ); |
253 | $checkedNS[$userNS] = true; |
254 | } |
255 | |
256 | $mainMsg = wfMessage( 'inputbox-ns-main' )->inContentLanguage()->text(); |
257 | if ( $userNS === 'Main' || $userNS === $mainMsg ) { |
258 | $i = 0; |
259 | } elseif ( array_search( $userNS, $namespaces ) ) { |
260 | $i = array_search( $userNS, $namespaces ); |
261 | } elseif ( isset( $nsAliases[$userNS] ) ) { |
262 | $i = $nsAliases[$userNS]; |
263 | } else { |
264 | // Namespace not recognized, skip |
265 | continue; |
266 | } |
267 | $showNamespaces[$i] = $userNS; |
268 | if ( isset( $checkedNS[$userNS] ) && $checkedNS[$userNS] ) { |
269 | $checkedNS[$i] = true; |
270 | } |
271 | } |
272 | |
273 | // Show valid namespaces |
274 | foreach ( $showNamespaces as $i => $name ) { |
275 | $checked = []; |
276 | // Namespace flagged with "**" or if it's the only one |
277 | if ( ( isset( $checkedNS[$i] ) && $checkedNS[$i] ) || count( $showNamespaces ) === 1 ) { |
278 | $checked = [ 'checked' => 'checked' ]; |
279 | } |
280 | |
281 | if ( count( $showNamespaces ) === 1 ) { |
282 | // Checkbox |
283 | $htmlOut .= Html::element( 'input', |
284 | [ |
285 | 'type' => 'hidden', |
286 | 'name' => 'ns' . $i, |
287 | 'value' => 1, |
288 | 'id' => 'mw-inputbox-ns' . $i . $idRandStr |
289 | ] + $checked |
290 | ); |
291 | } else { |
292 | // Checkbox |
293 | $htmlOut .= $this->buildCheckboxInput( |
294 | $name, 'ns' . $i, 'mw-inputbox-ns' . $i . $idRandStr, "1", $checked |
295 | ); |
296 | } |
297 | } |
298 | |
299 | // Line break |
300 | $htmlOut .= $this->mBR; |
301 | } elseif ( $type === 'search' ) { |
302 | // Go button |
303 | $htmlOut .= $this->buildSubmitInput( |
304 | [ |
305 | 'type' => 'submit', |
306 | 'name' => 'go', |
307 | 'value' => $this->mButtonLabel |
308 | ] |
309 | ); |
310 | $htmlOut .= "\u{00A0}"; |
311 | } |
312 | |
313 | // Search button |
314 | $htmlOut .= $this->buildSubmitInput( |
315 | [ |
316 | 'type' => 'submit', |
317 | 'name' => 'fulltext', |
318 | 'value' => $this->mSearchButtonLabel |
319 | ] |
320 | ); |
321 | |
322 | // Hidden fulltext param for IE (bug 17161) |
323 | if ( $type === 'fulltext' ) { |
324 | $htmlOut .= Html::hidden( 'fulltext', 'Search' ); |
325 | } |
326 | |
327 | $htmlOut .= Html::closeElement( 'form' ); |
328 | $htmlOut .= Html::closeElement( 'div' ); |
329 | |
330 | // Return HTML |
331 | return $htmlOut; |
332 | } |
333 | |
334 | /** |
335 | * Generate search form version 2 |
336 | * @return string |
337 | */ |
338 | public function getSearchForm2() { |
339 | // Use button label fallbacks |
340 | if ( !$this->mButtonLabel ) { |
341 | $this->mButtonLabel = wfMessage( 'inputbox-tryexact' )->text(); |
342 | } |
343 | |
344 | if ( $this->mID !== '' ) { |
345 | $unescapedID = $this->mID; |
346 | } else { |
347 | // The label element needs a unique id, use |
348 | // random number to avoid multiple input boxes |
349 | // having conflicts. |
350 | $unescapedID = wfRandom(); |
351 | } |
352 | $id = Sanitizer::escapeIdForAttribute( $unescapedID ); |
353 | $htmlLabel = ''; |
354 | if ( strlen( trim( $this->mLabelText ) ) ) { |
355 | $htmlLabel = Html::openElement( 'label', [ 'for' => 'bodySearchInput' . $id ] ); |
356 | $htmlLabel .= $this->mParser->recursiveTagParse( $this->mLabelText ); |
357 | $htmlLabel .= Html::closeElement( 'label' ); |
358 | } |
359 | $htmlOut = Html::openElement( 'form', |
360 | [ |
361 | 'name' => 'bodySearch' . $id, |
362 | 'id' => 'bodySearch' . $id, |
363 | 'class' => 'bodySearch' . ( $this->mInline ? ' mw-inputbox-inline' : '' ), |
364 | 'action' => SpecialPage::getTitleFor( 'Search' )->getLocalUrl(), |
365 | ] |
366 | ); |
367 | $htmlOut .= Html::openElement( 'div', |
368 | [ |
369 | 'class' => 'bodySearchWrap' . ( $this->mInline ? ' mw-inputbox-inline' : '' ), |
370 | 'style' => $this->bgColorStyle(), |
371 | ] |
372 | ); |
373 | $htmlOut .= $htmlLabel; |
374 | |
375 | $htmlOut .= $this->buildTextBox( [ |
376 | 'type' => $this->mHidden ? 'hidden' : 'text', |
377 | 'name' => 'search', |
378 | // enable SearchSuggest with mw-searchInput class |
379 | 'class' => 'mw-searchInput', |
380 | 'size' => $this->mWidth, |
381 | 'id' => 'bodySearchInput' . $id, |
382 | 'dir' => $this->mDir, |
383 | 'placeholder' => $this->mPlaceholderText |
384 | ] ); |
385 | |
386 | $htmlOut .= "\u{00A0}" . $this->buildSubmitInput( |
387 | [ |
388 | 'type' => 'submit', |
389 | 'name' => 'go', |
390 | 'value' => $this->mButtonLabel, |
391 | ] |
392 | ); |
393 | |
394 | // Better testing needed here! |
395 | if ( $this->mFullTextButton !== '' ) { |
396 | $htmlOut .= $this->buildSubmitInput( |
397 | [ |
398 | 'type' => 'submit', |
399 | 'name' => 'fulltext', |
400 | 'value' => $this->mSearchButtonLabel |
401 | ] |
402 | ); |
403 | } |
404 | |
405 | $htmlOut .= Html::closeElement( 'div' ); |
406 | $htmlOut .= Html::closeElement( 'form' ); |
407 | |
408 | // Return HTML |
409 | return $htmlOut; |
410 | } |
411 | |
412 | /** |
413 | * Generate create page form |
414 | * @return string |
415 | */ |
416 | public function getCreateForm() { |
417 | if ( $this->mType === 'comment' ) { |
418 | if ( !$this->mButtonLabel ) { |
419 | $this->mButtonLabel = wfMessage( 'inputbox-postcomment' )->text(); |
420 | } |
421 | } else { |
422 | if ( !$this->mButtonLabel ) { |
423 | $this->mButtonLabel = wfMessage( 'inputbox-createarticle' )->text(); |
424 | } |
425 | } |
426 | |
427 | $htmlOut = Html::openElement( 'div', |
428 | [ |
429 | 'class' => 'mw-inputbox-centered', |
430 | 'style' => $this->bgColorStyle(), |
431 | ] |
432 | ); |
433 | $createBoxParams = [ |
434 | 'name' => 'createbox', |
435 | 'class' => 'createbox', |
436 | 'action' => $this->config->get( MainConfigNames::Script ), |
437 | 'method' => 'get' |
438 | ]; |
439 | if ( $this->mID !== '' ) { |
440 | $createBoxParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID ); |
441 | } |
442 | $htmlOut .= Html::openElement( 'form', $createBoxParams ); |
443 | $editArgs = $this->getEditActionArgs(); |
444 | $htmlOut .= Html::hidden( $editArgs['name'], $editArgs['value'] ); |
445 | if ( $this->mPreload !== null ) { |
446 | $htmlOut .= Html::hidden( 'preload', $this->mPreload ); |
447 | } |
448 | if ( is_array( $this->mPreloadparams ) ) { |
449 | foreach ( $this->mPreloadparams as $preloadparams ) { |
450 | $htmlOut .= Html::hidden( 'preloadparams[]', $preloadparams ); |
451 | } |
452 | } |
453 | if ( $this->mEditIntro !== null ) { |
454 | $htmlOut .= Html::hidden( 'editintro', $this->mEditIntro ); |
455 | } |
456 | if ( $this->mSummary !== null ) { |
457 | $htmlOut .= Html::hidden( 'summary', $this->mSummary ); |
458 | } |
459 | if ( $this->mNosummary !== null ) { |
460 | $htmlOut .= Html::hidden( 'nosummary', $this->mNosummary ); |
461 | } |
462 | if ( $this->mPrefix !== '' ) { |
463 | $htmlOut .= Html::hidden( 'prefix', $this->mPrefix ); |
464 | } |
465 | if ( $this->mMinor !== null ) { |
466 | $htmlOut .= Html::hidden( 'minor', $this->mMinor ); |
467 | } |
468 | // @phan-suppress-next-line PhanSuspiciousValueComparison False positive |
469 | if ( $this->mType === 'comment' ) { |
470 | $htmlOut .= Html::hidden( 'section', 'new' ); |
471 | if ( $this->mUseDT ) { |
472 | $htmlOut .= Html::hidden( 'dtpreload', '1' ); |
473 | } |
474 | } |
475 | |
476 | $htmlOut .= $this->buildTextBox( [ |
477 | 'type' => $this->mHidden ? 'hidden' : 'text', |
478 | 'name' => 'title', |
479 | 'class' => $this->getLinebreakClasses() . |
480 | 'mw-inputbox-createbox', |
481 | 'value' => $this->mDefaultText, |
482 | 'placeholder' => $this->mPlaceholderText, |
483 | // For visible input fields, use required so that the form will not |
484 | // submit without a value |
485 | 'required' => !$this->mHidden, |
486 | 'size' => $this->mWidth, |
487 | 'dir' => $this->mDir |
488 | ] ); |
489 | |
490 | $htmlOut .= $this->mBR; |
491 | $htmlOut .= $this->buildSubmitInput( |
492 | [ |
493 | 'type' => 'submit', |
494 | 'name' => 'create', |
495 | 'value' => $this->mButtonLabel |
496 | ], |
497 | true |
498 | ); |
499 | $htmlOut .= Html::closeElement( 'form' ); |
500 | $htmlOut .= Html::closeElement( 'div' ); |
501 | |
502 | // Return HTML |
503 | return $htmlOut; |
504 | } |
505 | |
506 | /** |
507 | * Generate move page form |
508 | * @return string |
509 | */ |
510 | public function getMoveForm() { |
511 | if ( !$this->mButtonLabel ) { |
512 | $this->mButtonLabel = wfMessage( 'inputbox-movearticle' )->text(); |
513 | } |
514 | |
515 | $htmlOut = Html::openElement( 'div', |
516 | [ |
517 | 'class' => 'mw-inputbox-centered', |
518 | 'style' => $this->bgColorStyle(), |
519 | ] |
520 | ); |
521 | $moveBoxParams = [ |
522 | 'name' => 'movebox', |
523 | 'class' => 'mw-movebox', |
524 | 'action' => $this->config->get( MainConfigNames::Script ), |
525 | 'method' => 'get' |
526 | ]; |
527 | if ( $this->mID !== '' ) { |
528 | $moveBoxParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID ); |
529 | } |
530 | $htmlOut .= Html::openElement( 'form', $moveBoxParams ); |
531 | $htmlOut .= Html::hidden( 'title', |
532 | SpecialPage::getTitleFor( 'Movepage', $this->mPage )->getPrefixedText() ); |
533 | $htmlOut .= Html::hidden( 'wpReason', $this->mSummary ); |
534 | $htmlOut .= Html::hidden( 'prefix', $this->mPrefix ); |
535 | |
536 | $htmlOut .= $this->buildTextBox( [ |
537 | 'type' => $this->mHidden ? 'hidden' : 'text', |
538 | 'name' => 'wpNewTitle', |
539 | 'class' => $this->getLinebreakClasses() . 'mw-moveboxInput', |
540 | 'value' => $this->mDefaultText, |
541 | 'placeholder' => $this->mPlaceholderText, |
542 | 'size' => $this->mWidth, |
543 | 'dir' => $this->mDir |
544 | ] ); |
545 | |
546 | $htmlOut .= $this->mBR; |
547 | $htmlOut .= $this->buildSubmitInput( |
548 | [ |
549 | 'type' => 'submit', |
550 | 'value' => $this->mButtonLabel |
551 | ], |
552 | true |
553 | ); |
554 | $htmlOut .= Html::closeElement( 'form' ); |
555 | $htmlOut .= Html::closeElement( 'div' ); |
556 | |
557 | // Return HTML |
558 | return $htmlOut; |
559 | } |
560 | |
561 | /** |
562 | * Generate new section form |
563 | * @return string |
564 | */ |
565 | public function getCommentForm() { |
566 | if ( !$this->mButtonLabel ) { |
567 | $this->mButtonLabel = wfMessage( 'inputbox-postcommenttitle' )->text(); |
568 | } |
569 | |
570 | $htmlOut = Html::openElement( 'div', |
571 | [ |
572 | 'class' => 'mw-inputbox-centered', |
573 | 'style' => $this->bgColorStyle(), |
574 | ] |
575 | ); |
576 | $commentFormParams = [ |
577 | 'name' => 'commentbox', |
578 | 'class' => 'commentbox', |
579 | 'action' => $this->config->get( MainConfigNames::Script ), |
580 | 'method' => 'get' |
581 | ]; |
582 | if ( $this->mID !== '' ) { |
583 | $commentFormParams['id'] = Sanitizer::escapeIdForAttribute( $this->mID ); |
584 | } |
585 | $htmlOut .= Html::openElement( 'form', $commentFormParams ); |
586 | $editArgs = $this->getEditActionArgs(); |
587 | $htmlOut .= Html::hidden( $editArgs['name'], $editArgs['value'] ); |
588 | if ( $this->mPreload !== null ) { |
589 | $htmlOut .= Html::hidden( 'preload', $this->mPreload ); |
590 | } |
591 | if ( is_array( $this->mPreloadparams ) ) { |
592 | foreach ( $this->mPreloadparams as $preloadparams ) { |
593 | $htmlOut .= Html::hidden( 'preloadparams[]', $preloadparams ); |
594 | } |
595 | } |
596 | if ( $this->mEditIntro !== null ) { |
597 | $htmlOut .= Html::hidden( 'editintro', $this->mEditIntro ); |
598 | } |
599 | |
600 | $htmlOut .= $this->buildTextBox( [ |
601 | 'type' => $this->mHidden ? 'hidden' : 'text', |
602 | 'name' => 'preloadtitle', |
603 | 'class' => $this->getLinebreakClasses() . 'commentboxInput', |
604 | 'value' => $this->mDefaultText, |
605 | 'placeholder' => $this->mPlaceholderText, |
606 | 'size' => $this->mWidth, |
607 | 'dir' => $this->mDir |
608 | ] ); |
609 | |
610 | $htmlOut .= Html::hidden( 'section', 'new' ); |
611 | if ( $this->mUseDT ) { |
612 | $htmlOut .= Html::hidden( 'dtpreload', '1' ); |
613 | } |
614 | $htmlOut .= Html::hidden( 'title', $this->mPage ); |
615 | $htmlOut .= $this->mBR; |
616 | $htmlOut .= $this->buildSubmitInput( |
617 | [ |
618 | 'type' => 'submit', |
619 | 'name' => 'create', |
620 | 'value' => $this->mButtonLabel |
621 | ], |
622 | true |
623 | ); |
624 | $htmlOut .= Html::closeElement( 'form' ); |
625 | $htmlOut .= Html::closeElement( 'div' ); |
626 | |
627 | // Return HTML |
628 | return $htmlOut; |
629 | } |
630 | |
631 | /** |
632 | * Extract options from a blob of text |
633 | * |
634 | * @param string $text Tag contents |
635 | */ |
636 | public function extractOptions( $text ) { |
637 | // Parse all possible options |
638 | $values = []; |
639 | foreach ( explode( "\n", $text ) as $line ) { |
640 | if ( strpos( $line, '=' ) === false ) { |
641 | continue; |
642 | } |
643 | [ $name, $value ] = explode( '=', $line, 2 ); |
644 | $name = strtolower( trim( $name ) ); |
645 | $value = Sanitizer::decodeCharReferences( trim( $value ) ); |
646 | if ( $name === 'preloadparams[]' ) { |
647 | // We have to special-case this one because it's valid for it to appear more than once. |
648 | $this->mPreloadparams[] = $value; |
649 | } else { |
650 | $values[ $name ] = $value; |
651 | } |
652 | } |
653 | |
654 | // Validate the dir value. |
655 | if ( isset( $values['dir'] ) && !in_array( $values['dir'], [ 'ltr', 'rtl' ] ) ) { |
656 | unset( $values['dir'] ); |
657 | } |
658 | |
659 | // Build list of options, with local member names |
660 | $options = [ |
661 | 'type' => 'mType', |
662 | 'width' => 'mWidth', |
663 | 'preload' => 'mPreload', |
664 | 'page' => 'mPage', |
665 | 'editintro' => 'mEditIntro', |
666 | 'useve' => 'mUseVE', |
667 | 'usedt' => 'mUseDT', |
668 | 'summary' => 'mSummary', |
669 | 'nosummary' => 'mNosummary', |
670 | 'minor' => 'mMinor', |
671 | 'break' => 'mBR', |
672 | 'default' => 'mDefaultText', |
673 | 'placeholder' => 'mPlaceholderText', |
674 | 'bgcolor' => 'mBGColor', |
675 | 'buttonlabel' => 'mButtonLabel', |
676 | 'searchbuttonlabel' => 'mSearchButtonLabel', |
677 | 'fulltextbutton' => 'mFullTextButton', |
678 | 'namespaces' => 'mNamespaces', |
679 | 'labeltext' => 'mLabelText', |
680 | 'hidden' => 'mHidden', |
681 | 'id' => 'mID', |
682 | 'inline' => 'mInline', |
683 | 'prefix' => 'mPrefix', |
684 | 'dir' => 'mDir', |
685 | 'searchfilter' => 'mSearchFilter', |
686 | 'tour' => 'mTour', |
687 | 'arialabel' => 'mTextBoxAriaLabel' |
688 | ]; |
689 | // Options we should maybe run through lang converter. |
690 | $convertOptions = [ |
691 | 'default' => true, |
692 | 'buttonlabel' => true, |
693 | 'searchbuttonlabel' => true, |
694 | 'placeholder' => true, |
695 | 'arialabel' => true |
696 | ]; |
697 | foreach ( $options as $name => $var ) { |
698 | if ( isset( $values[$name] ) ) { |
699 | $this->$var = $values[$name]; |
700 | if ( isset( $convertOptions[$name] ) ) { |
701 | $this->$var = $this->languageConvert( $this->$var ); |
702 | } |
703 | } |
704 | } |
705 | |
706 | // Insert a line break if configured to do so |
707 | $this->mBR = ( strtolower( $this->mBR ) === 'no' ) ? ' ' : '<br />'; |
708 | |
709 | // Validate the width; make sure it's a valid, positive integer |
710 | $this->mWidth = intval( $this->mWidth <= 0 ? 50 : $this->mWidth ); |
711 | |
712 | // Validate background color |
713 | if ( !$this->isValidColor( $this->mBGColor ) ) { |
714 | $this->mBGColor = 'transparent'; |
715 | } |
716 | |
717 | // T297725: De-obfuscate attempts to trick people into making edits to .js pages |
718 | $target = $this->mType === 'commenttitle' ? $this->mPage : $this->mDefaultText; |
719 | if ( $this->mHidden && $this->mPreload && substr( $target, -3 ) === '.js' ) { |
720 | $this->mHidden = null; |
721 | } |
722 | } |
723 | |
724 | /** |
725 | * Do a security check on the bgcolor parameter |
726 | * @param string $color |
727 | * @return bool |
728 | */ |
729 | public function isValidColor( $color ) { |
730 | $regex = <<<REGEX |
731 | /^ ( |
732 | [a-zA-Z]* | # color names |
733 | \# [0-9a-f]{3} | # short hexadecimal |
734 | \# [0-9a-f]{6} | # long hexadecimal |
735 | rgb \s* \( \s* ( |
736 | \d+ \s* , \s* \d+ \s* , \s* \d+ | # rgb integer |
737 | [0-9.]+% \s* , \s* [0-9.]+% \s* , \s* [0-9.]+% # rgb percent |
738 | ) \s* \) |
739 | ) $ /xi |
740 | REGEX; |
741 | return (bool)preg_match( $regex, $color ); |
742 | } |
743 | |
744 | /** |
745 | * Factory method to help build the textbox widget. |
746 | * |
747 | * @param array $defaultAttr |
748 | * @return string |
749 | */ |
750 | private function buildTextBox( $defaultAttr ) { |
751 | if ( $this->mTextBoxAriaLabel ) { |
752 | $defaultAttr[ 'aria-label' ] = $this->mTextBoxAriaLabel; |
753 | } |
754 | |
755 | $class = $defaultAttr[ 'class' ] ?? ''; |
756 | $class .= ' mw-ui-input mw-ui-input-inline'; |
757 | $defaultAttr[ 'class' ] = $class; |
758 | return Html::element( 'input', $defaultAttr ); |
759 | } |
760 | |
761 | /** |
762 | * Factory method to help build checkbox input. |
763 | * |
764 | * @param string $label text displayed next to checkbox (label) |
765 | * @param string $name name of input |
766 | * @param string $id id of input |
767 | * @param string $value value of input |
768 | * @param array $defaultAttr (optional) |
769 | * @return string |
770 | */ |
771 | private function buildCheckboxInput( $label, $name, $id, $value, $defaultAttr = [] ) { |
772 | $htmlOut = ' <div class="mw-inputbox-element mw-ui-checkbox">'; |
773 | $htmlOut .= Html::element( 'input', |
774 | [ |
775 | 'type' => 'checkbox', |
776 | 'name' => $name, |
777 | 'value' => $value, |
778 | 'id' => $id, |
779 | ] + $defaultAttr |
780 | ); |
781 | // Label |
782 | $htmlOut .= Html::label( $label, $id ); |
783 | $htmlOut .= '</div> '; |
784 | return $htmlOut; |
785 | } |
786 | |
787 | /** |
788 | * Factory method to help build submit button. |
789 | * |
790 | * @param array $defaultAttr |
791 | * @param bool $isProgressive (optional) |
792 | * @return string |
793 | */ |
794 | private function buildSubmitInput( $defaultAttr, $isProgressive = false ) { |
795 | $defaultAttr[ 'class' ] ??= ''; |
796 | $defaultAttr[ 'class' ] .= ' mw-ui-button'; |
797 | if ( $isProgressive ) { |
798 | $defaultAttr[ 'class' ] .= ' mw-ui-progressive'; |
799 | } |
800 | $defaultAttr[ 'class' ] = trim( $defaultAttr[ 'class' ] ); |
801 | return Html::element( 'input', $defaultAttr ); |
802 | } |
803 | |
804 | private function bgColorStyle() { |
805 | if ( $this->mBGColor !== 'transparent' ) { |
806 | return 'background-color: ' . $this->mBGColor . ';'; |
807 | } |
808 | return ''; |
809 | } |
810 | |
811 | /** |
812 | * Returns true, if the VisualEditor is requested from the inputbox wikitext definition and |
813 | * if the VisualEditor extension is actually installed or not, false otherwise. |
814 | * |
815 | * @return bool |
816 | */ |
817 | private function shouldUseVE() { |
818 | return ExtensionRegistry::getInstance()->isLoaded( 'VisualEditor' ) && $this->mUseVE !== null; |
819 | } |
820 | |
821 | /** |
822 | * For compatability with pre T119158 behaviour |
823 | * |
824 | * If a field that is going to be used as an attribute |
825 | * and it contains "-{" in it, run it through language |
826 | * converter. |
827 | * |
828 | * Its not really clear if it would make more sense to |
829 | * always convert instead of only if -{ is present. This |
830 | * function just more or less restores the previous |
831 | * accidental behaviour. |
832 | * |
833 | * @see https://phabricator.wikimedia.org/T180485 |
834 | * @param string $text |
835 | * @return string |
836 | */ |
837 | private function languageConvert( $text ) { |
838 | $langConv = $this->mParser->getTargetLanguageConverter(); |
839 | if ( $langConv->hasVariants() && strpos( $text, '-{' ) !== false ) { |
840 | $text = $langConv->convert( $text ); |
841 | } |
842 | return $text; |
843 | } |
844 | } |