6use InvalidArgumentException;
32 protected $mValidationCallback;
34 protected $mFilterCallback;
143 if ( $this->mParent ) {
144 return $this->mParent->msg( $key, ...
$params );
183 $cloner = $this->mParams[
'cloner'] ??
null;
185 $field = $cloner->findNearestField( $this, $name );
191 if ( $backCompat && str_starts_with( $name,
'wp' ) &&
192 !$this->mParent->hasField( $name )
195 return $this->mParent->getField( substr( $name, 2 ) );
197 return $this->mParent->getField( $name );
214 $cloner = $field->mParams[
'cloner'] ??
null;
216 $value = $cloner->extractFieldData( $field, $alldata );
221 $value = $alldata[$field->mParams[
'fieldname']] ?? $field->getDefault();
225 if ( $asDisplay && $field instanceof
HTMLCheckField && ( $field->mParams[
'invert'] ??
false ) ) {
256 $makeException =
function (
string $details ) use ( $origParams ): InvalidArgumentException {
257 return new InvalidArgumentException(
258 "Invalid hide-if or disable-if specification for $this->mName: " .
259 $details .
" in " . var_export( $origParams,
true )
265 if ( count(
$params ) !== 1 ) {
266 throw $makeException(
"NOT takes exactly one parameter" );
274 foreach (
$params as $i => $p ) {
275 if ( !is_array( $p ) ) {
276 $type = get_debug_type( $p );
277 throw $makeException(
"Expected array, found $type at index $i" );
285 if ( count(
$params ) !== 2 ) {
286 throw $makeException(
"$op takes exactly two parameters" );
289 if ( !is_string( $name ) || !is_string( $value ) ) {
290 throw $makeException(
"Parameters for $op must be strings" );
295 throw $makeException(
"Unknown operation" );
308 $valueChk = [
'AND' =>
false,
'OR' =>
true,
'NAND' =>
false,
'NOR' => true ];
309 $valueRet = [
'AND' =>
true,
'OR' =>
false,
'NAND' =>
false,
'NOR' => true ];
318 return !$valueRet[$op];
321 return $valueRet[$op];
332 return ( $value === $testValue );
334 return ( $value !== $testValue );
368 return [ $op, $field->getName(), $value ];
379 foreach ( $this->mCondState as $type =>
$params ) {
394 return isset( $this->mCondState[
'hide'] ) &&
407 return ( $this->mParams[
'disabled'] ??
false ) ||
409 ( isset( $this->mCondState[
'disable'] )
441 if ( $this->
isHidden( $alldata ) ) {
445 if ( isset( $this->mParams[
'required'] )
446 && $this->mParams[
'required'] !==
false
447 && ( $value ===
'' || $value ===
false || $value ===
null )
449 return $this->
msg(
'htmlform-required' );
452 if ( $this->mValidationCallback ===
null ) {
456 $p = ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
459 $language = $this->mParent ? $this->mParent->getLanguage() : RequestContext::getMain()->getLanguage();
461 return $p->isGood() ? true : Status::wrap( $p )->getHTML(
false,
false, $language );
475 public function filter( $value, $alldata ) {
476 if ( $this->mFilterCallback !==
null ) {
477 $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
504 $this->mShowEmptyLabels = $show;
522 || $request->
getCheck(
'wpFormIdentifier' );
534 if ( $request->getCheck( $this->mName ) ) {
535 return $request->getText( $this->mName );
553 $this->mParent =
$params[
'parent'];
559 __METHOD__ .
": Constructing an HTMLFormField without a 'parent' parameter",
564 # Generate the label from a message, if possible
565 if ( isset(
$params[
'label-message'] ) ) {
567 } elseif ( isset(
$params[
'label'] ) ) {
568 if (
$params[
'label'] ===
' ' ||
$params[
'label'] ===
"\u{00A0}" ) {
570 $this->mLabel =
"\u{00A0}";
572 $this->mLabel = htmlspecialchars(
$params[
'label'] );
574 } elseif ( isset(
$params[
'label-raw'] ) ) {
575 $this->mLabel =
$params[
'label-raw'];
580 if ( isset(
$params[
'dir'] ) ) {
584 $this->mID =
"mw-input-{$this->mName}";
586 if ( isset(
$params[
'default'] ) ) {
587 $this->mDefault =
$params[
'default'];
590 if ( isset(
$params[
'id'] ) ) {
594 if ( isset(
$params[
'cssclass'] ) ) {
595 $this->mClass =
$params[
'cssclass'];
598 if ( isset(
$params[
'csshelpclass'] ) ) {
599 $this->mHelpClass =
$params[
'csshelpclass'];
602 if ( isset(
$params[
'validation-callback'] ) ) {
603 $this->mValidationCallback =
$params[
'validation-callback'];
606 if ( isset(
$params[
'filter-callback'] ) ) {
607 $this->mFilterCallback =
$params[
'filter-callback'];
610 if ( isset(
$params[
'hidelabel'] ) ) {
611 $this->mShowEmptyLabels =
false;
613 if ( isset(
$params[
'notices'] ) ) {
614 $this->mNotices =
$params[
'notices'];
619 $this->mCondState[
'hide'] =
$params[
'hide-if'];
620 $this->mCondStateClass[] =
'mw-htmlform-hide-if';
626 $this->mCondState[
'disable'] =
$params[
'disable-if'];
627 $this->mCondStateClass[] =
'mw-htmlform-disable-if';
645 $cellAttributes = [];
649 if ( !empty( $this->mParams[
'vertical-label'] ) ) {
650 $cellAttributes[
'colspan'] = 2;
651 $verticalLabel =
true;
653 $verticalLabel =
false;
658 $field = Html::rawElement(
660 [
'class' =>
'mw-input' ] + $cellAttributes,
661 $inputHtml .
"\n$errors"
664 if ( $this->mCondState ) {
666 $rowClasses .= implode(
' ', $this->mCondStateClass );
667 if ( $this->
isHidden( $this->mParent->mFieldData ) ) {
668 $rowClasses .=
' mw-htmlform-hide-if-hidden';
672 if ( $verticalLabel ) {
673 $html = Html::rawElement(
'tr',
674 $rowAttributes + [
'class' =>
"mw-htmlform-vertical-label $rowClasses" ], $label );
675 $html .= Html::rawElement(
'tr',
677 'class' =>
"mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
681 $html = Html::rawElement(
'tr',
683 'class' =>
"mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
688 return $html . $helptext;
706 $cellAttributes = [];
711 'mw-htmlform-nolabel' => ( $label ===
'' )
714 $horizontalLabel = $this->mParams[
'horizontal-label'] ??
false;
716 if ( $horizontalLabel ) {
717 $field =
"\u{00A0}" . $inputHtml .
"\n$errors";
719 $field = Html::rawElement(
722 [
'class' => $outerDivClass ] + $cellAttributes,
723 $inputHtml .
"\n$errors"
727 $wrapperAttributes = [
'class' => [
728 "mw-htmlform-field-$fieldType",
733 if ( $this->mCondState ) {
735 $wrapperAttributes[
'class'] = array_merge( $wrapperAttributes[
'class'], $this->mCondStateClass );
736 if ( $this->
isHidden( $this->mParent->mFieldData ) ) {
737 $wrapperAttributes[
'class'][] =
'mw-htmlform-hide-if-hidden';
740 return Html::rawElement(
'div', $wrapperAttributes, $label . $field ) .
756 if ( !$inputField ) {
761 new \OOUI\Widget( [
'content' =>
new \OOUI\HtmlSnippet( $this->
getDiv( $value ) ) ] ),
767 if ( is_string( $inputField ) ) {
771 $inputField = new \OOUI\Widget( [
'content' =>
new \OOUI\HtmlSnippet( $inputField ) ] );
778 foreach ( $errors as &$error ) {
779 $error = new \OOUI\HtmlSnippet( $error );
783 'classes' => [
"mw-htmlform-field-$fieldType" ],
785 'help' => ( $help !==
null && $help !==
'' ) ?
new \OOUI\HtmlSnippet( $help ) :
null,
787 'infusable' => $infusable,
789 'notices' => $this->mNotices ?: [],
791 if ( $this->mClass !==
'' ) {
795 $preloadModules =
false;
798 $preloadModules =
true;
799 $config[
'classes'][] =
'mw-htmlform-autoinfuse';
801 if ( $this->mCondState ) {
802 $config[
'classes'] = array_merge( $config[
'classes'], $this->mCondStateClass );
803 if ( $this->
isHidden( $this->mParent->mFieldData ) ) {
804 $config[
'classes'][] =
'mw-htmlform-hide-if-hidden';
810 if ( $label && $label !==
"\u{00A0}" && $label !==
' ' ) {
811 $config[
'label'] = new \OOUI\HtmlSnippet( $label );
814 if ( $this->mCondState ) {
815 $preloadModules =
true;
821 if ( $preloadModules ) {
822 $this->mParent->getOutput()->addModules(
'mediawiki.htmlform.ooui' );
823 $this->mParent->getOutput()->addModules( $this->
getOOUIModules() );
837 $isDisabled = ( $this->mParams[
'disabled'] ?? false );
841 $labelValue = trim( $this->
getLabel() );
844 if ( $labelValue !==
'' && $labelValue !==
"\u{00A0}" && $labelValue !==
' ' ) {
846 $labelClasses = [
'cdx-label' ];
848 $labelClasses[] =
'cdx-label--disabled';
851 $labelDiv = Html::rawElement(
'div', [
'class' => $labelClasses ],
853 Html::rawElement(
'label', [
'class' =>
'cdx-label__label' ] + $labelFor,
855 Html::rawElement(
'span', [
'class' =>
'cdx-label__label__text' ],
870 $validationMessage =
'';
872 if ( $errors !==
'' ) {
873 $validationMessage = Html::rawElement(
'div', [
'class' =>
'cdx-field__validation-message' ],
881 $controlClasses = [
'cdx-field__control' ];
883 $controlClasses[] =
'cdx-field__control--has-help-text';
885 $control = Html::rawElement(
'div', [
'class' => $controlClasses ], $inputHtml );
889 "mw-htmlform-field-{$this->getClassName()}",
895 $fieldClasses[] =
'cdx-field--disabled';
897 $fieldAttributes = [];
899 if ( $this->mCondState ) {
901 $fieldClasses = array_merge( $fieldClasses, $this->mCondStateClass );
902 if ( $this->
isHidden( $this->mParent->mFieldData ) ) {
903 $fieldClasses[] =
'mw-htmlform-hide-if-hidden';
907 return Html::rawElement(
'div', [
'class' => $fieldClasses ] + $fieldAttributes,
908 $labelDiv . $control . $helptext . $validationMessage
920 $name = explode(
'\\', static::class );
953 return !$this->
isHelpInline() && $this->getHelpMessages();
979 return "\n" . $errors .
996 $this->mVFormClass =
' mw-ui-vform-field';
997 return $this->
getDiv( $value );
1009 return "\n" . $errors .
1024 if ( $helptext ===
null ) {
1028 $rowAttributes = [];
1029 if ( $this->mCondState ) {
1034 $tdClasses = [
'htmlform-tip' ];
1035 if ( $this->mHelpClass !==
false ) {
1038 return Html::rawElement(
'tr', $rowAttributes,
1039 Html::rawElement(
'td', [
'colspan' => 2,
'class' => $tdClasses ], $helptext )
1053 if ( $helptext ===
null ) {
1057 $wrapperAttributes = [
1058 'class' => array_merge( $cssClasses, [
'htmlform-tip' ] ),
1060 if ( $this->mHelpClass !==
false ) {
1063 if ( $this->mCondState ) {
1065 $wrapperAttributes[
'class'] = array_merge( $wrapperAttributes[
'class'], $this->mCondStateClass );
1067 return Html::rawElement(
'div', $wrapperAttributes, $helptext );
1081 private function getHelpMessages(): array {
1082 if ( isset( $this->mParams[
'help-message'] ) ) {
1083 return [ $this->mParams[
'help-message'] ];
1084 } elseif ( isset( $this->mParams[
'help-messages'] ) ) {
1085 return $this->mParams[
'help-messages'];
1086 } elseif ( isset( $this->mParams[
'help-raw'] ) ) {
1087 return [
new HtmlArmor( $this->mParams[
'help-raw'] ) ];
1088 } elseif ( isset( $this->mParams[
'help'] ) ) {
1090 return [
new HtmlArmor( $this->mParams[
'help'] ) ];
1105 foreach ( $this->getHelpMessages() as $msg ) {
1107 $html[] = HtmlArmor::getHtml( $msg );
1109 $msg = $this->getMessage( $msg );
1110 if ( $msg->exists() ) {
1111 $html[] = $msg->parse();
1116 return $html ? implode( $this->msg(
'word-separator' )->escaped(), $html ) :
null;
1128 return $this->mParams[
'help-inline'] ??
true;
1144 $errors = $this->validate( $value, $this->mParent->mFieldData );
1146 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1150 return [ self::formatErrors( $errors ),
'mw-htmlform-invalid-input' ];
1161 $errors = $this->validate( $value, $this->mParent->mFieldData );
1163 if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1167 if ( !is_array( $errors ) ) {
1168 $errors = [ $errors ];
1170 foreach ( $errors as &$error ) {
1171 if ( $error instanceof
Message ) {
1172 $error = $error->parse();
1184 return $this->mLabel ??
'';
1194 # Don't output a for= attribute for labels with no associated input.
1195 # Kind of hacky here, possibly we don't want these to be <label>s at all.
1196 $for = $this->needsLabel() ? [
'for' => $this->mID ] : [];
1198 $labelValue = trim( $this->getLabel() );
1199 $hasLabel = $labelValue !==
'' && $labelValue !==
"\u{00A0}" && $labelValue !==
' ';
1201 $displayFormat = $this->mParent->getDisplayFormat();
1202 $horizontalLabel = $this->mParams[
'horizontal-label'] ??
false;
1204 if ( $displayFormat ===
'table' ) {
1205 return Html::rawElement(
'td',
1206 [
'class' =>
'mw-label' ] + $cellAttributes,
1207 Html::rawElement(
'label', $for, $labelValue ) );
1208 } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
1209 if ( $displayFormat ===
'div' && !$horizontalLabel ) {
1210 return Html::rawElement(
'div',
1211 [
'class' =>
'mw-label' ] + $cellAttributes,
1212 Html::rawElement(
'label', $for, $labelValue ) );
1214 return Html::rawElement(
'label', $for, $labelValue );
1226 return $this->mDefault ??
null;
1235 if ( empty( $this->mParams[
'tooltip'] ) ) {
1239 return Linker::tooltipAndAccesskeyAttribs( $this->mParams[
'tooltip'] );
1248 if ( empty( $this->mParams[
'tooltip'] ) ) {
1253 'title' => Linker::titleAttrib( $this->mParams[
'tooltip'] ),
1254 'accessKey' => Linker::accesskey( $this->mParams[
'tooltip'] ),
1266 static $boolAttribs = [
'disabled',
'required',
'autofocus',
'multiple',
'readonly' ];
1269 foreach ( $list as $key ) {
1270 if ( in_array( $key, $boolAttribs ) ) {
1271 if ( !empty( $this->mParams[$key] ) ) {
1274 } elseif ( isset( $this->mParams[$key] ) ) {
1275 $ret[$key] = $this->mParams[$key];
1291 private function lookupOptionsKeys( $options, $needsParse ) {
1293 foreach ( $options as $key => $value ) {
1294 $msg = $this->msg( $key );
1295 $msgAsText = $needsParse ? $msg->parse() : $msg->plain();
1296 if ( array_key_exists( $msgAsText, $ret ) ) {
1297 LoggerFactory::getInstance(
'translation-problem' )->error(
1298 'The option that uses the message key {msg_key_one} has the same translation as ' .
1299 'another option in {lang}. This means that {msg_key_one} will not be used as an option.',
1301 'msg_key_one' => $key,
1302 'lang' => $this->mParent ?
1303 $this->mParent->getLanguageCode()->toBcp47Code() :
1309 $ret[$msgAsText] = is_array( $value )
1310 ? $this->lookupOptionsKeys( $value, $needsParse )
1324 if ( is_array( $array ) ) {
1325 return array_map( [ __CLASS__,
'forceToStringRecursive' ], $array );
1327 return strval( $array );
1338 if ( $this->mOptions ===
false ) {
1339 if ( array_key_exists(
'options-messages', $this->mParams ) ) {
1340 $needsParse = $this->mParams[
'options-messages-parse'] ??
false;
1341 if ( $needsParse ) {
1342 $this->mOptionsLabelsNotFromMessage =
true;
1344 $this->mOptions = $this->lookupOptionsKeys( $this->mParams[
'options-messages'], $needsParse );
1345 } elseif ( array_key_exists(
'options', $this->mParams ) ) {
1346 $this->mOptionsLabelsNotFromMessage =
true;
1347 $this->mOptions = self::forceToStringRecursive( $this->mParams[
'options'] );
1348 } elseif ( array_key_exists(
'options-message', $this->mParams ) ) {
1349 $message = $this->getMessage( $this->mParams[
'options-message'] )->inContentLanguage()->plain();
1350 $this->mOptions = Html::listDropdownOptions( $message );
1352 $this->mOptions =
null;
1356 return $this->mOptions;
1365 $oldoptions = $this->getOptions();
1367 if ( $oldoptions ===
null ) {
1371 return Html::listDropdownOptionsOoui( $oldoptions );
1384 foreach ( $options as $value ) {
1385 if ( is_array( $value ) ) {
1386 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1388 $flatOpts[] = $value;
1409 if ( is_array( $errors ) && count( $errors ) === 1 ) {
1410 $errors = array_shift( $errors );
1413 if ( is_array( $errors ) ) {
1414 foreach ( $errors as &$error ) {
1415 $error = Html::rawElement(
'li', [],
1419 $errors = Html::rawElement(
'ul', [], implode(
"\n", $errors ) );
1420 } elseif ( $errors instanceof
Message ) {
1421 $errors = $errors->parse();
1424 return Html::errorBox( $errors );
1436 if ( $this->mParent ) {
1437 $message->setContext( $this->mParent );
1451 return !empty( $this->mParams[
'nodata'] );
1463 return (
bool)$this->mCondState;
1468class_alias( HTMLFormField::class,
'HTMLFormField' );
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.
array $params
The job parameters.
if(!defined('MW_SETUP_CALLBACK'))
Marks HTML that shouldn't be escaped.
Group all the pieces relevant to the context of a request into one instance.
Generic operation result class Has warning/error list, boolean status and arbitrary value.