MediaWiki fundraising/REL1_35
HTMLForm.php
Go to the documentation of this file.
1<?php
2
24use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
25
135class HTMLForm extends ContextSource {
136 use ProtectedHookAccessorTrait;
137
138 // A mapping of 'type' inputs onto standard HTMLFormField subclasses
139 public static $typeMappings = [
140 'api' => HTMLApiField::class,
141 'text' => HTMLTextField::class,
142 'textwithbutton' => HTMLTextFieldWithButton::class,
143 'textarea' => HTMLTextAreaField::class,
144 'select' => HTMLSelectField::class,
145 'combobox' => HTMLComboboxField::class,
146 'radio' => HTMLRadioField::class,
147 'multiselect' => HTMLMultiSelectField::class,
148 'limitselect' => HTMLSelectLimitField::class,
149 'check' => HTMLCheckField::class,
150 'toggle' => HTMLCheckField::class,
151 'int' => HTMLIntField::class,
152 'float' => HTMLFloatField::class,
153 'info' => HTMLInfoField::class,
154 'selectorother' => HTMLSelectOrOtherField::class,
155 'selectandother' => HTMLSelectAndOtherField::class,
156 'namespaceselect' => HTMLSelectNamespace::class,
157 'namespaceselectwithbutton' => HTMLSelectNamespaceWithButton::class,
158 'tagfilter' => HTMLTagFilter::class,
159 'sizefilter' => HTMLSizeFilterField::class,
160 'submit' => HTMLSubmitField::class,
161 'hidden' => HTMLHiddenField::class,
162 'edittools' => HTMLEditTools::class,
163 'checkmatrix' => HTMLCheckMatrix::class,
164 'cloner' => HTMLFormFieldCloner::class,
165 'autocompleteselect' => HTMLAutoCompleteSelectField::class,
166 'language' => HTMLSelectLanguageField::class,
167 'date' => HTMLDateTimeField::class,
168 'time' => HTMLDateTimeField::class,
169 'datetime' => HTMLDateTimeField::class,
170 'expiry' => HTMLExpiryField::class,
171 // HTMLTextField will output the correct type="" attribute automagically.
172 // There are about four zillion other HTML5 input types, like range, but
173 // we don't use those at the moment, so no point in adding all of them.
174 'email' => HTMLTextField::class,
175 'password' => HTMLTextField::class,
176 'url' => HTMLTextField::class,
177 'title' => HTMLTitleTextField::class,
178 'user' => HTMLUserTextField::class,
179 'usersmultiselect' => HTMLUsersMultiselectField::class,
180 'titlesmultiselect' => HTMLTitlesMultiselectField::class,
181 'namespacesmultiselect' => HTMLNamespacesMultiselectField::class,
182 ];
183
185
187
189 protected $mFlatFields = [];
190 protected $mFieldTree = [];
191 protected $mShowReset = false;
192 protected $mShowSubmit = true;
194 protected $mSubmitFlags = [ 'primary', 'progressive' ];
195 protected $mShowCancel = false;
196 protected $mCancelTarget;
197
200
201 protected $mPre = '';
202 protected $mHeader = '';
203 protected $mFooter = '';
204 protected $mSectionHeaders = [];
205 protected $mSectionFooters = [];
206 protected $mPost = '';
207 protected $mId;
208 protected $mName;
209 protected $mTableId = '';
210
211 protected $mSubmitID;
212 protected $mSubmitName;
213 protected $mSubmitText;
215
217 protected $mTitle;
218 protected $mMethod = 'post';
219 protected $mWasSubmitted = false;
220
226 protected $mAction = false;
227
233 protected $mCollapsible = false;
234
240 protected $mCollapsed = false;
241
247 protected $mAutocomplete = null;
248
249 protected $mUseMultipart = false;
254 protected $mHiddenFields = [];
259 protected $mButtons = [];
260
261 protected $mWrapperLegend = false;
262 protected $mWrapperAttributes = [];
263
268 protected $mTokenSalt = '';
269
277 protected $mSubSectionBeforeFields = true;
278
284 protected $displayFormat = 'table';
285
291 'table',
292 'div',
293 'raw',
294 'inline',
295 ];
296
302 'vform',
303 'ooui',
304 ];
305
315 public static function factory( $displayFormat, ...$arguments ) {
316 switch ( $displayFormat ) {
317 case 'vform':
318 return new VFormHTMLForm( ...$arguments );
319 case 'ooui':
320 return new OOUIHTMLForm( ...$arguments );
321 default:
322 $form = new self( ...$arguments );
323 $form->setDisplayFormat( $displayFormat );
324 return $form;
325 }
326 }
327
339 public function __construct( $descriptor, /*IContextSource*/ $context = null,
340 $messagePrefix = ''
341 ) {
342 if ( $context instanceof IContextSource ) {
343 $this->setContext( $context );
344 $this->mTitle = false; // We don't need them to set a title
345 $this->mMessagePrefix = $messagePrefix;
346 } elseif ( $context === null && $messagePrefix !== '' ) {
347 $this->mMessagePrefix = $messagePrefix;
348 } elseif ( is_string( $context ) && $messagePrefix === '' ) {
349 // B/C since 1.18
350 // it's actually $messagePrefix
351 $this->mMessagePrefix = $context;
352 }
353
354 // Evil hack for mobile :(
355 if (
356 !$this->getConfig()->get( 'HTMLFormAllowTableFormat' )
357 && $this->displayFormat === 'table'
358 ) {
359 $this->displayFormat = 'div';
360 }
361
362 $this->addFields( $descriptor );
363 }
364
374 public function addFields( $descriptor ) {
375 $loadedDescriptor = [];
376
377 foreach ( $descriptor as $fieldname => $info ) {
378
379 $section = $info['section'] ?? '';
380
381 if ( isset( $info['type'] ) && $info['type'] === 'file' ) {
382 $this->mUseMultipart = true;
383 }
384
385 $field = static::loadInputFromParameters( $fieldname, $info, $this );
386
387 $setSection =& $loadedDescriptor;
388 if ( $section ) {
389 foreach ( explode( '/', $section ) as $newName ) {
390 if ( !isset( $setSection[$newName] ) ) {
391 $setSection[$newName] = [];
392 }
393
394 $setSection =& $setSection[$newName];
395 }
396 }
397
398 $setSection[$fieldname] = $field;
399 $this->mFlatFields[$fieldname] = $field;
400 }
401
402 $this->mFieldTree = array_merge( $this->mFieldTree, $loadedDescriptor );
403
404 return $this;
405 }
406
411 public function hasField( $fieldname ) {
412 return isset( $this->mFlatFields[$fieldname] );
413 }
414
420 public function getField( $fieldname ) {
421 if ( !$this->hasField( $fieldname ) ) {
422 throw new DomainException( __METHOD__ . ': no field named ' . $fieldname );
423 }
424 return $this->mFlatFields[$fieldname];
425 }
426
437 public function setDisplayFormat( $format ) {
438 if (
439 in_array( $format, $this->availableSubclassDisplayFormats, true ) ||
440 in_array( $this->displayFormat, $this->availableSubclassDisplayFormats, true )
441 ) {
442 throw new MWException( 'Cannot change display format after creation, ' .
443 'use HTMLForm::factory() instead' );
444 }
445
446 if ( !in_array( $format, $this->availableDisplayFormats, true ) ) {
447 throw new MWException( 'Display format must be one of ' .
448 print_r(
449 array_merge(
450 $this->availableDisplayFormats,
451 $this->availableSubclassDisplayFormats
452 ),
453 true
454 ) );
455 }
456
457 // Evil hack for mobile :(
458 if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) {
459 $format = 'div';
460 }
461
462 $this->displayFormat = $format;
463
464 return $this;
465 }
466
472 public function getDisplayFormat() {
473 return $this->displayFormat;
474 }
475
493 public static function getClassFromDescriptor( $fieldname, &$descriptor ) {
494 if ( isset( $descriptor['class'] ) ) {
495 $class = $descriptor['class'];
496 } elseif ( isset( $descriptor['type'] ) ) {
497 $class = static::$typeMappings[$descriptor['type']];
498 $descriptor['class'] = $class;
499 } else {
500 $class = null;
501 }
502
503 if ( !$class ) {
504 throw new MWException( "Descriptor with no class for $fieldname: "
505 . print_r( $descriptor, true ) );
506 }
507
508 return $class;
509 }
510
523 public static function loadInputFromParameters( $fieldname, $descriptor,
524 HTMLForm $parent = null
525 ) {
526 $class = static::getClassFromDescriptor( $fieldname, $descriptor );
527
528 $descriptor['fieldname'] = $fieldname;
529 if ( $parent ) {
530 $descriptor['parent'] = $parent;
531 }
532
533 # @todo This will throw a fatal error whenever someone try to use
534 # 'class' to feed a CSS class instead of 'cssclass'. Would be
535 # great to avoid the fatal error and show a nice error.
536 return new $class( $descriptor );
537 }
538
548 public function prepareForm() {
549 # Check if we have the info we need
550 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
551 throw new MWException( 'You must call setTitle() on an HTMLForm' );
552 }
553
554 # Load data from the request.
555 if (
556 $this->mFormIdentifier === null ||
557 $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier
558 ) {
559 $this->loadData();
560 } else {
561 $this->mFieldData = [];
562 }
563
564 return $this;
565 }
566
571 public function tryAuthorizedSubmit() {
572 $result = false;
573
574 if ( $this->mFormIdentifier === null ) {
575 $identOkay = true;
576 } else {
577 $identOkay = $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier;
578 }
579
580 $tokenOkay = false;
581 if ( $this->getMethod() !== 'post' ) {
582 $tokenOkay = true; // no session check needed
583 } elseif ( $this->getRequest()->wasPosted() ) {
584 $editToken = $this->getRequest()->getVal( 'wpEditToken' );
585 if ( $this->getUser()->isLoggedIn() || $editToken !== null ) {
586 // Session tokens for logged-out users have no security value.
587 // However, if the user gave one, check it in order to give a nice
588 // "session expired" error instead of "permission denied" or such.
589 $tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
590 } else {
591 $tokenOkay = true;
592 }
593 }
594
595 if ( $tokenOkay && $identOkay ) {
596 $this->mWasSubmitted = true;
597 $result = $this->trySubmit();
598 }
599
600 return $result;
601 }
602
610 public function show() {
611 $this->prepareForm();
612
613 $result = $this->tryAuthorizedSubmit();
614 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
615 return $result;
616 }
617
618 $this->displayForm( $result );
619
620 return false;
621 }
622
628 public function showAlways() {
629 $this->prepareForm();
630
631 $result = $this->tryAuthorizedSubmit();
632
633 $this->displayForm( $result );
634
635 return $result;
636 }
637
650 public function trySubmit() {
651 $valid = true;
652 $hoistedErrors = Status::newGood();
653 if ( $this->mValidationErrorMessage ) {
654 foreach ( $this->mValidationErrorMessage as $error ) {
655 $hoistedErrors->fatal( ...$error );
656 }
657 } else {
658 $hoistedErrors->fatal( 'htmlform-invalid-input' );
659 }
660
661 $this->mWasSubmitted = true;
662
663 # Check for cancelled submission
664 foreach ( $this->mFlatFields as $fieldname => $field ) {
665 if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
666 continue;
667 }
668 if ( $field->cancelSubmit( $this->mFieldData[$fieldname], $this->mFieldData ) ) {
669 $this->mWasSubmitted = false;
670 return false;
671 }
672 }
673
674 # Check for validation
675 foreach ( $this->mFlatFields as $fieldname => $field ) {
676 if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
677 continue;
678 }
679 if ( $field->isHidden( $this->mFieldData ) ) {
680 continue;
681 }
682 $res = $field->validate( $this->mFieldData[$fieldname], $this->mFieldData );
683 if ( $res !== true ) {
684 $valid = false;
685 if ( $res !== false && !$field->canDisplayErrors() ) {
686 if ( is_string( $res ) ) {
687 $hoistedErrors->fatal( 'rawmessage', $res );
688 } else {
689 $hoistedErrors->fatal( $res );
690 }
691 }
692 }
693 }
694
695 if ( !$valid ) {
696 return $hoistedErrors;
697 }
698
699 $callback = $this->mSubmitCallback;
700 if ( !is_callable( $callback ) ) {
701 throw new MWException( 'HTMLForm: no submit callback provided. Use ' .
702 'setSubmitCallback() to set one.' );
703 }
704
705 $data = $this->filterDataForSubmit( $this->mFieldData );
706
707 $res = call_user_func( $callback, $data, $this );
708 if ( $res === false ) {
709 $this->mWasSubmitted = false;
710 }
711
712 return $res;
713 }
714
726 public function wasSubmitted() {
727 return $this->mWasSubmitted;
728 }
729
740 public function setSubmitCallback( $cb ) {
741 $this->mSubmitCallback = $cb;
742
743 return $this;
744 }
745
754 public function setValidationErrorMessage( $msg ) {
755 $this->mValidationErrorMessage = $msg;
756
757 return $this;
758 }
759
767 public function setIntro( $msg ) {
768 $this->setPreText( $msg );
769
770 return $this;
771 }
772
781 public function setPreText( $msg ) {
782 $this->mPre = $msg;
783
784 return $this;
785 }
786
794 public function addPreText( $msg ) {
795 $this->mPre .= $msg;
796
797 return $this;
798 }
799
807 public function getPreText() {
808 return $this->mPre;
809 }
810
819 public function addHeaderText( $msg, $section = null ) {
820 if ( $section === null ) {
821 $this->mHeader .= $msg;
822 } else {
823 if ( !isset( $this->mSectionHeaders[$section] ) ) {
824 $this->mSectionHeaders[$section] = '';
825 }
826 $this->mSectionHeaders[$section] .= $msg;
827 }
828
829 return $this;
830 }
831
841 public function setHeaderText( $msg, $section = null ) {
842 if ( $section === null ) {
843 $this->mHeader = $msg;
844 } else {
845 $this->mSectionHeaders[$section] = $msg;
846 }
847
848 return $this;
849 }
850
859 public function getHeaderText( $section = null ) {
860 if ( $section === null ) {
861 return $this->mHeader;
862 } else {
863 return $this->mSectionHeaders[$section] ?? '';
864 }
865 }
866
875 public function addFooterText( $msg, $section = null ) {
876 if ( $section === null ) {
877 $this->mFooter .= $msg;
878 } else {
879 if ( !isset( $this->mSectionFooters[$section] ) ) {
880 $this->mSectionFooters[$section] = '';
881 }
882 $this->mSectionFooters[$section] .= $msg;
883 }
884
885 return $this;
886 }
887
897 public function setFooterText( $msg, $section = null ) {
898 if ( $section === null ) {
899 $this->mFooter = $msg;
900 } else {
901 $this->mSectionFooters[$section] = $msg;
902 }
903
904 return $this;
905 }
906
914 public function getFooterText( $section = null ) {
915 if ( $section === null ) {
916 return $this->mFooter;
917 } else {
918 return $this->mSectionFooters[$section] ?? '';
919 }
920 }
921
929 public function addPostText( $msg ) {
930 $this->mPost .= $msg;
931
932 return $this;
933 }
934
942 public function setPostText( $msg ) {
943 $this->mPost = $msg;
944
945 return $this;
946 }
947
957 public function addHiddenField( $name, $value, array $attribs = [] ) {
958 $attribs += [ 'name' => $name ];
959 $this->mHiddenFields[] = [ $value, $attribs ];
960
961 return $this;
962 }
963
974 public function addHiddenFields( array $fields ) {
975 foreach ( $fields as $name => $value ) {
976 $this->mHiddenFields[] = [ $value, [ 'name' => $name ] ];
977 }
978
979 return $this;
980 }
981
1006 public function addButton( $data ) {
1007 if ( !is_array( $data ) ) {
1008 $args = func_get_args();
1009 if ( count( $args ) < 2 || count( $args ) > 4 ) {
1010 throw new InvalidArgumentException(
1011 'Incorrect number of arguments for deprecated calling style'
1012 );
1013 }
1014 $data = [
1015 'name' => $args[0],
1016 'value' => $args[1],
1017 'id' => $args[2] ?? null,
1018 'attribs' => $args[3] ?? null,
1019 ];
1020 } else {
1021 if ( !isset( $data['name'] ) ) {
1022 throw new InvalidArgumentException( 'A name is required' );
1023 }
1024 if ( !isset( $data['value'] ) ) {
1025 throw new InvalidArgumentException( 'A value is required' );
1026 }
1027 }
1028 $this->mButtons[] = $data + [
1029 'id' => null,
1030 'attribs' => null,
1031 'flags' => null,
1032 'framed' => true,
1033 ];
1034
1035 return $this;
1036 }
1037
1047 public function setTokenSalt( $salt ) {
1048 $this->mTokenSalt = $salt;
1049
1050 return $this;
1051 }
1052
1067 public function displayForm( $submitResult ) {
1068 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
1069 }
1070
1081 public function getHTML( $submitResult ) {
1082 # For good measure (it is the default)
1083 $this->getOutput()->preventClickjacking();
1084 $this->getOutput()->addModules( 'mediawiki.htmlform' );
1085 $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' );
1086
1087 $html = ''
1088 . $this->getErrorsOrWarnings( $submitResult, 'error' )
1089 . $this->getErrorsOrWarnings( $submitResult, 'warning' )
1090 . $this->getHeaderText()
1091 . $this->getBody()
1092 . $this->getHiddenFields()
1093 . $this->getButtons()
1094 . $this->getFooterText();
1095
1096 $html = $this->wrapForm( $html );
1097
1098 return '' . $this->mPre . $html . $this->mPost;
1099 }
1100
1108 public function setCollapsibleOptions( $collapsedByDefault = false ) {
1109 $this->mCollapsible = true;
1110 $this->mCollapsed = $collapsedByDefault;
1111 return $this;
1112 }
1113
1119 protected function getFormAttributes() {
1120 # Use multipart/form-data
1121 $encType = $this->mUseMultipart
1122 ? 'multipart/form-data'
1123 : 'application/x-www-form-urlencoded';
1124 # Attributes
1125 $attribs = [
1126 'class' => 'mw-htmlform',
1127 'action' => $this->getAction(),
1128 'method' => $this->getMethod(),
1129 'enctype' => $encType,
1130 ];
1131 if ( $this->mId ) {
1132 $attribs['id'] = $this->mId;
1133 }
1134 if ( is_string( $this->mAutocomplete ) ) {
1135 $attribs['autocomplete'] = $this->mAutocomplete;
1136 }
1137 if ( $this->mName ) {
1138 $attribs['name'] = $this->mName;
1139 }
1140 if ( $this->needsJSForHtml5FormValidation() ) {
1141 $attribs['novalidate'] = true;
1142 }
1143 return $attribs;
1144 }
1145
1154 public function wrapForm( $html ) {
1155 # Include a <fieldset> wrapper for style, if requested.
1156 if ( $this->mWrapperLegend !== false ) {
1157 $legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false;
1158 $html = Xml::fieldset( $legend, $html, $this->mWrapperAttributes );
1159 }
1160
1161 return Html::rawElement(
1162 'form',
1163 $this->getFormAttributes(),
1164 $html
1165 );
1166 }
1167
1172 public function getHiddenFields() {
1173 $html = '';
1174 if ( $this->mFormIdentifier !== null ) {
1175 $html .= Html::hidden(
1176 'wpFormIdentifier',
1177 $this->mFormIdentifier
1178 ) . "\n";
1179 }
1180 if ( $this->getMethod() === 'post' ) {
1181 $html .= Html::hidden(
1182 'wpEditToken',
1183 $this->getUser()->getEditToken( $this->mTokenSalt ),
1184 [ 'id' => 'wpEditToken' ]
1185 ) . "\n";
1186 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
1187 }
1188
1189 $articlePath = $this->getConfig()->get( 'ArticlePath' );
1190 if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
1191 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
1192 }
1193
1194 foreach ( $this->mHiddenFields as $data ) {
1195 list( $value, $attribs ) = $data;
1196 $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
1197 }
1198
1199 return $html;
1200 }
1201
1207 public function getButtons() {
1208 $buttons = '';
1209 $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' );
1210
1211 if ( $this->mShowSubmit ) {
1212 $attribs = [];
1213
1214 if ( isset( $this->mSubmitID ) ) {
1215 $attribs['id'] = $this->mSubmitID;
1216 }
1217
1218 if ( isset( $this->mSubmitName ) ) {
1219 $attribs['name'] = $this->mSubmitName;
1220 }
1221
1222 if ( isset( $this->mSubmitTooltip ) ) {
1223 $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
1224 }
1225
1226 $attribs['class'] = [ 'mw-htmlform-submit' ];
1227
1228 if ( $useMediaWikiUIEverywhere ) {
1229 foreach ( $this->mSubmitFlags as $flag ) {
1230 $attribs['class'][] = 'mw-ui-' . $flag;
1231 }
1232 $attribs['class'][] = 'mw-ui-button';
1233 }
1234
1235 $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
1236 }
1237
1238 if ( $this->mShowReset ) {
1239 $buttons .= Html::element(
1240 'input',
1241 [
1242 'type' => 'reset',
1243 'value' => $this->msg( 'htmlform-reset' )->text(),
1244 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
1245 ]
1246 ) . "\n";
1247 }
1248
1249 if ( $this->mShowCancel ) {
1250 $target = $this->mCancelTarget ?: Title::newMainPage();
1251 if ( $target instanceof Title ) {
1252 $target = $target->getLocalURL();
1253 }
1254 $buttons .= Html::element(
1255 'a',
1256 [
1257 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
1258 'href' => $target,
1259 ],
1260 $this->msg( 'cancel' )->text()
1261 ) . "\n";
1262 }
1263
1264 foreach ( $this->mButtons as $button ) {
1265 $attrs = [
1266 'type' => 'submit',
1267 'name' => $button['name'],
1268 'value' => $button['value']
1269 ];
1270
1271 if ( isset( $button['label-message'] ) ) {
1272 $label = $this->getMessage( $button['label-message'] )->parse();
1273 } elseif ( isset( $button['label'] ) ) {
1274 $label = htmlspecialchars( $button['label'] );
1275 } elseif ( isset( $button['label-raw'] ) ) {
1276 $label = $button['label-raw'];
1277 } else {
1278 $label = htmlspecialchars( $button['value'] );
1279 }
1280
1281 if ( $button['attribs'] ) {
1282 $attrs += $button['attribs'];
1283 }
1284
1285 if ( isset( $button['id'] ) ) {
1286 $attrs['id'] = $button['id'];
1287 }
1288
1289 if ( $useMediaWikiUIEverywhere ) {
1290 $attrs['class'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : [];
1291 $attrs['class'][] = 'mw-ui-button';
1292 }
1293
1294 $buttons .= Html::rawElement( 'button', $attrs, $label ) . "\n";
1295 }
1296
1297 if ( !$buttons ) {
1298 return '';
1299 }
1300
1301 return Html::rawElement( 'span',
1302 [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
1303 }
1304
1310 public function getBody() {
1311 return $this->displaySection( $this->mFieldTree, $this->mTableId );
1312 }
1313
1323 public function getErrorsOrWarnings( $elements, $elementsType ) {
1324 if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
1325 throw new DomainException( $elementsType . ' is not a valid type.' );
1326 }
1327 $elementstr = false;
1328 if ( $elements instanceof Status ) {
1329 list( $errorStatus, $warningStatus ) = $elements->splitByErrorType();
1330 $status = $elementsType === 'error' ? $errorStatus : $warningStatus;
1331 if ( $status->isGood() ) {
1332 $elementstr = '';
1333 } else {
1334 $elementstr = $status
1335 ->getMessage()
1336 ->setContext( $this )
1337 ->setInterfaceMessageFlag( true )
1338 ->parse();
1339 }
1340 } elseif ( is_array( $elements ) && $elementsType === 'error' ) {
1341 $elementstr = $this->formatErrors( $elements );
1342 } elseif ( $elementsType === 'error' ) {
1343 $elementstr = $elements;
1344 }
1345
1346 return $elementstr
1347 ? Html::rawElement( 'div', [ 'class' => $elementsType . 'box' ], $elementstr )
1348 : '';
1349 }
1350
1358 public function formatErrors( $errors ) {
1359 $errorstr = '';
1360
1361 foreach ( $errors as $error ) {
1362 $errorstr .= Html::rawElement(
1363 'li',
1364 [],
1365 $this->getMessage( $error )->parse()
1366 );
1367 }
1368
1369 $errorstr = Html::rawElement( 'ul', [], $errorstr );
1370
1371 return $errorstr;
1372 }
1373
1381 public function setSubmitText( $t ) {
1382 $this->mSubmitText = $t;
1383
1384 return $this;
1385 }
1386
1393 public function setSubmitDestructive() {
1394 $this->mSubmitFlags = [ 'destructive', 'primary' ];
1395
1396 return $this;
1397 }
1398
1407 public function setSubmitTextMsg( $msg ) {
1408 if ( !$msg instanceof Message ) {
1409 $msg = $this->msg( $msg );
1410 }
1411 $this->setSubmitText( $msg->text() );
1412
1413 return $this;
1414 }
1415
1420 public function getSubmitText() {
1421 return $this->mSubmitText ?: $this->msg( 'htmlform-submit' )->text();
1422 }
1423
1429 public function setSubmitName( $name ) {
1430 $this->mSubmitName = $name;
1431
1432 return $this;
1433 }
1434
1440 public function setSubmitTooltip( $name ) {
1441 $this->mSubmitTooltip = $name;
1442
1443 return $this;
1444 }
1445
1454 public function setSubmitID( $t ) {
1455 $this->mSubmitID = $t;
1456
1457 return $this;
1458 }
1459
1475 public function setFormIdentifier( $ident ) {
1476 $this->mFormIdentifier = $ident;
1477
1478 return $this;
1479 }
1480
1491 public function suppressDefaultSubmit( $suppressSubmit = true ) {
1492 $this->mShowSubmit = !$suppressSubmit;
1493
1494 return $this;
1495 }
1496
1503 public function showCancel( $show = true ) {
1504 $this->mShowCancel = $show;
1505 return $this;
1506 }
1507
1514 public function setCancelTarget( $target ) {
1515 $this->mCancelTarget = $target;
1516 return $this;
1517 }
1518
1528 public function setTableId( $id ) {
1529 $this->mTableId = $id;
1530
1531 return $this;
1532 }
1533
1539 public function setId( $id ) {
1540 $this->mId = $id;
1541
1542 return $this;
1543 }
1544
1549 public function setName( $name ) {
1550 $this->mName = $name;
1551
1552 return $this;
1553 }
1554
1566 public function setWrapperLegend( $legend ) {
1567 $this->mWrapperLegend = $legend;
1568
1569 return $this;
1570 }
1571
1579 public function setWrapperAttributes( $attributes ) {
1580 $this->mWrapperAttributes = $attributes;
1581
1582 return $this;
1583 }
1584
1594 public function setWrapperLegendMsg( $msg ) {
1595 if ( !$msg instanceof Message ) {
1596 $msg = $this->msg( $msg );
1597 }
1598 $this->setWrapperLegend( $msg->text() );
1599
1600 return $this;
1601 }
1602
1612 public function setMessagePrefix( $p ) {
1613 $this->mMessagePrefix = $p;
1614
1615 return $this;
1616 }
1617
1625 public function setTitle( $t ) {
1626 $this->mTitle = $t;
1627
1628 return $this;
1629 }
1630
1635 public function getTitle() {
1636 return $this->mTitle === false
1637 ? $this->getContext()->getTitle()
1638 : $this->mTitle;
1639 }
1640
1648 public function setMethod( $method = 'post' ) {
1649 $this->mMethod = strtolower( $method );
1650
1651 return $this;
1652 }
1653
1657 public function getMethod() {
1658 return $this->mMethod;
1659 }
1660
1671 protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
1672 return Xml::fieldset( $legend, $section, $attributes ) . "\n";
1673 }
1674
1692 public function displaySection( $fields,
1693 $sectionName = '',
1694 $fieldsetIDPrefix = '',
1695 &$hasUserVisibleFields = false
1696 ) {
1697 if ( $this->mFieldData === null ) {
1698 throw new LogicException( 'HTMLForm::displaySection() called on uninitialized field data. '
1699 . 'You probably called displayForm() without calling prepareForm() first.' );
1700 }
1701
1703
1704 $html = [];
1705 $subsectionHtml = '';
1706 $hasLabel = false;
1707
1708 // Conveniently, PHP method names are case-insensitive.
1709 // For grep: this can call getDiv, getRaw, getInline, getVForm, getOOUI
1710 $getFieldHtmlMethod = $displayFormat === 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
1711
1712 foreach ( $fields as $key => $value ) {
1713 if ( $value instanceof HTMLFormField ) {
1714 $v = array_key_exists( $key, $this->mFieldData )
1715 ? $this->mFieldData[$key]
1716 : $value->getDefault();
1717
1718 $retval = $value->$getFieldHtmlMethod( $v );
1719
1720 // check, if the form field should be added to
1721 // the output.
1722 if ( $value->hasVisibleOutput() ) {
1723 $html[] = $retval;
1724
1725 $labelValue = trim( $value->getLabel() );
1726 if ( $labelValue !== "\u{00A0}" && $labelValue !== '&#160;' && $labelValue !== '' ) {
1727 $hasLabel = true;
1728 }
1729
1730 $hasUserVisibleFields = true;
1731 }
1732 } elseif ( is_array( $value ) ) {
1733 $subsectionHasVisibleFields = false;
1734 $section =
1735 $this->displaySection( $value,
1736 "mw-htmlform-$key",
1737 "$fieldsetIDPrefix$key-",
1738 $subsectionHasVisibleFields );
1739 $legend = null;
1740
1741 if ( $subsectionHasVisibleFields === true ) {
1742 // Display the section with various niceties.
1743 $hasUserVisibleFields = true;
1744
1745 $legend = $this->getLegend( $key );
1746
1747 $section = $this->getHeaderText( $key ) .
1748 $section .
1749 $this->getFooterText( $key );
1750
1751 $attributes = [];
1752 if ( $fieldsetIDPrefix ) {
1753 $attributes['id'] = Sanitizer::escapeIdForAttribute( "$fieldsetIDPrefix$key" );
1754 }
1755 $subsectionHtml .= $this->wrapFieldSetSection(
1756 $legend, $section, $attributes, $fields === $this->mFieldTree
1757 );
1758 } else {
1759 // Just return the inputs, nothing fancy.
1760 $subsectionHtml .= $section;
1761 }
1762 }
1763 }
1764
1765 $html = $this->formatSection( $html, $sectionName, $hasLabel );
1766
1767 if ( $subsectionHtml ) {
1768 if ( $this->mSubSectionBeforeFields ) {
1769 return $subsectionHtml . "\n" . $html;
1770 } else {
1771 return $html . "\n" . $subsectionHtml;
1772 }
1773 } else {
1774 return $html;
1775 }
1776 }
1777
1786 protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
1787 if ( !$fieldsHtml ) {
1788 // Do not generate any wrappers for empty sections. Sections may be empty if they only have
1789 // subsections, but no fields. A legend will still be added in wrapFieldSetSection().
1790 return '';
1791 }
1792
1794 $html = implode( '', $fieldsHtml );
1795
1796 if ( $displayFormat === 'raw' ) {
1797 return $html;
1798 }
1799
1800 $classes = [];
1801
1802 if ( !$anyFieldHasLabel ) { // Avoid strange spacing when no labels exist
1803 $classes[] = 'mw-htmlform-nolabel';
1804 }
1805
1806 $attribs = [
1807 'class' => implode( ' ', $classes ),
1808 ];
1809
1810 if ( $sectionName ) {
1811 $attribs['id'] = Sanitizer::escapeIdForAttribute( $sectionName );
1812 }
1813
1814 if ( $displayFormat === 'table' ) {
1815 return Html::rawElement( 'table',
1816 $attribs,
1817 Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n";
1818 } elseif ( $displayFormat === 'inline' ) {
1819 return Html::rawElement( 'span', $attribs, "\n$html\n" );
1820 } else {
1821 return Html::rawElement( 'div', $attribs, "\n$html\n" );
1822 }
1823 }
1824
1828 public function loadData() {
1829 $fieldData = [];
1830
1831 foreach ( $this->mFlatFields as $fieldname => $field ) {
1832 $request = $this->getRequest();
1833 if ( $field->skipLoadData( $request ) ) {
1834 continue;
1835 } elseif ( !empty( $field->mParams['disabled'] ) ) {
1836 $fieldData[$fieldname] = $field->getDefault();
1837 } else {
1838 $fieldData[$fieldname] = $field->loadDataFromRequest( $request );
1839 }
1840 }
1841
1842 # Filter data.
1843 foreach ( $fieldData as $name => &$value ) {
1844 $field = $this->mFlatFields[$name];
1845 $value = $field->filter( $value, $this->mFlatFields );
1846 }
1847
1848 $this->mFieldData = $fieldData;
1849 }
1850
1858 public function suppressReset( $suppressReset = true ) {
1859 $this->mShowReset = !$suppressReset;
1860
1861 return $this;
1862 }
1863
1874 public function filterDataForSubmit( $data ) {
1875 return $data;
1876 }
1877
1887 public function getLegend( $key ) {
1888 return $this->msg( $this->mMessagePrefix ? "{$this->mMessagePrefix}-$key" : $key )->text();
1889 }
1890
1901 public function setAction( $action ) {
1902 $this->mAction = $action;
1903
1904 return $this;
1905 }
1906
1914 public function getAction() {
1915 // If an action is alredy provided, return it
1916 if ( $this->mAction !== false ) {
1917 return $this->mAction;
1918 }
1919
1920 $articlePath = $this->getConfig()->get( 'ArticlePath' );
1921 // Check whether we are in GET mode and the ArticlePath contains a "?"
1922 // meaning that getLocalURL() would return something like "index.php?title=...".
1923 // As browser remove the query string before submitting GET forms,
1924 // it means that the title would be lost. In such case use wfScript() instead
1925 // and put title in an hidden field (see getHiddenFields()).
1926 if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
1927 return wfScript();
1928 }
1929
1930 return $this->getTitle()->getLocalURL();
1931 }
1932
1943 public function setAutocomplete( $autocomplete ) {
1944 $this->mAutocomplete = $autocomplete;
1945
1946 return $this;
1947 }
1948
1955 protected function getMessage( $value ) {
1956 return Message::newFromSpecifier( $value )->setContext( $this );
1957 }
1958
1969 foreach ( $this->mFlatFields as $fieldname => $field ) {
1970 if ( $field->needsJSForHtml5FormValidation() ) {
1971 return true;
1972 }
1973 }
1974 return false;
1975 }
1976}
addFields( $fields)
getUser()
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
Title null $mTitle
getContext()
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
setContext(IContextSource $context)
The parent class to generate form fields.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition HTMLForm.php:135
needsJSForHtml5FormValidation()
Whether this form, with its current fields, requires the user agent to have JavaScript enabled for th...
setSubmitCallback( $cb)
Set a callback to a function to do something with the form once it's been successfully validated.
Definition HTMLForm.php:740
setHeaderText( $msg, $section=null)
Set header text, inside the form.
Definition HTMLForm.php:841
displayForm( $submitResult)
Display the form (sending to the context's OutputPage object), with an appropriate error message or s...
string null $mAutocomplete
Form attribute autocomplete.
Definition HTMLForm.php:247
bool string $mAction
Form action URL.
Definition HTMLForm.php:226
setAction( $action)
Set the value for the action attribute of the form.
string array $mTokenSalt
Salt for the edit token.
Definition HTMLForm.php:268
string[] $mSubmitFlags
Definition HTMLForm.php:194
getSubmitText()
Get the text for the submit button, either customised or a default.
setMethod( $method='post')
Set the method used to submit the form.
$mValidationErrorMessage
Definition HTMLForm.php:199
setValidationErrorMessage( $msg)
Set a message to display on a validation error.
Definition HTMLForm.php:754
getMessage( $value)
Turns a *-message parameter (which could be a MessageSpecifier, or a message name,...
setAutocomplete( $autocomplete)
Set the value for the autocomplete attribute of the form.
static loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent=null)
Initialise a new Object for the field Stable to override.
Definition HTMLForm.php:523
setSubmitName( $name)
setTitle( $t)
Set the title for form submission.
static getClassFromDescriptor( $fieldname, &$descriptor)
Get the HTMLFormField subclass for this descriptor.
Definition HTMLForm.php:493
array $availableSubclassDisplayFormats
Available formats in which to display the form.
Definition HTMLForm.php:301
string $displayFormat
Format in which to display form.
Definition HTMLForm.php:284
__construct( $descriptor, $context=null, $messagePrefix='')
Build a new HTMLForm from an array of field attributes.
Definition HTMLForm.php:339
setTableId( $id)
Set the id of the <table> or outermost <div> element.
getHTML( $submitResult)
Returns the raw HTML generated by the form.
getLegend( $key)
Get a string to go in the "<legend>" of a section fieldset.
setSubmitTextMsg( $msg)
Set the text for the submit button to a message.
wrapFieldSetSection( $legend, $section, $attributes, $isRoot)
Wraps the given $section into an user-visible fieldset.
filterDataForSubmit( $data)
Overload this if you want to apply special filtration routines to the form as a whole,...
setWrapperLegendMsg( $msg)
Prompt the whole form to be wrapped in a "<fieldset>", with this message as its "<legend>" element.
setId( $id)
setWrapperLegend( $legend)
Prompt the whole form to be wrapped in a "<fieldset>", with this text as its "<legend>" element.
formatErrors( $errors)
Format a stack of error messages into a single HTML string.
$mSubSectionBeforeFields
If true, sections that contain both fields and subsections will render their subsections before their...
Definition HTMLForm.php:277
setDisplayFormat( $format)
Set format in which to display the form.
Definition HTMLForm.php:437
addButton( $data)
Add a button to the form.
loadData()
Construct the form fields from the Descriptor array.
getPreText()
Get the introductory message HTML.
Definition HTMLForm.php:807
getHiddenFields()
Get the hidden fields that should go inside the form.
setSubmitDestructive()
Identify that the submit button in the form has a destructive action.
suppressDefaultSubmit( $suppressSubmit=true)
Stop a default submit button being shown for this form.
setSubmitID( $t)
Set the id for the submit button.
getDisplayFormat()
Getter for displayFormat.
Definition HTMLForm.php:472
getAction()
Get the value for the action attribute of the form.
show()
The here's-one-I-made-earlier option: do the submission if posted, or display the form with or withou...
Definition HTMLForm.php:610
hasField( $fieldname)
Definition HTMLForm.php:411
$mWrapperAttributes
Definition HTMLForm.php:262
addFooterText( $msg, $section=null)
Add footer text, inside the form.
Definition HTMLForm.php:875
addPostText( $msg)
Add text to the end of the display.
Definition HTMLForm.php:929
getFooterText( $section=null)
Get footer text.
Definition HTMLForm.php:914
addHeaderText( $msg, $section=null)
Add HTML to the header, inside the form.
Definition HTMLForm.php:819
setWrapperAttributes( $attributes)
For internal use only.
prepareForm()
Prepare form for submission.
Definition HTMLForm.php:548
setCollapsibleOptions( $collapsedByDefault=false)
Enable collapsible mode, and set whether the form is collapsed by default.
getTitle()
Get the title.
wasSubmitted()
Test whether the form was considered to have been submitted or not, i.e.
Definition HTMLForm.php:726
getHeaderText( $section=null)
Get header text.
Definition HTMLForm.php:859
array[] $mButtons
-var array<array{name:string,value:string,label-message?:string|string[]|MessageSpecifier,...
Definition HTMLForm.php:259
tryAuthorizedSubmit()
Try submitting, with edit token check first.
Definition HTMLForm.php:571
setSubmitTooltip( $name)
displaySection( $fields, $sectionName='', $fieldsetIDPrefix='', &$hasUserVisibleFields=false)
array[] $mHiddenFields
-var array<int,array{0:string,1:array}>
Definition HTMLForm.php:254
setPreText( $msg)
Set the introductory message HTML, overwriting any existing message.
Definition HTMLForm.php:781
bool $mCollapsible
Whether the form can be collapsed.
Definition HTMLForm.php:233
addHiddenField( $name, $value, array $attribs=[])
Add a hidden field to the output.
Definition HTMLForm.php:957
setFooterText( $msg, $section=null)
Set footer text, inside the form.
Definition HTMLForm.php:897
setMessagePrefix( $p)
Set the prefix for various default messages.
getField( $fieldname)
Definition HTMLForm.php:420
bool $mCollapsed
Whether the form is collapsed by default.
Definition HTMLForm.php:240
wrapForm( $html)
Wrap the form innards in an actual "<form>" element Stable to override.
getFormAttributes()
Get HTML attributes for the <form> tag.
getBody()
Get the whole body of the form.
showAlways()
Same as self::show with the difference, that the form will be added to the output,...
Definition HTMLForm.php:628
setTokenSalt( $salt)
Set the salt for the edit token.
addPreText( $msg)
Add HTML to introductory message.
Definition HTMLForm.php:794
setFormIdentifier( $ident)
Set an internal identifier for this form.
setName( $name)
suppressReset( $suppressReset=true)
Stop a reset button being shown for this form.
setPostText( $msg)
Set text at the end of the display.
Definition HTMLForm.php:942
static $typeMappings
Definition HTMLForm.php:139
formatSection(array $fieldsHtml, $sectionName, $anyFieldHasLabel)
Put a form section together from the individual fields' HTML, merging it and wrapping.
setCancelTarget( $target)
Sets the target where the user is redirected to after clicking cancel.
array $availableDisplayFormats
Available formats in which to display the form.
Definition HTMLForm.php:290
showCancel( $show=true)
Show a cancel button (or prevent it).
setSubmitText( $t)
Set the text for the submit button.
static factory( $displayFormat,... $arguments)
Construct a HTMLForm object for given display type.
Definition HTMLForm.php:315
setIntro( $msg)
Set the introductory message, overwriting any existing message.
Definition HTMLForm.php:767
getButtons()
Get the submit and (potentially) reset buttons.
trySubmit()
Validate all the fields, and call the submission callback function if everything is kosher.
Definition HTMLForm.php:650
HTMLFormField[] $mFlatFields
Definition HTMLForm.php:189
getErrorsOrWarnings( $elements, $elementsType)
Returns a formatted list of errors or warnings from the given elements.
addHiddenFields(array $fields)
Add an array of hidden fields to the output.
Definition HTMLForm.php:974
addFields( $descriptor)
Add fields to the form.
Definition HTMLForm.php:374
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2294
MediaWiki exception.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:161
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:452
Compact stacked vertical format for forms, implemented using OOUI widgets.
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Represents a title within MediaWiki.
Definition Title.php:42
Compact stacked vertical format for forms.
Interface for objects which can provide a MediaWiki context on request.
if( $line===false) $args
Definition mcc.php:124