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