MediaWiki REL1_41
HTMLFormField.php
Go to the documentation of this file.
1<?php
2
7
14abstract class HTMLFormField {
16 public $mParams;
17
19 protected $mValidationCallback;
21 protected $mName;
22 protected $mDir;
23 protected $mLabel; # String label, as HTML. Set on construction.
24 protected $mID;
25 protected $mClass = '';
26 protected $mVFormClass = '';
27 protected $mHelpClass = false;
28 protected $mDefault;
29 private $mNotices;
30
34 protected $mOptions = false;
39 protected $mCondState = [];
40 protected $mCondStateClass = [];
41
46 protected $mShowEmptyLabels = true;
47
51 public $mParent;
52
63 abstract public function getInputHTML( $value );
64
73 public function getInputOOUI( $value ) {
74 return false;
75 }
76
83 public function canDisplayErrors() {
84 return $this->hasVisibleOutput();
85 }
86
99 public function msg( $key, ...$params ) {
100 if ( $this->mParent ) {
101 return $this->mParent->msg( $key, ...$params );
102 }
103 return wfMessage( $key, ...$params );
104 }
105
113 public function hasVisibleOutput() {
114 return true;
115 }
116
123 public function getName() {
124 return $this->mName;
125 }
126
138 protected function getNearestField( $name, $backCompat = false ) {
139 // When the field is belong to a HTMLFormFieldCloner
140 if ( isset( $this->mParams['cloner'] ) ) {
141 $field = $this->mParams['cloner']->findNearestField( $this, $name );
142 if ( $field ) {
143 return $field;
144 }
145 }
146
147 if ( $backCompat && str_starts_with( $name, 'wp' ) &&
148 !$this->mParent->hasField( $name )
149 ) {
150 // Don't break the existed use cases.
151 return $this->mParent->getField( substr( $name, 2 ) );
152 }
153 return $this->mParent->getField( $name );
154 }
155
167 protected function getNearestFieldValue( $alldata, $name, $asDisplay = false, $backCompat = false ) {
168 $field = $this->getNearestField( $name, $backCompat );
169 // When the field belongs to a HTMLFormFieldCloner
170 if ( isset( $field->mParams['cloner'] ) ) {
171 $value = $field->mParams['cloner']->extractFieldData( $field, $alldata );
172 } else {
173 // Note $alldata is an empty array when first rendering a form with a formIdentifier.
174 // In that case, $alldata[$field->mParams['fieldname']] is unset and we use the
175 // field's default value
176 $value = $alldata[$field->mParams['fieldname']] ?? $field->getDefault();
177 }
178
179 // Check invert state for HTMLCheckField
180 if ( $asDisplay && $field instanceof HTMLCheckField && ( $field->mParams['invert'] ?? false ) ) {
181 $value = !$value;
182 }
183
184 return $value;
185 }
186
197 protected function getNearestFieldByName( $alldata, $name, $asDisplay = false ) {
198 return (string)$this->getNearestFieldValue( $alldata, $name, $asDisplay );
199 }
200
207 protected function validateCondState( $params ) {
208 $origParams = $params;
209 $op = array_shift( $params );
210
211 $makeException = function ( string $details ) use ( $origParams ): InvalidArgumentException {
212 return new InvalidArgumentException(
213 "Invalid hide-if or disable-if specification for $this->mName: " .
214 $details . " in " . var_export( $origParams, true )
215 );
216 };
217
218 switch ( $op ) {
219 case 'NOT':
220 if ( count( $params ) !== 1 ) {
221 throw $makeException( "NOT takes exactly one parameter" );
222 }
223 // Fall-through intentionally
224
225 case 'AND':
226 case 'OR':
227 case 'NAND':
228 case 'NOR':
229 foreach ( $params as $i => $p ) {
230 if ( !is_array( $p ) ) {
231 $type = gettype( $p );
232 throw $makeException( "Expected array, found $type at index $i" );
233 }
234 $this->validateCondState( $p );
235 }
236 break;
237
238 case '===':
239 case '!==':
240 if ( count( $params ) !== 2 ) {
241 throw $makeException( "$op takes exactly two parameters" );
242 }
243 [ $name, $value ] = $params;
244 if ( !is_string( $name ) || !is_string( $value ) ) {
245 throw $makeException( "Parameters for $op must be strings" );
246 }
247 break;
248
249 default:
250 throw $makeException( "Unknown operation" );
251 }
252 }
253
261 protected function checkStateRecurse( array $alldata, array $params ) {
262 $op = array_shift( $params );
263 $valueChk = [ 'AND' => false, 'OR' => true, 'NAND' => false, 'NOR' => true ];
264 $valueRet = [ 'AND' => true, 'OR' => false, 'NAND' => false, 'NOR' => true ];
265
266 switch ( $op ) {
267 case 'AND':
268 case 'OR':
269 case 'NAND':
270 case 'NOR':
271 foreach ( $params as $p ) {
272 if ( $valueChk[$op] === $this->checkStateRecurse( $alldata, $p ) ) {
273 return !$valueRet[$op];
274 }
275 }
276 return $valueRet[$op];
277
278 case 'NOT':
279 return !$this->checkStateRecurse( $alldata, $params[0] );
280
281 case '===':
282 case '!==':
283 [ $field, $value ] = $params;
284 $testValue = (string)$this->getNearestFieldValue( $alldata, $field, true, true );
285 switch ( $op ) {
286 case '===':
287 return ( $value === $testValue );
288 case '!==':
289 return ( $value !== $testValue );
290 }
291 }
292 }
293
302 protected function parseCondState( $params ) {
303 $op = array_shift( $params );
304
305 switch ( $op ) {
306 case 'AND':
307 case 'OR':
308 case 'NAND':
309 case 'NOR':
310 $ret = [ $op ];
311 foreach ( $params as $p ) {
312 $ret[] = $this->parseCondState( $p );
313 }
314 return $ret;
315
316 case 'NOT':
317 return [ 'NOT', $this->parseCondState( $params[0] ) ];
318
319 case '===':
320 case '!==':
321 [ $name, $value ] = $params;
322 $field = $this->getNearestField( $name, true );
323 return [ $op, $field->getName(), $value ];
324 }
325 }
326
332 protected function parseCondStateForClient() {
333 $parsed = [];
334 foreach ( $this->mCondState as $type => $params ) {
335 $parsed[$type] = $this->parseCondState( $params );
336 }
337 return $parsed;
338 }
339
348 public function isHidden( $alldata ) {
349 return isset( $this->mCondState['hide'] ) &&
350 $this->checkStateRecurse( $alldata, $this->mCondState['hide'] );
351 }
352
361 public function isDisabled( $alldata ) {
362 return ( $this->mParams['disabled'] ?? false ) ||
363 $this->isHidden( $alldata ) ||
364 isset( $this->mCondState['disable'] ) &&
365 $this->checkStateRecurse( $alldata, $this->mCondState['disable'] );
366 }
367
379 public function cancelSubmit( $value, $alldata ) {
380 return false;
381 }
382
395 public function validate( $value, $alldata ) {
396 if ( $this->isHidden( $alldata ) ) {
397 return true;
398 }
399
400 if ( isset( $this->mParams['required'] )
401 && $this->mParams['required'] !== false
402 && ( $value === '' || $value === false || $value === null )
403 ) {
404 return $this->msg( 'htmlform-required' );
405 }
406
407 if ( !isset( $this->mValidationCallback ) ) {
408 return true;
409 }
410
411 $p = ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
412
413 if ( $p instanceof StatusValue ) {
414 $language = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage();
415
416 return $p->isGood() ? true : Status::wrap( $p )->getHTML( false, false, $language );
417 }
418
419 return $p;
420 }
421
430 public function filter( $value, $alldata ) {
431 if ( isset( $this->mFilterCallback ) ) {
432 $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
433 }
434
435 return $value;
436 }
437
445 protected function needsLabel() {
446 return true;
447 }
448
458 public function setShowEmptyLabel( $show ) {
459 $this->mShowEmptyLabels = $show;
460 }
461
473 protected function isSubmitAttempt( WebRequest $request ) {
474 // HTMLForm would add a hidden field of edit token for forms that require to be posted.
475 return $request->wasPosted() && $request->getCheck( 'wpEditToken' )
476 // The identifier matching or not has been checked in HTMLForm::prepareForm()
477 || $request->getCheck( 'wpFormIdentifier' );
478 }
479
488 public function loadDataFromRequest( $request ) {
489 if ( $request->getCheck( $this->mName ) ) {
490 return $request->getText( $this->mName );
491 } else {
492 return $this->getDefault();
493 }
494 }
495
504 public function __construct( $params ) {
505 $this->mParams = $params;
506
507 if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
508 $this->mParent = $params['parent'];
509 } else {
510 // Normally parent is added automatically by HTMLForm::factory.
511 // Several field types already assume unconditionally this is always set,
512 // so deprecate manually creating an HTMLFormField without a parent form set.
514 __METHOD__ . ": Constructing an HTMLFormField without a 'parent' parameter",
515 "1.40"
516 );
517 }
518
519 # Generate the label from a message, if possible
520 if ( isset( $params['label-message'] ) ) {
521 $this->mLabel = $this->getMessage( $params['label-message'] )->parse();
522 } elseif ( isset( $params['label'] ) ) {
523 if ( $params['label'] === '&#160;' || $params['label'] === "\u{00A0}" ) {
524 // Apparently some things set &nbsp directly and in an odd format
525 $this->mLabel = "\u{00A0}";
526 } else {
527 $this->mLabel = htmlspecialchars( $params['label'] );
528 }
529 } elseif ( isset( $params['label-raw'] ) ) {
530 $this->mLabel = $params['label-raw'];
531 }
532
533 $this->mName = $params['name'] ?? 'wp' . $params['fieldname'];
534
535 if ( isset( $params['dir'] ) ) {
536 $this->mDir = $params['dir'];
537 }
538
539 $this->mID = "mw-input-{$this->mName}";
540
541 if ( isset( $params['default'] ) ) {
542 $this->mDefault = $params['default'];
543 }
544
545 if ( isset( $params['id'] ) ) {
546 $this->mID = $params['id'];
547 }
548
549 if ( isset( $params['cssclass'] ) ) {
550 $this->mClass = $params['cssclass'];
551 }
552
553 if ( isset( $params['csshelpclass'] ) ) {
554 $this->mHelpClass = $params['csshelpclass'];
555 }
556
557 if ( isset( $params['validation-callback'] ) ) {
558 $this->mValidationCallback = $params['validation-callback'];
559 }
560
561 if ( isset( $params['filter-callback'] ) ) {
562 $this->mFilterCallback = $params['filter-callback'];
563 }
564
565 if ( isset( $params['hidelabel'] ) ) {
566 $this->mShowEmptyLabels = false;
567 }
568 if ( isset( $params['notices'] ) ) {
569 $this->mNotices = $params['notices'];
570 }
571
572 if ( isset( $params['hide-if'] ) && $params['hide-if'] ) {
573 $this->validateCondState( $params['hide-if'] );
574 $this->mCondState['hide'] = $params['hide-if'];
575 $this->mCondStateClass[] = 'mw-htmlform-hide-if';
576 }
577 if ( !( isset( $params['disabled'] ) && $params['disabled'] ) &&
578 isset( $params['disable-if'] ) && $params['disable-if']
579 ) {
580 $this->validateCondState( $params['disable-if'] );
581 $this->mCondState['disable'] = $params['disable-if'];
582 $this->mCondStateClass[] = 'mw-htmlform-disable-if';
583 }
584 }
585
595 public function getTableRow( $value ) {
596 [ $errors, $errorClass ] = $this->getErrorsAndErrorClass( $value );
597 $inputHtml = $this->getInputHTML( $value );
598 $fieldType = $this->getClassName();
599 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
600 $cellAttributes = [];
601 $rowAttributes = [];
602 $rowClasses = '';
603
604 if ( !empty( $this->mParams['vertical-label'] ) ) {
605 $cellAttributes['colspan'] = 2;
606 $verticalLabel = true;
607 } else {
608 $verticalLabel = false;
609 }
610
611 $label = $this->getLabelHtml( $cellAttributes );
612
613 $field = Html::rawElement(
614 'td',
615 [ 'class' => 'mw-input' ] + $cellAttributes,
616 $inputHtml . "\n$errors"
617 );
618
619 if ( $this->mCondState ) {
620 $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
621 $rowClasses .= implode( ' ', $this->mCondStateClass );
622 }
623
624 if ( $verticalLabel ) {
625 $html = Html::rawElement( 'tr',
626 $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
627 $html .= Html::rawElement( 'tr',
628 $rowAttributes + [
629 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
630 ],
631 $field );
632 } else {
633 $html = Html::rawElement( 'tr',
634 $rowAttributes + [
635 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
636 ],
637 $label . $field );
638 }
639
640 return $html . $helptext;
641 }
642
653 public function getDiv( $value ) {
654 [ $errors, $errorClass ] = $this->getErrorsAndErrorClass( $value );
655 $inputHtml = $this->getInputHTML( $value );
656 $fieldType = $this->getClassName();
657 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
658 $cellAttributes = [];
659 $label = $this->getLabelHtml( $cellAttributes );
660
661 $outerDivClass = [
662 'mw-input',
663 'mw-htmlform-nolabel' => ( $label === '' )
664 ];
665
666 $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
667
668 if ( $horizontalLabel ) {
669 $field = "\u{00A0}" . $inputHtml . "\n$errors";
670 } else {
671 $field = Html::rawElement(
672 'div',
673 // @phan-suppress-next-line PhanUselessBinaryAddRight
674 [ 'class' => $outerDivClass ] + $cellAttributes,
675 $inputHtml . "\n$errors"
676 );
677 }
678
679 $wrapperAttributes = [ 'class' => [
680 "mw-htmlform-field-$fieldType",
681 $this->mClass,
682 $this->mVFormClass,
683 $errorClass,
684 ] ];
685 if ( $this->mCondState ) {
686 $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
687 $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
688 }
689 return Html::rawElement( 'div', $wrapperAttributes, $label . $field ) .
690 $helptext;
691 }
692
702 public function getOOUI( $value ) {
703 $inputField = $this->getInputOOUI( $value );
704
705 if ( !$inputField ) {
706 // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
707 // generate the whole field, label and errors and all, then wrap it in a Widget.
708 // It might look weird, but it'll work OK.
709 return $this->getFieldLayoutOOUI(
710 new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
711 [ 'align' => 'top' ]
712 );
713 }
714
715 $infusable = true;
716 if ( is_string( $inputField ) ) {
717 // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
718 // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
719 // JavaScript doesn't know how to rebuilt the contents.
720 $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
721 $infusable = false;
722 }
723
724 $fieldType = $this->getClassName();
725 $help = $this->getHelpText();
726 $errors = $this->getErrorsRaw( $value );
727 foreach ( $errors as &$error ) {
728 $error = new OOUI\HtmlSnippet( $error );
729 }
730
731 $config = [
732 'classes' => [ "mw-htmlform-field-$fieldType" ],
733 'align' => $this->getLabelAlignOOUI(),
734 'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
735 'errors' => $errors,
736 'infusable' => $infusable,
737 'helpInline' => $this->isHelpInline(),
738 'notices' => $this->mNotices ?: [],
739 ];
740 if ( $this->mClass !== '' ) {
741 $config['classes'][] = $this->mClass;
742 }
743
744 $preloadModules = false;
745
746 if ( $infusable && $this->shouldInfuseOOUI() ) {
747 $preloadModules = true;
748 $config['classes'][] = 'mw-htmlform-autoinfuse';
749 }
750 if ( $this->mCondState ) {
751 $config['classes'] = array_merge( $config['classes'], $this->mCondStateClass );
752 }
753
754 // the element could specify, that the label doesn't need to be added
755 $label = $this->getLabel();
756 if ( $label && $label !== "\u{00A0}" && $label !== '&#160;' ) {
757 $config['label'] = new OOUI\HtmlSnippet( $label );
758 }
759
760 if ( $this->mCondState ) {
761 $preloadModules = true;
762 $config['condState'] = $this->parseCondStateForClient();
763 }
764
765 $config['modules'] = $this->getOOUIModules();
766
767 if ( $preloadModules ) {
768 $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
769 $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
770 }
771
772 return $this->getFieldLayoutOOUI( $inputField, $config );
773 }
774
782 protected function getClassName() {
783 $name = explode( '\\', static::class );
784 return end( $name );
785 }
786
792 protected function getLabelAlignOOUI() {
793 return 'top';
794 }
795
802 protected function getFieldLayoutOOUI( $inputField, $config ) {
803 return new HTMLFormFieldLayout( $inputField, $config );
804 }
805
814 protected function shouldInfuseOOUI() {
815 // Always infuse fields with popup help text, since the interface for it is nicer with JS
816 return !$this->isHelpInline() && $this->getHelpMessages();
817 }
818
826 protected function getOOUIModules() {
827 return [];
828 }
829
840 public function getRaw( $value ) {
841 [ $errors, ] = $this->getErrorsAndErrorClass( $value );
842 return "\n" . $errors .
843 $this->getLabelHtml() .
844 $this->getInputHTML( $value ) .
845 $this->getHelpTextHtmlRaw( $this->getHelpText() );
846 }
847
857 public function getVForm( $value ) {
858 // Ewwww
859 $this->mVFormClass = ' mw-ui-vform-field';
860 return $this->getDiv( $value );
861 }
862
870 public function getInline( $value ) {
871 [ $errors, ] = $this->getErrorsAndErrorClass( $value );
872 return "\n" . $errors .
873 $this->getLabelHtml() .
874 "\u{00A0}" .
875 $this->getInputHTML( $value ) .
876 $this->getHelpTextHtmlDiv( $this->getHelpText() );
877 }
878
886 public function getHelpTextHtmlTable( $helptext ) {
887 if ( $helptext === null ) {
888 return '';
889 }
890
891 $rowAttributes = [];
892 if ( $this->mCondState ) {
893 $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
894 $rowAttributes['class'] = $this->mCondStateClass;
895 }
896
897 $tdClasses = [ 'htmlform-tip' ];
898 if ( $this->mHelpClass !== false ) {
899 $tdClasses[] = $this->mHelpClass;
900 }
901 return Html::rawElement( 'tr', $rowAttributes,
902 Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext )
903 );
904 }
905
914 public function getHelpTextHtmlDiv( $helptext ) {
915 if ( $helptext === null ) {
916 return '';
917 }
918
919 $wrapperAttributes = [
920 'class' => [ 'htmlform-tip' ],
921 ];
922 if ( $this->mHelpClass !== false ) {
923 $wrapperAttributes['class'][] = $this->mHelpClass;
924 }
925 if ( $this->mCondState ) {
926 $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
927 $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
928 }
929 return Html::rawElement( 'div', $wrapperAttributes, $helptext );
930 }
931
939 public function getHelpTextHtmlRaw( $helptext ) {
940 return $this->getHelpTextHtmlDiv( $helptext );
941 }
942
943 private function getHelpMessages(): array {
944 if ( isset( $this->mParams['help-message'] ) ) {
945 return [ $this->mParams['help-message'] ];
946 } elseif ( isset( $this->mParams['help-messages'] ) ) {
947 return $this->mParams['help-messages'];
948 } elseif ( isset( $this->mParams['help'] ) ) {
949 return [ new HtmlArmor( $this->mParams['help'] ) ];
950 }
951
952 return [];
953 }
954
961 public function getHelpText() {
962 $html = [];
963
964 foreach ( $this->getHelpMessages() as $msg ) {
965 if ( $msg instanceof HtmlArmor ) {
966 $html[] = HtmlArmor::getHtml( $msg );
967 } else {
968 $msg = $this->getMessage( $msg );
969 if ( $msg->exists() ) {
970 $html[] = $msg->parse();
971 }
972 }
973 }
974
975 return $html ? implode( $this->msg( 'word-separator' )->escaped(), $html ) : null;
976 }
977
986 public function isHelpInline() {
987 return $this->mParams['help-inline'] ?? true;
988 }
989
1002 public function getErrorsAndErrorClass( $value ) {
1003 $errors = $this->validate( $value, $this->mParent->mFieldData );
1004
1005 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1006 return [ '', '' ];
1007 }
1008
1009 return [ self::formatErrors( $errors ), 'mw-htmlform-invalid-input' ];
1010 }
1011
1019 public function getErrorsRaw( $value ) {
1020 $errors = $this->validate( $value, $this->mParent->mFieldData );
1021
1022 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1023 return [];
1024 }
1025
1026 if ( !is_array( $errors ) ) {
1027 $errors = [ $errors ];
1028 }
1029 foreach ( $errors as &$error ) {
1030 if ( $error instanceof Message ) {
1031 $error = $error->parse();
1032 }
1033 }
1034
1035 return $errors;
1036 }
1037
1042 public function getLabel() {
1043 return $this->mLabel ?? '';
1044 }
1045
1052 public function getLabelHtml( $cellAttributes = [] ) {
1053 # Don't output a for= attribute for labels with no associated input.
1054 # Kind of hacky here, possibly we don't want these to be <label>s at all.
1055 $for = $this->needsLabel() ? [ 'for' => $this->mID ] : [];
1056
1057 $labelValue = trim( $this->getLabel() );
1058 $hasLabel = $labelValue !== '' && $labelValue !== "\u{00A0}" && $labelValue !== '&#160;';
1059
1060 $displayFormat = $this->mParent->getDisplayFormat();
1061 $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
1062
1063 if ( $displayFormat === 'table' ) {
1064 return Html::rawElement( 'td',
1065 [ 'class' => 'mw-label' ] + $cellAttributes,
1066 Html::rawElement( 'label', $for, $labelValue ) );
1067 } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
1068 if ( $displayFormat === 'div' && !$horizontalLabel ) {
1069 return Html::rawElement( 'div',
1070 [ 'class' => 'mw-label' ] + $cellAttributes,
1071 Html::rawElement( 'label', $for, $labelValue ) );
1072 } else {
1073 return Html::rawElement( 'label', $for, $labelValue );
1074 }
1075 }
1076
1077 return '';
1078 }
1079
1084 public function getDefault() {
1085 return $this->mDefault ?? null;
1086 }
1087
1093 public function getTooltipAndAccessKey() {
1094 if ( empty( $this->mParams['tooltip'] ) ) {
1095 return [];
1096 }
1097
1098 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
1099 }
1100
1106 public function getTooltipAndAccessKeyOOUI() {
1107 if ( empty( $this->mParams['tooltip'] ) ) {
1108 return [];
1109 }
1110
1111 return [
1112 'title' => Linker::titleAttrib( $this->mParams['tooltip'] ),
1113 'accessKey' => Linker::accesskey( $this->mParams['tooltip'] ),
1114 ];
1115 }
1116
1124 public function getAttributes( array $list ) {
1125 static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
1126
1127 $ret = [];
1128 foreach ( $list as $key ) {
1129 if ( in_array( $key, $boolAttribs ) ) {
1130 if ( !empty( $this->mParams[$key] ) ) {
1131 $ret[$key] = '';
1132 }
1133 } elseif ( isset( $this->mParams[$key] ) ) {
1134 $ret[$key] = $this->mParams[$key];
1135 }
1136 }
1137
1138 return $ret;
1139 }
1140
1150 private function lookupOptionsKeys( $options, $needsParse ) {
1151 $ret = [];
1152 foreach ( $options as $key => $value ) {
1153 $msg = $this->msg( $key );
1154 $key = $needsParse ? $msg->parse() : $msg->plain();
1155 $ret[$key] = is_array( $value )
1156 ? $this->lookupOptionsKeys( $value, $needsParse )
1157 : strval( $value );
1158 }
1159 return $ret;
1160 }
1161
1169 public static function forceToStringRecursive( $array ) {
1170 if ( is_array( $array ) ) {
1171 return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1172 } else {
1173 return strval( $array );
1174 }
1175 }
1176
1183 public function getOptions() {
1184 if ( $this->mOptions === false ) {
1185 if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1186 $needsParse = $this->mParams['options-messages-parse'] ?? false;
1187 if ( $needsParse ) {
1188 $this->mOptionsLabelsNotFromMessage = true;
1189 }
1190 $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'], $needsParse );
1191 } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1192 $this->mOptionsLabelsNotFromMessage = true;
1193 $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1194 } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1195 $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1196 $this->mOptions = Xml::listDropDownOptions( $message );
1197 } else {
1198 $this->mOptions = null;
1199 }
1200 }
1201
1202 return $this->mOptions;
1203 }
1204
1210 public function getOptionsOOUI() {
1211 $oldoptions = $this->getOptions();
1212
1213 if ( $oldoptions === null ) {
1214 return null;
1215 }
1216
1217 return Xml::listDropDownOptionsOoui( $oldoptions );
1218 }
1219
1227 public static function flattenOptions( $options ) {
1228 $flatOpts = [];
1229
1230 foreach ( $options as $value ) {
1231 if ( is_array( $value ) ) {
1232 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1233 } else {
1234 $flatOpts[] = $value;
1235 }
1236 }
1237
1238 return $flatOpts;
1239 }
1240
1254 protected static function formatErrors( $errors ) {
1255 if ( is_array( $errors ) && count( $errors ) === 1 ) {
1256 $errors = array_shift( $errors );
1257 }
1258
1259 if ( is_array( $errors ) ) {
1260 foreach ( $errors as &$error ) {
1261 $error = Html::rawElement( 'li', [],
1262 $error instanceof Message ? $error->parse() : $error
1263 );
1264 }
1265 $errors = Html::rawElement( 'ul', [], implode( "\n", $errors ) );
1266 } elseif ( $errors instanceof Message ) {
1267 $errors = $errors->parse();
1268 }
1269
1270 return Html::errorBox( $errors );
1271 }
1272
1279 protected function getMessage( $value ) {
1280 $message = Message::newFromSpecifier( $value );
1281
1282 if ( $this->mParent ) {
1283 $message->setContext( $this->mParent );
1284 }
1285
1286 return $message;
1287 }
1288
1296 public function skipLoadData( $request ) {
1297 return !empty( $this->mParams['nodata'] );
1298 }
1299
1308 // This is probably more restrictive than it needs to be, but better safe than sorry
1309 return (bool)$this->mCondState;
1310 }
1311}
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:88
A checkbox field.
The parent class to generate form fields.
getName()
Get the field name that will be used for submission.
getHelpTextHtmlDiv( $helptext)
Generate help text HTML in div format.
needsJSForHtml5FormValidation()
Whether this field requires the user agent to have JavaScript enabled for the client-side HTML5 form ...
parseCondStateForClient()
Parse the cond-state array for client-side.
HTMLForm null $mParent
getLabelAlignOOUI()
Get label alignment when generating field for OOUI.
getInputOOUI( $value)
Same as getInputHTML, but returns an OOUI object.
getHelpTextHtmlRaw( $helptext)
Generate help text HTML formatted for raw output.
isHelpInline()
Determine if the help text should be displayed inline.
isDisabled( $alldata)
Test whether this field is supposed to be disabled, based on the values of the other form fields.
getFieldLayoutOOUI( $inputField, $config)
Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
validateCondState( $params)
Validate the cond-state params, the existence check of fields should be done later.
isHidden( $alldata)
Test whether this field is supposed to be hidden, based on the values of the other form fields.
filter( $value, $alldata)
bool $mShowEmptyLabels
If true will generate an empty div element with no label.
getNearestField( $name, $backCompat=false)
Get the closest field matching a given name.
shouldInfuseOOUI()
Whether the field should be automatically infused.
__construct( $params)
Initialise the object.
getMessage( $value)
Turns a *-message parameter (which could be a MessageSpecifier, or a message name,...
getClassName()
Gets the non namespaced class name.
getOOUIModules()
Get the list of extra ResourceLoader modules which must be loaded client-side before it's possible to...
getNearestFieldByName( $alldata, $name, $asDisplay=false)
Fetch a field value from $alldata for the closest field matching a given name.
skipLoadData( $request)
Skip this field when collecting data.
array $mCondState
Array to hold params for 'hide-if' or 'disable-if' statements.
getVForm( $value)
Get the complete field for the input, including help text, labels, and whatever.
getInputHTML( $value)
This function must be implemented to return the HTML to generate the input object itself.
getErrorsAndErrorClass( $value)
Determine form errors to display and their classes.
validate( $value, $alldata)
Override this function to add specific validation checks on the field input.
getOOUI( $value)
Get the OOUI version of the div.
canDisplayErrors()
True if this field type is able to display errors; false if validation errors need to be displayed in...
checkStateRecurse(array $alldata, array $params)
Helper function for isHidden and isDisabled to handle recursive data structures.
getHelpTextHtmlTable( $helptext)
Generate help text HTML in table format.
array array[] $mParams
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
getRaw( $value)
Get the complete raw fields for the input, including help text, labels, and whatever.
getTooltipAndAccessKeyOOUI()
Returns the attributes required for the tooltip and accesskey, for OOUI widgets' config.
getErrorsRaw( $value)
Determine form errors to display, returning them in an array.
getHelpText()
Determine the help text to display.
getOptions()
Fetch the array of options from the field's parameters.
parseCondState( $params)
Parse the cond-state array to use the field name for submission, since the key in the form descriptor...
getTableRow( $value)
Get the complete table row for the input, including help text, labels, and whatever.
getInline( $value)
Get the complete field as an inline element.
isSubmitAttempt(WebRequest $request)
Can we assume that the request is an attempt to submit a HTMLForm, as opposed to an attempt to just v...
msg( $key,... $params)
Get a translated interface message.
array null false $mOptions
getLabelHtml( $cellAttributes=[])
loadDataFromRequest( $request)
Get the value that this input has been set to from a posted form, or the input's default value if it ...
getAttributes(array $list)
Returns the given attributes from the parameters.
getNearestFieldValue( $alldata, $name, $asDisplay=false, $backCompat=false)
Fetch a field value from $alldata for the closest field matching a given name.
setShowEmptyLabel( $show)
Tell the field whether to generate a separate label element if its label is blank.
needsLabel()
Should this field have a label, or is there no input element with the appropriate id for the label to...
getDiv( $value)
Get the complete div for the input, including help text, labels, and whatever.
cancelSubmit( $value, $alldata)
Override this function if the control can somehow trigger a form submission that shouldn't actually s...
getOptionsOOUI()
Get options and make them into arrays suitable for OOUI.
hasVisibleOutput()
If this field has a user-visible output or not.
getTooltipAndAccessKey()
Returns the attributes required for the tooltip and accesskey, for Html::element() etc.
static formatErrors( $errors)
Formats one or more errors as accepted by field validation-callback.
static forceToStringRecursive( $array)
Recursively forces values in an array to strings, because issues arise with integer 0 as a value.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:158
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:30
This class is a collection of static functions that serve two purposes:
Definition Html.php:57
Some internal bits split of from Skin.php.
Definition Linker.php:65
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:58
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:144
parse()
Fully parse the text from wikitext to HTML.
Definition Message.php:1045
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:427
Generic operation result class Has warning/error list, boolean status and arbitrary value.
return true
Definition router.php:92