19 protected $mValidationCallback;
23 protected $mLabel; # String label, as HTML. Set on construction.
39 protected $mCondState = [];
46 protected $mShowEmptyLabels =
true;
99 public function msg( $key, ...$params ) {
100 if ( $this->mParent ) {
101 return $this->mParent->msg( $key, ...$params );
140 if ( isset( $this->mParams[
'cloner'] ) ) {
141 $field = $this->mParams[
'cloner']->findNearestField( $this, $name );
147 if ( $backCompat && str_starts_with( $name,
'wp' ) &&
148 !$this->mParent->hasField( $name )
151 return $this->mParent->getField( substr( $name, 2 ) );
153 return $this->mParent->getField( $name );
170 if ( isset( $field->mParams[
'cloner'] ) ) {
171 $value = $field->mParams[
'cloner']->extractFieldData( $field, $alldata );
176 $value = $alldata[$field->mParams[
'fieldname']] ?? $field->getDefault();
180 if ( $asDisplay && $field instanceof
HTMLCheckField && ( $field->mParams[
'invert'] ??
false ) ) {
208 $origParams = $params;
209 $op = array_shift( $params );
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 )
220 if ( count( $params ) !== 1 ) {
221 throw $makeException(
"NOT takes exactly one parameter" );
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" );
240 if ( count( $params ) !== 2 ) {
241 throw $makeException(
"$op takes exactly two parameters" );
243 [ $name, $value ] = $params;
244 if ( !is_string( $name ) || !is_string( $value ) ) {
245 throw $makeException(
"Parameters for $op must be strings" );
250 throw $makeException(
"Unknown operation" );
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 ];
271 foreach ( $params as $p ) {
273 return !$valueRet[$op];
276 return $valueRet[$op];
283 [ $field, $value ] = $params;
287 return ( $value === $testValue );
289 return ( $value !== $testValue );
303 $op = array_shift( $params );
311 foreach ( $params as $p ) {
321 [ $name, $value ] = $params;
323 return [ $op, $field->getName(), $value ];
334 foreach ( $this->mCondState as $type => $params ) {
349 return isset( $this->mCondState[
'hide'] ) &&
362 return ( $this->mParams[
'disabled'] ??
false ) ||
364 isset( $this->mCondState[
'disable'] ) &&
396 if ( $this->
isHidden( $alldata ) ) {
400 if ( isset( $this->mParams[
'required'] )
401 && $this->mParams[
'required'] !==
false
402 && ( $value ===
'' || $value ===
false || $value ===
null )
404 return $this->
msg(
'htmlform-required' );
407 if ( !isset( $this->mValidationCallback ) ) {
411 $p = ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
414 $language = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage();
416 return $p->isGood() ?
true : Status::wrap( $p )->getHTML(
false,
false, $language );
430 public function filter( $value, $alldata ) {
431 if ( isset( $this->mFilterCallback ) ) {
432 $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
459 $this->mShowEmptyLabels = $show;
477 || $request->
getCheck(
'wpFormIdentifier' );
489 if ( $request->getCheck( $this->mName ) ) {
490 return $request->getText( $this->mName );
505 $this->mParams = $params;
507 if ( isset( $params[
'parent'] ) && $params[
'parent'] instanceof
HTMLForm ) {
508 $this->mParent = $params[
'parent'];
514 __METHOD__ .
": Constructing an HTMLFormField without a 'parent' parameter",
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'] ===
' ' || $params[
'label'] ===
"\u{00A0}" ) {
525 $this->mLabel =
"\u{00A0}";
527 $this->mLabel = htmlspecialchars( $params[
'label'] );
529 } elseif ( isset( $params[
'label-raw'] ) ) {
530 $this->mLabel = $params[
'label-raw'];
533 $this->mName = $params[
'name'] ??
'wp' . $params[
'fieldname'];
535 if ( isset( $params[
'dir'] ) ) {
536 $this->mDir = $params[
'dir'];
539 $this->mID =
"mw-input-{$this->mName}";
541 if ( isset( $params[
'default'] ) ) {
542 $this->mDefault = $params[
'default'];
545 if ( isset( $params[
'id'] ) ) {
546 $this->mID = $params[
'id'];
549 if ( isset( $params[
'cssclass'] ) ) {
550 $this->mClass = $params[
'cssclass'];
553 if ( isset( $params[
'csshelpclass'] ) ) {
554 $this->mHelpClass = $params[
'csshelpclass'];
557 if ( isset( $params[
'validation-callback'] ) ) {
558 $this->mValidationCallback = $params[
'validation-callback'];
561 if ( isset( $params[
'filter-callback'] ) ) {
562 $this->mFilterCallback = $params[
'filter-callback'];
565 if ( isset( $params[
'hidelabel'] ) ) {
566 $this->mShowEmptyLabels =
false;
568 if ( isset( $params[
'notices'] ) ) {
569 $this->mNotices = $params[
'notices'];
572 if ( isset( $params[
'hide-if'] ) && $params[
'hide-if'] ) {
574 $this->mCondState[
'hide'] = $params[
'hide-if'];
575 $this->mCondStateClass[] =
'mw-htmlform-hide-if';
577 if ( !( isset( $params[
'disabled'] ) && $params[
'disabled'] ) &&
578 isset( $params[
'disable-if'] ) && $params[
'disable-if']
581 $this->mCondState[
'disable'] = $params[
'disable-if'];
582 $this->mCondStateClass[] =
'mw-htmlform-disable-if';
600 $cellAttributes = [];
604 if ( !empty( $this->mParams[
'vertical-label'] ) ) {
605 $cellAttributes[
'colspan'] = 2;
606 $verticalLabel =
true;
608 $verticalLabel =
false;
613 $field = Html::rawElement(
615 [
'class' =>
'mw-input' ] + $cellAttributes,
616 $inputHtml .
"\n$errors"
619 if ( $this->mCondState ) {
621 $rowClasses .= implode(
' ', $this->mCondStateClass );
624 if ( $verticalLabel ) {
625 $html = Html::rawElement(
'tr',
626 $rowAttributes + [
'class' =>
"mw-htmlform-vertical-label $rowClasses" ], $label );
627 $html .= Html::rawElement(
'tr',
629 'class' =>
"mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
633 $html = Html::rawElement(
'tr',
635 'class' =>
"mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
640 return $html . $helptext;
658 $cellAttributes = [];
663 'mw-htmlform-nolabel' => ( $label ===
'' )
666 $horizontalLabel = $this->mParams[
'horizontal-label'] ??
false;
668 if ( $horizontalLabel ) {
669 $field =
"\u{00A0}" . $inputHtml .
"\n$errors";
671 $field = Html::rawElement(
674 [
'class' => $outerDivClass ] + $cellAttributes,
675 $inputHtml .
"\n$errors"
679 $wrapperAttributes = [
'class' => [
680 "mw-htmlform-field-$fieldType",
685 if ( $this->mCondState ) {
687 $wrapperAttributes[
'class'] = array_merge( $wrapperAttributes[
'class'], $this->mCondStateClass );
689 return Html::rawElement(
'div', $wrapperAttributes, $label . $field ) .
705 if ( !$inputField ) {
710 new OOUI\Widget( [
'content' =>
new OOUI\HtmlSnippet( $this->
getDiv( $value ) ) ] ),
716 if ( is_string( $inputField ) ) {
720 $inputField =
new OOUI\Widget( [
'content' =>
new OOUI\HtmlSnippet( $inputField ) ] );
727 foreach ( $errors as &$error ) {
728 $error =
new OOUI\HtmlSnippet( $error );
732 'classes' => [
"mw-htmlform-field-$fieldType" ],
734 'help' => ( $help !==
null && $help !==
'' ) ?
new OOUI\HtmlSnippet( $help ) :
null,
736 'infusable' => $infusable,
738 'notices' => $this->mNotices ?: [],
740 if ( $this->mClass !==
'' ) {
741 $config[
'classes'][] = $this->mClass;
744 $preloadModules =
false;
747 $preloadModules =
true;
748 $config[
'classes'][] =
'mw-htmlform-autoinfuse';
750 if ( $this->mCondState ) {
751 $config[
'classes'] = array_merge( $config[
'classes'], $this->mCondStateClass );
756 if ( $label && $label !==
"\u{00A0}" && $label !==
' ' ) {
757 $config[
'label'] =
new OOUI\HtmlSnippet( $label );
760 if ( $this->mCondState ) {
761 $preloadModules =
true;
767 if ( $preloadModules ) {
768 $this->mParent->getOutput()->addModules(
'mediawiki.htmlform.ooui' );
769 $this->mParent->getOutput()->addModules( $this->
getOOUIModules() );
783 $name = explode(
'\\', static::class );
816 return !$this->
isHelpInline() && $this->getHelpMessages();
842 return "\n" . $errors .
859 $this->mVFormClass =
' mw-ui-vform-field';
860 return $this->
getDiv( $value );
872 return "\n" . $errors .
887 if ( $helptext ===
null ) {
892 if ( $this->mCondState ) {
894 $rowAttributes[
'class'] = $this->mCondStateClass;
897 $tdClasses = [
'htmlform-tip' ];
898 if ( $this->mHelpClass !==
false ) {
899 $tdClasses[] = $this->mHelpClass;
901 return Html::rawElement(
'tr', $rowAttributes,
902 Html::rawElement(
'td', [
'colspan' => 2,
'class' => $tdClasses ], $helptext )
915 if ( $helptext ===
null ) {
919 $wrapperAttributes = [
920 'class' => [
'htmlform-tip' ],
922 if ( $this->mHelpClass !==
false ) {
923 $wrapperAttributes[
'class'][] = $this->mHelpClass;
925 if ( $this->mCondState ) {
927 $wrapperAttributes[
'class'] = array_merge( $wrapperAttributes[
'class'], $this->mCondStateClass );
929 return Html::rawElement(
'div', $wrapperAttributes, $helptext );
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'] ) ];
964 foreach ( $this->getHelpMessages() as $msg ) {
966 $html[] = HtmlArmor::getHtml( $msg );
968 $msg = $this->getMessage( $msg );
969 if ( $msg->exists() ) {
970 $html[] = $msg->parse();
975 return $html ? implode( $this->msg(
'word-separator' )->escaped(), $html ) :
null;
987 return $this->mParams[
'help-inline'] ??
true;
1003 $errors = $this->validate( $value, $this->mParent->mFieldData );
1005 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1009 return [ self::formatErrors( $errors ),
'mw-htmlform-invalid-input' ];
1020 $errors = $this->validate( $value, $this->mParent->mFieldData );
1022 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1026 if ( !is_array( $errors ) ) {
1027 $errors = [ $errors ];
1029 foreach ( $errors as &$error ) {
1030 if ( $error instanceof
Message ) {
1031 $error = $error->parse();
1043 return $this->mLabel ??
'';
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 ] : [];
1057 $labelValue = trim( $this->getLabel() );
1058 $hasLabel = $labelValue !==
'' && $labelValue !==
"\u{00A0}" && $labelValue !==
' ';
1060 $displayFormat = $this->mParent->getDisplayFormat();
1061 $horizontalLabel = $this->mParams[
'horizontal-label'] ??
false;
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 ) );
1073 return Html::rawElement(
'label', $for, $labelValue );
1085 return $this->mDefault ??
null;
1094 if ( empty( $this->mParams[
'tooltip'] ) ) {
1098 return Linker::tooltipAndAccesskeyAttribs( $this->mParams[
'tooltip'] );
1107 if ( empty( $this->mParams[
'tooltip'] ) ) {
1112 'title' => Linker::titleAttrib( $this->mParams[
'tooltip'] ),
1113 'accessKey' => Linker::accesskey( $this->mParams[
'tooltip'] ),
1125 static $boolAttribs = [
'disabled',
'required',
'autofocus',
'multiple',
'readonly' ];
1128 foreach ( $list as $key ) {
1129 if ( in_array( $key, $boolAttribs ) ) {
1130 if ( !empty( $this->mParams[$key] ) ) {
1133 } elseif ( isset( $this->mParams[$key] ) ) {
1134 $ret[$key] = $this->mParams[$key];
1150 private function lookupOptionsKeys( $options, $needsParse ) {
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 )
1170 if ( is_array( $array ) ) {
1171 return array_map( [ __CLASS__,
'forceToStringRecursive' ], $array );
1173 return strval( $array );
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;
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 );
1198 $this->mOptions =
null;
1202 return $this->mOptions;
1211 $oldoptions = $this->getOptions();
1213 if ( $oldoptions ===
null ) {
1217 return Xml::listDropDownOptionsOoui( $oldoptions );
1230 foreach ( $options as $value ) {
1231 if ( is_array( $value ) ) {
1232 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1234 $flatOpts[] = $value;
1255 if ( is_array( $errors ) && count( $errors ) === 1 ) {
1256 $errors = array_shift( $errors );
1259 if ( is_array( $errors ) ) {
1260 foreach ( $errors as &$error ) {
1261 $error = Html::rawElement(
'li', [],
1265 $errors = Html::rawElement(
'ul', [], implode(
"\n", $errors ) );
1266 } elseif ( $errors instanceof
Message ) {
1267 $errors = $errors->parse();
1270 return Html::errorBox( $errors );
1282 if ( $this->mParent ) {
1283 $message->setContext( $this->mParent );
1297 return !empty( $this->mParams[
'nodata'] );
1309 return (
bool)$this->mCondState;
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'))
Marks HTML that shouldn't be escaped.
The Message class deals with fetching and processing of interface message into a variety of formats.
parse()
Fully parse the text from wikitext to HTML.
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Generic operation result class Has warning/error list, boolean status and arbitrary value.