MediaWiki REL1_34
HTMLForm.php
Go to the documentation of this file.
1<?php
2
131class HTMLForm extends ContextSource {
132 // A mapping of 'type' inputs onto standard HTMLFormField subclasses
133 public static $typeMappings = [
134 'api' => HTMLApiField::class,
135 'text' => HTMLTextField::class,
136 'textwithbutton' => HTMLTextFieldWithButton::class,
137 'textarea' => HTMLTextAreaField::class,
138 'select' => HTMLSelectField::class,
139 'combobox' => HTMLComboboxField::class,
140 'radio' => HTMLRadioField::class,
141 'multiselect' => HTMLMultiSelectField::class,
142 'limitselect' => HTMLSelectLimitField::class,
143 'check' => HTMLCheckField::class,
144 'toggle' => HTMLCheckField::class,
145 'int' => HTMLIntField::class,
146 'float' => HTMLFloatField::class,
147 'info' => HTMLInfoField::class,
148 'selectorother' => HTMLSelectOrOtherField::class,
149 'selectandother' => HTMLSelectAndOtherField::class,
150 'namespaceselect' => HTMLSelectNamespace::class,
151 'namespaceselectwithbutton' => HTMLSelectNamespaceWithButton::class,
152 'tagfilter' => HTMLTagFilter::class,
153 'sizefilter' => HTMLSizeFilterField::class,
154 'submit' => HTMLSubmitField::class,
155 'hidden' => HTMLHiddenField::class,
156 'edittools' => HTMLEditTools::class,
157 'checkmatrix' => HTMLCheckMatrix::class,
158 'cloner' => HTMLFormFieldCloner::class,
159 'autocompleteselect' => HTMLAutoCompleteSelectField::class,
160 'language' => HTMLSelectLanguageField::class,
161 'date' => HTMLDateTimeField::class,
162 'time' => HTMLDateTimeField::class,
163 'datetime' => HTMLDateTimeField::class,
164 'expiry' => HTMLExpiryField::class,
165 // HTMLTextField will output the correct type="" attribute automagically.
166 // There are about four zillion other HTML5 input types, like range, but
167 // we don't use those at the moment, so no point in adding all of them.
168 'email' => HTMLTextField::class,
169 'password' => HTMLTextField::class,
170 'url' => HTMLTextField::class,
171 'title' => HTMLTitleTextField::class,
172 'user' => HTMLUserTextField::class,
173 'usersmultiselect' => HTMLUsersMultiselectField::class,
174 'titlesmultiselect' => HTMLTitlesMultiselectField::class,
175 'namespacesmultiselect' => HTMLNamespacesMultiselectField::class,
176 ];
177
179
181
183 protected $mFlatFields = [];
184 protected $mFieldTree = [];
185 protected $mShowReset = false;
186 protected $mShowSubmit = true;
188 protected $mSubmitFlags = [ 'primary', 'progressive' ];
189 protected $mShowCancel = false;
190 protected $mCancelTarget;
191
194
195 protected $mPre = '';
196 protected $mHeader = '';
197 protected $mFooter = '';
198 protected $mSectionHeaders = [];
199 protected $mSectionFooters = [];
200 protected $mPost = '';
201 protected $mId;
202 protected $mName;
203 protected $mTableId = '';
204
205 protected $mSubmitID;
206 protected $mSubmitName;
207 protected $mSubmitText;
209
211 protected $mTitle;
212 protected $mMethod = 'post';
213 protected $mWasSubmitted = false;
214
220 protected $mAction = false;
221
227 protected $mCollapsible = false;
228
234 protected $mCollapsed = false;
235
241 protected $mAutocomplete = null;
242
243 protected $mUseMultipart = false;
244 protected $mHiddenFields = [];
249 protected $mButtons = [];
250
251 protected $mWrapperLegend = false;
252 protected $mWrapperAttributes = [];
253
258 protected $mTokenSalt = '';
259
267 protected $mSubSectionBeforeFields = true;
268
274 protected $displayFormat = 'table';
275
281 'table',
282 'div',
283 'raw',
284 'inline',
285 ];
286
292 'vform',
293 'ooui',
294 ];
295
303 public static function factory( $displayFormat, ...$arguments ) {
304 switch ( $displayFormat ) {
305 case 'vform':
306 return new VFormHTMLForm( ...$arguments );
307 case 'ooui':
308 return new OOUIHTMLForm( ...$arguments );
309 default:
310 $form = new self( ...$arguments );
311 $form->setDisplayFormat( $displayFormat );
312 return $form;
313 }
314 }
315
325 public function __construct( $descriptor, /*IContextSource*/ $context = null,
326 $messagePrefix = ''
327 ) {
328 if ( $context instanceof IContextSource ) {
329 $this->setContext( $context );
330 $this->mTitle = false; // We don't need them to set a title
331 $this->mMessagePrefix = $messagePrefix;
332 } elseif ( $context === null && $messagePrefix !== '' ) {
333 $this->mMessagePrefix = $messagePrefix;
334 } elseif ( is_string( $context ) && $messagePrefix === '' ) {
335 // B/C since 1.18
336 // it's actually $messagePrefix
337 $this->mMessagePrefix = $context;
338 }
339
340 // Evil hack for mobile :(
341 if (
342 !$this->getConfig()->get( 'HTMLFormAllowTableFormat' )
343 && $this->displayFormat === 'table'
344 ) {
345 $this->displayFormat = 'div';
346 }
347
348 $this->addFields( $descriptor );
349 }
350
360 public function addFields( $descriptor ) {
361 $loadedDescriptor = [];
362
363 foreach ( $descriptor as $fieldname => $info ) {
364
365 $section = $info['section'] ?? '';
366
367 if ( isset( $info['type'] ) && $info['type'] === 'file' ) {
368 $this->mUseMultipart = true;
369 }
370
371 $field = static::loadInputFromParameters( $fieldname, $info, $this );
372
373 $setSection =& $loadedDescriptor;
374 if ( $section ) {
375 foreach ( explode( '/', $section ) as $newName ) {
376 if ( !isset( $setSection[$newName] ) ) {
377 $setSection[$newName] = [];
378 }
379
380 $setSection =& $setSection[$newName];
381 }
382 }
383
384 $setSection[$fieldname] = $field;
385 $this->mFlatFields[$fieldname] = $field;
386 }
387
388 $this->mFieldTree = array_merge( $this->mFieldTree, $loadedDescriptor );
389
390 return $this;
391 }
392
397 public function hasField( $fieldname ) {
398 return isset( $this->mFlatFields[$fieldname] );
399 }
400
406 public function getField( $fieldname ) {
407 if ( !$this->hasField( $fieldname ) ) {
408 throw new DomainException( __METHOD__ . ': no field named ' . $fieldname );
409 }
410 return $this->mFlatFields[$fieldname];
411 }
412
423 public function setDisplayFormat( $format ) {
424 if (
425 in_array( $format, $this->availableSubclassDisplayFormats, true ) ||
426 in_array( $this->displayFormat, $this->availableSubclassDisplayFormats, true )
427 ) {
428 throw new MWException( 'Cannot change display format after creation, ' .
429 'use HTMLForm::factory() instead' );
430 }
431
432 if ( !in_array( $format, $this->availableDisplayFormats, true ) ) {
433 throw new MWException( 'Display format must be one of ' .
434 print_r(
435 array_merge(
436 $this->availableDisplayFormats,
437 $this->availableSubclassDisplayFormats
438 ),
439 true
440 ) );
441 }
442
443 // Evil hack for mobile :(
444 if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $format === 'table' ) {
445 $format = 'div';
446 }
447
448 $this->displayFormat = $format;
449
450 return $this;
451 }
452
458 public function getDisplayFormat() {
459 return $this->displayFormat;
460 }
461
479 public static function getClassFromDescriptor( $fieldname, &$descriptor ) {
480 if ( isset( $descriptor['class'] ) ) {
481 $class = $descriptor['class'];
482 } elseif ( isset( $descriptor['type'] ) ) {
483 $class = static::$typeMappings[$descriptor['type']];
484 $descriptor['class'] = $class;
485 } else {
486 $class = null;
487 }
488
489 if ( !$class ) {
490 throw new MWException( "Descriptor with no class for $fieldname: "
491 . print_r( $descriptor, true ) );
492 }
493
494 return $class;
495 }
496
508 public static function loadInputFromParameters( $fieldname, $descriptor,
509 HTMLForm $parent = null
510 ) {
511 $class = static::getClassFromDescriptor( $fieldname, $descriptor );
512
513 $descriptor['fieldname'] = $fieldname;
514 if ( $parent ) {
515 $descriptor['parent'] = $parent;
516 }
517
518 # @todo This will throw a fatal error whenever someone try to use
519 # 'class' to feed a CSS class instead of 'cssclass'. Would be
520 # great to avoid the fatal error and show a nice error.
521 return new $class( $descriptor );
522 }
523
533 public function prepareForm() {
534 # Check if we have the info we need
535 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
536 throw new MWException( 'You must call setTitle() on an HTMLForm' );
537 }
538
539 # Load data from the request.
540 if (
541 $this->mFormIdentifier === null ||
542 $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier
543 ) {
544 $this->loadData();
545 } else {
546 $this->mFieldData = [];
547 }
548
549 return $this;
550 }
551
556 public function tryAuthorizedSubmit() {
557 $result = false;
558
559 if ( $this->mFormIdentifier === null ) {
560 $identOkay = true;
561 } else {
562 $identOkay = $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier;
563 }
564
565 $tokenOkay = false;
566 if ( $this->getMethod() !== 'post' ) {
567 $tokenOkay = true; // no session check needed
568 } elseif ( $this->getRequest()->wasPosted() ) {
569 $editToken = $this->getRequest()->getVal( 'wpEditToken' );
570 if ( $this->getUser()->isLoggedIn() || $editToken !== null ) {
571 // Session tokens for logged-out users have no security value.
572 // However, if the user gave one, check it in order to give a nice
573 // "session expired" error instead of "permission denied" or such.
574 $tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
575 } else {
576 $tokenOkay = true;
577 }
578 }
579
580 if ( $tokenOkay && $identOkay ) {
581 $this->mWasSubmitted = true;
582 $result = $this->trySubmit();
583 }
584
585 return $result;
586 }
587
594 public function show() {
595 $this->prepareForm();
596
597 $result = $this->tryAuthorizedSubmit();
598 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
599 return $result;
600 }
601
602 $this->displayForm( $result );
603
604 return false;
605 }
606
612 public function showAlways() {
613 $this->prepareForm();
614
615 $result = $this->tryAuthorizedSubmit();
616
617 $this->displayForm( $result );
618
619 return $result;
620 }
621
633 public function trySubmit() {
634 $valid = true;
635 $hoistedErrors = Status::newGood();
636 if ( $this->mValidationErrorMessage ) {
637 foreach ( $this->mValidationErrorMessage as $error ) {
638 $hoistedErrors->fatal( ...$error );
639 }
640 } else {
641 $hoistedErrors->fatal( 'htmlform-invalid-input' );
642 }
643
644 $this->mWasSubmitted = true;
645
646 # Check for cancelled submission
647 foreach ( $this->mFlatFields as $fieldname => $field ) {
648 if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
649 continue;
650 }
651 if ( $field->cancelSubmit( $this->mFieldData[$fieldname], $this->mFieldData ) ) {
652 $this->mWasSubmitted = false;
653 return false;
654 }
655 }
656
657 # Check for validation
658 foreach ( $this->mFlatFields as $fieldname => $field ) {
659 if ( !array_key_exists( $fieldname, $this->mFieldData ) ) {
660 continue;
661 }
662 if ( $field->isHidden( $this->mFieldData ) ) {
663 continue;
664 }
665 $res = $field->validate( $this->mFieldData[$fieldname], $this->mFieldData );
666 if ( $res !== true ) {
667 $valid = false;
668 if ( $res !== false && !$field->canDisplayErrors() ) {
669 if ( is_string( $res ) ) {
670 $hoistedErrors->fatal( 'rawmessage', $res );
671 } else {
672 $hoistedErrors->fatal( $res );
673 }
674 }
675 }
676 }
677
678 if ( !$valid ) {
679 return $hoistedErrors;
680 }
681
682 $callback = $this->mSubmitCallback;
683 if ( !is_callable( $callback ) ) {
684 throw new MWException( 'HTMLForm: no submit callback provided. Use ' .
685 'setSubmitCallback() to set one.' );
686 }
687
688 $data = $this->filterDataForSubmit( $this->mFieldData );
689
690 $res = call_user_func( $callback, $data, $this );
691 if ( $res === false ) {
692 $this->mWasSubmitted = false;
693 }
694
695 return $res;
696 }
697
709 public function wasSubmitted() {
710 return $this->mWasSubmitted;
711 }
712
723 public function setSubmitCallback( $cb ) {
724 $this->mSubmitCallback = $cb;
725
726 return $this;
727 }
728
737 public function setValidationErrorMessage( $msg ) {
738 $this->mValidationErrorMessage = $msg;
739
740 return $this;
741 }
742
750 public function setIntro( $msg ) {
751 $this->setPreText( $msg );
752
753 return $this;
754 }
755
764 public function setPreText( $msg ) {
765 $this->mPre = $msg;
766
767 return $this;
768 }
769
777 public function addPreText( $msg ) {
778 $this->mPre .= $msg;
779
780 return $this;
781 }
782
790 public function getPreText() {
791 return $this->mPre;
792 }
793
802 public function addHeaderText( $msg, $section = null ) {
803 if ( $section === null ) {
804 $this->mHeader .= $msg;
805 } else {
806 if ( !isset( $this->mSectionHeaders[$section] ) ) {
807 $this->mSectionHeaders[$section] = '';
808 }
809 $this->mSectionHeaders[$section] .= $msg;
810 }
811
812 return $this;
813 }
814
824 public function setHeaderText( $msg, $section = null ) {
825 if ( $section === null ) {
826 $this->mHeader = $msg;
827 } else {
828 $this->mSectionHeaders[$section] = $msg;
829 }
830
831 return $this;
832 }
833
841 public function getHeaderText( $section = null ) {
842 if ( $section === null ) {
843 return $this->mHeader;
844 } else {
845 return $this->mSectionHeaders[$section] ?? '';
846 }
847 }
848
857 public function addFooterText( $msg, $section = null ) {
858 if ( $section === null ) {
859 $this->mFooter .= $msg;
860 } else {
861 if ( !isset( $this->mSectionFooters[$section] ) ) {
862 $this->mSectionFooters[$section] = '';
863 }
864 $this->mSectionFooters[$section] .= $msg;
865 }
866
867 return $this;
868 }
869
879 public function setFooterText( $msg, $section = null ) {
880 if ( $section === null ) {
881 $this->mFooter = $msg;
882 } else {
883 $this->mSectionFooters[$section] = $msg;
884 }
885
886 return $this;
887 }
888
896 public function getFooterText( $section = null ) {
897 if ( $section === null ) {
898 return $this->mFooter;
899 } else {
900 return $this->mSectionFooters[$section] ?? '';
901 }
902 }
903
911 public function addPostText( $msg ) {
912 $this->mPost .= $msg;
913
914 return $this;
915 }
916
924 public function setPostText( $msg ) {
925 $this->mPost = $msg;
926
927 return $this;
928 }
929
939 public function addHiddenField( $name, $value, array $attribs = [] ) {
940 $attribs += [ 'name' => $name ];
941 $this->mHiddenFields[] = [ $value, $attribs ];
942
943 return $this;
944 }
945
956 public function addHiddenFields( array $fields ) {
957 foreach ( $fields as $name => $value ) {
958 $this->mHiddenFields[] = [ $value, [ 'name' => $name ] ];
959 }
960
961 return $this;
962 }
963
991 public function addButton( $data ) {
992 if ( !is_array( $data ) ) {
993 $args = func_get_args();
994 if ( count( $args ) < 2 || count( $args ) > 4 ) {
995 throw new InvalidArgumentException(
996 'Incorrect number of arguments for deprecated calling style'
997 );
998 }
999 $data = [
1000 'name' => $args[0],
1001 'value' => $args[1],
1002 'id' => $args[2] ?? null,
1003 'attribs' => $args[3] ?? null,
1004 ];
1005 } else {
1006 if ( !isset( $data['name'] ) ) {
1007 throw new InvalidArgumentException( 'A name is required' );
1008 }
1009 if ( !isset( $data['value'] ) ) {
1010 throw new InvalidArgumentException( 'A value is required' );
1011 }
1012 }
1013 $this->mButtons[] = $data + [
1014 'id' => null,
1015 'attribs' => null,
1016 'flags' => null,
1017 'framed' => true,
1018 ];
1019
1020 return $this;
1021 }
1022
1032 public function setTokenSalt( $salt ) {
1033 $this->mTokenSalt = $salt;
1034
1035 return $this;
1036 }
1037
1050 public function displayForm( $submitResult ) {
1051 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
1052 }
1053
1062 public function getHTML( $submitResult ) {
1063 # For good measure (it is the default)
1064 $this->getOutput()->preventClickjacking();
1065 $this->getOutput()->addModules( 'mediawiki.htmlform' );
1066 $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' );
1067
1068 $html = ''
1069 . $this->getErrorsOrWarnings( $submitResult, 'error' )
1070 . $this->getErrorsOrWarnings( $submitResult, 'warning' )
1071 . $this->getHeaderText()
1072 . $this->getBody()
1073 . $this->getHiddenFields()
1074 . $this->getButtons()
1075 . $this->getFooterText();
1076
1077 $html = $this->wrapForm( $html );
1078
1079 return '' . $this->mPre . $html . $this->mPost;
1080 }
1081
1089 public function setCollapsibleOptions( $collapsedByDefault = false ) {
1090 $this->mCollapsible = true;
1091 $this->mCollapsed = $collapsedByDefault;
1092 return $this;
1093 }
1094
1099 protected function getFormAttributes() {
1100 # Use multipart/form-data
1101 $encType = $this->mUseMultipart
1102 ? 'multipart/form-data'
1103 : 'application/x-www-form-urlencoded';
1104 # Attributes
1105 $attribs = [
1106 'class' => 'mw-htmlform',
1107 'action' => $this->getAction(),
1108 'method' => $this->getMethod(),
1109 'enctype' => $encType,
1110 ];
1111 if ( $this->mId ) {
1112 $attribs['id'] = $this->mId;
1113 }
1114 if ( is_string( $this->mAutocomplete ) ) {
1115 $attribs['autocomplete'] = $this->mAutocomplete;
1116 }
1117 if ( $this->mName ) {
1118 $attribs['name'] = $this->mName;
1119 }
1120 if ( $this->needsJSForHtml5FormValidation() ) {
1121 $attribs['novalidate'] = true;
1122 }
1123 return $attribs;
1124 }
1125
1133 public function wrapForm( $html ) {
1134 # Include a <fieldset> wrapper for style, if requested.
1135 if ( $this->mWrapperLegend !== false ) {
1136 $legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false;
1137 $html = Xml::fieldset( $legend, $html, $this->mWrapperAttributes );
1138 }
1139
1140 return Html::rawElement(
1141 'form',
1142 $this->getFormAttributes(),
1143 $html
1144 );
1145 }
1146
1151 public function getHiddenFields() {
1152 $html = '';
1153 if ( $this->mFormIdentifier !== null ) {
1154 $html .= Html::hidden(
1155 'wpFormIdentifier',
1156 $this->mFormIdentifier
1157 ) . "\n";
1158 }
1159 if ( $this->getMethod() === 'post' ) {
1160 $html .= Html::hidden(
1161 'wpEditToken',
1162 $this->getUser()->getEditToken( $this->mTokenSalt ),
1163 [ 'id' => 'wpEditToken' ]
1164 ) . "\n";
1165 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
1166 }
1167
1168 $articlePath = $this->getConfig()->get( 'ArticlePath' );
1169 if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
1170 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
1171 }
1172
1173 foreach ( $this->mHiddenFields as $data ) {
1174 list( $value, $attribs ) = $data;
1175 $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
1176 }
1177
1178 return $html;
1179 }
1180
1185 public function getButtons() {
1186 $buttons = '';
1187 $useMediaWikiUIEverywhere = $this->getConfig()->get( 'UseMediaWikiUIEverywhere' );
1188
1189 if ( $this->mShowSubmit ) {
1190 $attribs = [];
1191
1192 if ( isset( $this->mSubmitID ) ) {
1193 $attribs['id'] = $this->mSubmitID;
1194 }
1195
1196 if ( isset( $this->mSubmitName ) ) {
1197 $attribs['name'] = $this->mSubmitName;
1198 }
1199
1200 if ( isset( $this->mSubmitTooltip ) ) {
1201 $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
1202 }
1203
1204 $attribs['class'] = [ 'mw-htmlform-submit' ];
1205
1206 if ( $useMediaWikiUIEverywhere ) {
1207 foreach ( $this->mSubmitFlags as $flag ) {
1208 $attribs['class'][] = 'mw-ui-' . $flag;
1209 }
1210 $attribs['class'][] = 'mw-ui-button';
1211 }
1212
1213 $buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
1214 }
1215
1216 if ( $this->mShowReset ) {
1217 $buttons .= Html::element(
1218 'input',
1219 [
1220 'type' => 'reset',
1221 'value' => $this->msg( 'htmlform-reset' )->text(),
1222 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
1223 ]
1224 ) . "\n";
1225 }
1226
1227 if ( $this->mShowCancel ) {
1228 $target = $this->mCancelTarget ?: Title::newMainPage();
1229 if ( $target instanceof Title ) {
1230 $target = $target->getLocalURL();
1231 }
1232 $buttons .= Html::element(
1233 'a',
1234 [
1235 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
1236 'href' => $target,
1237 ],
1238 $this->msg( 'cancel' )->text()
1239 ) . "\n";
1240 }
1241
1242 // IE<8 has bugs with <button>, so we'll need to avoid them.
1243 $isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) );
1244
1245 foreach ( $this->mButtons as $button ) {
1246 $attrs = [
1247 'type' => 'submit',
1248 'name' => $button['name'],
1249 'value' => $button['value']
1250 ];
1251
1252 if ( isset( $button['label-message'] ) ) {
1253 $label = $this->getMessage( $button['label-message'] )->parse();
1254 } elseif ( isset( $button['label'] ) ) {
1255 $label = htmlspecialchars( $button['label'] );
1256 } elseif ( isset( $button['label-raw'] ) ) {
1257 $label = $button['label-raw'];
1258 } else {
1259 $label = htmlspecialchars( $button['value'] );
1260 }
1261
1262 if ( $button['attribs'] ) {
1263 $attrs += $button['attribs'];
1264 }
1265
1266 if ( isset( $button['id'] ) ) {
1267 $attrs['id'] = $button['id'];
1268 }
1269
1270 if ( $useMediaWikiUIEverywhere ) {
1271 $attrs['class'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : [];
1272 $attrs['class'][] = 'mw-ui-button';
1273 }
1274
1275 if ( $isBadIE ) {
1276 $buttons .= Html::element( 'input', $attrs ) . "\n";
1277 } else {
1278 $buttons .= Html::rawElement( 'button', $attrs, $label ) . "\n";
1279 }
1280 }
1281
1282 if ( !$buttons ) {
1283 return '';
1284 }
1285
1286 return Html::rawElement( 'span',
1287 [ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
1288 }
1289
1294 public function getBody() {
1295 return $this->displaySection( $this->mFieldTree, $this->mTableId );
1296 }
1297
1306 public function getErrorsOrWarnings( $elements, $elementsType ) {
1307 if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
1308 throw new DomainException( $elementsType . ' is not a valid type.' );
1309 }
1310 $elementstr = false;
1311 if ( $elements instanceof Status ) {
1312 list( $errorStatus, $warningStatus ) = $elements->splitByErrorType();
1313 $status = $elementsType === 'error' ? $errorStatus : $warningStatus;
1314 if ( $status->isGood() ) {
1315 $elementstr = '';
1316 } else {
1317 $elementstr = $this->getOutput()->parseAsInterface(
1318 $status->getWikiText()
1319 );
1320 }
1321 } elseif ( is_array( $elements ) && $elementsType === 'error' ) {
1322 $elementstr = $this->formatErrors( $elements );
1323 } elseif ( $elementsType === 'error' ) {
1324 $elementstr = $elements;
1325 }
1326
1327 return $elementstr
1328 ? Html::rawElement( 'div', [ 'class' => $elementsType . 'box' ], $elementstr )
1329 : '';
1330 }
1331
1339 public function formatErrors( $errors ) {
1340 $errorstr = '';
1341
1342 foreach ( $errors as $error ) {
1343 $errorstr .= Html::rawElement(
1344 'li',
1345 [],
1346 $this->getMessage( $error )->parse()
1347 );
1348 }
1349
1350 $errorstr = Html::rawElement( 'ul', [], $errorstr );
1351
1352 return $errorstr;
1353 }
1354
1362 public function setSubmitText( $t ) {
1363 $this->mSubmitText = $t;
1364
1365 return $this;
1366 }
1367
1374 public function setSubmitDestructive() {
1375 $this->mSubmitFlags = [ 'destructive', 'primary' ];
1376
1377 return $this;
1378 }
1379
1388 public function setSubmitTextMsg( $msg ) {
1389 if ( !$msg instanceof Message ) {
1390 $msg = $this->msg( $msg );
1391 }
1392 $this->setSubmitText( $msg->text() );
1393
1394 return $this;
1395 }
1396
1401 public function getSubmitText() {
1402 return $this->mSubmitText ?: $this->msg( 'htmlform-submit' )->text();
1403 }
1404
1410 public function setSubmitName( $name ) {
1411 $this->mSubmitName = $name;
1412
1413 return $this;
1414 }
1415
1421 public function setSubmitTooltip( $name ) {
1422 $this->mSubmitTooltip = $name;
1423
1424 return $this;
1425 }
1426
1435 public function setSubmitID( $t ) {
1436 $this->mSubmitID = $t;
1437
1438 return $this;
1439 }
1440
1456 public function setFormIdentifier( $ident ) {
1457 $this->mFormIdentifier = $ident;
1458
1459 return $this;
1460 }
1461
1472 public function suppressDefaultSubmit( $suppressSubmit = true ) {
1473 $this->mShowSubmit = !$suppressSubmit;
1474
1475 return $this;
1476 }
1477
1484 public function showCancel( $show = true ) {
1485 $this->mShowCancel = $show;
1486 return $this;
1487 }
1488
1495 public function setCancelTarget( $target ) {
1496 $this->mCancelTarget = $target;
1497 return $this;
1498 }
1499
1509 public function setTableId( $id ) {
1510 $this->mTableId = $id;
1511
1512 return $this;
1513 }
1514
1520 public function setId( $id ) {
1521 $this->mId = $id;
1522
1523 return $this;
1524 }
1525
1530 public function setName( $name ) {
1531 $this->mName = $name;
1532
1533 return $this;
1534 }
1535
1547 public function setWrapperLegend( $legend ) {
1548 $this->mWrapperLegend = $legend;
1549
1550 return $this;
1551 }
1552
1560 public function setWrapperAttributes( $attributes ) {
1561 $this->mWrapperAttributes = $attributes;
1562
1563 return $this;
1564 }
1565
1575 public function setWrapperLegendMsg( $msg ) {
1576 if ( !$msg instanceof Message ) {
1577 $msg = $this->msg( $msg );
1578 }
1579 $this->setWrapperLegend( $msg->text() );
1580
1581 return $this;
1582 }
1583
1593 public function setMessagePrefix( $p ) {
1594 $this->mMessagePrefix = $p;
1595
1596 return $this;
1597 }
1598
1606 public function setTitle( $t ) {
1607 $this->mTitle = $t;
1608
1609 return $this;
1610 }
1611
1616 public function getTitle() {
1617 return $this->mTitle === false
1618 ? $this->getContext()->getTitle()
1619 : $this->mTitle;
1620 }
1621
1629 public function setMethod( $method = 'post' ) {
1630 $this->mMethod = strtolower( $method );
1631
1632 return $this;
1633 }
1634
1638 public function getMethod() {
1639 return $this->mMethod;
1640 }
1641
1651 protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
1652 return Xml::fieldset( $legend, $section, $attributes ) . "\n";
1653 }
1654
1671 public function displaySection( $fields,
1672 $sectionName = '',
1673 $fieldsetIDPrefix = '',
1674 &$hasUserVisibleFields = false
1675 ) {
1676 if ( $this->mFieldData === null ) {
1677 throw new LogicException( 'HTMLForm::displaySection() called on uninitialized field data. '
1678 . 'You probably called displayForm() without calling prepareForm() first.' );
1679 }
1680
1682
1683 $html = [];
1684 $subsectionHtml = '';
1685 $hasLabel = false;
1686
1687 // Conveniently, PHP method names are case-insensitive.
1688 // For grep: this can call getDiv, getRaw, getInline, getVForm, getOOUI
1689 $getFieldHtmlMethod = $displayFormat === 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
1690
1691 foreach ( $fields as $key => $value ) {
1692 if ( $value instanceof HTMLFormField ) {
1693 $v = array_key_exists( $key, $this->mFieldData )
1694 ? $this->mFieldData[$key]
1695 : $value->getDefault();
1696
1697 $retval = $value->$getFieldHtmlMethod( $v );
1698
1699 // check, if the form field should be added to
1700 // the output.
1701 if ( $value->hasVisibleOutput() ) {
1702 $html[] = $retval;
1703
1704 $labelValue = trim( $value->getLabel() );
1705 if ( $labelValue !== "\u{00A0}" && $labelValue !== '&#160;' && $labelValue !== '' ) {
1706 $hasLabel = true;
1707 }
1708
1709 $hasUserVisibleFields = true;
1710 }
1711 } elseif ( is_array( $value ) ) {
1712 $subsectionHasVisibleFields = false;
1713 $section =
1714 $this->displaySection( $value,
1715 "mw-htmlform-$key",
1716 "$fieldsetIDPrefix$key-",
1717 $subsectionHasVisibleFields );
1718 $legend = null;
1719
1720 if ( $subsectionHasVisibleFields === true ) {
1721 // Display the section with various niceties.
1722 $hasUserVisibleFields = true;
1723
1724 $legend = $this->getLegend( $key );
1725
1726 $section = $this->getHeaderText( $key ) .
1727 $section .
1728 $this->getFooterText( $key );
1729
1730 $attributes = [];
1731 if ( $fieldsetIDPrefix ) {
1732 $attributes['id'] = Sanitizer::escapeIdForAttribute( "$fieldsetIDPrefix$key" );
1733 }
1734 $subsectionHtml .= $this->wrapFieldSetSection(
1735 $legend, $section, $attributes, $fields === $this->mFieldTree
1736 );
1737 } else {
1738 // Just return the inputs, nothing fancy.
1739 $subsectionHtml .= $section;
1740 }
1741 }
1742 }
1743
1744 $html = $this->formatSection( $html, $sectionName, $hasLabel );
1745
1746 if ( $subsectionHtml ) {
1747 if ( $this->mSubSectionBeforeFields ) {
1748 return $subsectionHtml . "\n" . $html;
1749 } else {
1750 return $html . "\n" . $subsectionHtml;
1751 }
1752 } else {
1753 return $html;
1754 }
1755 }
1756
1764 protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
1765 if ( !$fieldsHtml ) {
1766 // Do not generate any wrappers for empty sections. Sections may be empty if they only have
1767 // subsections, but no fields. A legend will still be added in wrapFieldSetSection().
1768 return '';
1769 }
1770
1772 $html = implode( '', $fieldsHtml );
1773
1774 if ( $displayFormat === 'raw' ) {
1775 return $html;
1776 }
1777
1778 $classes = [];
1779
1780 if ( !$anyFieldHasLabel ) { // Avoid strange spacing when no labels exist
1781 $classes[] = 'mw-htmlform-nolabel';
1782 }
1783
1784 $attribs = [
1785 'class' => implode( ' ', $classes ),
1786 ];
1787
1788 if ( $sectionName ) {
1789 $attribs['id'] = Sanitizer::escapeIdForAttribute( $sectionName );
1790 }
1791
1792 if ( $displayFormat === 'table' ) {
1793 return Html::rawElement( 'table',
1794 $attribs,
1795 Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n";
1796 } elseif ( $displayFormat === 'inline' ) {
1797 return Html::rawElement( 'span', $attribs, "\n$html\n" );
1798 } else {
1799 return Html::rawElement( 'div', $attribs, "\n$html\n" );
1800 }
1801 }
1802
1806 public function loadData() {
1807 $fieldData = [];
1808
1809 foreach ( $this->mFlatFields as $fieldname => $field ) {
1810 $request = $this->getRequest();
1811 if ( $field->skipLoadData( $request ) ) {
1812 continue;
1813 } elseif ( !empty( $field->mParams['disabled'] ) ) {
1814 $fieldData[$fieldname] = $field->getDefault();
1815 } else {
1816 $fieldData[$fieldname] = $field->loadDataFromRequest( $request );
1817 }
1818 }
1819
1820 # Filter data.
1821 foreach ( $fieldData as $name => &$value ) {
1822 $field = $this->mFlatFields[$name];
1823 $value = $field->filter( $value, $this->mFlatFields );
1824 }
1825
1826 $this->mFieldData = $fieldData;
1827 }
1828
1836 public function suppressReset( $suppressReset = true ) {
1837 $this->mShowReset = !$suppressReset;
1838
1839 return $this;
1840 }
1841
1851 public function filterDataForSubmit( $data ) {
1852 return $data;
1853 }
1854
1863 public function getLegend( $key ) {
1864 return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
1865 }
1866
1877 public function setAction( $action ) {
1878 $this->mAction = $action;
1879
1880 return $this;
1881 }
1882
1890 public function getAction() {
1891 // If an action is alredy provided, return it
1892 if ( $this->mAction !== false ) {
1893 return $this->mAction;
1894 }
1895
1896 $articlePath = $this->getConfig()->get( 'ArticlePath' );
1897 // Check whether we are in GET mode and the ArticlePath contains a "?"
1898 // meaning that getLocalURL() would return something like "index.php?title=...".
1899 // As browser remove the query string before submitting GET forms,
1900 // it means that the title would be lost. In such case use wfScript() instead
1901 // and put title in an hidden field (see getHiddenFields()).
1902 if ( strpos( $articlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
1903 return wfScript();
1904 }
1905
1906 return $this->getTitle()->getLocalURL();
1907 }
1908
1919 public function setAutocomplete( $autocomplete ) {
1920 $this->mAutocomplete = $autocomplete;
1921
1922 return $this;
1923 }
1924
1931 protected function getMessage( $value ) {
1932 return Message::newFromSpecifier( $value )->setContext( $this );
1933 }
1934
1945 foreach ( $this->mFlatFields as $fieldname => $field ) {
1946 if ( $field->needsJSForHtml5FormValidation() ) {
1947 return true;
1948 }
1949 }
1950 return false;
1951 }
1952}
addFields( $fields)
getUser()
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
getContext()
if( $line===false) $args
Definition cdb.php:64
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()
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:131
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:723
setHeaderText( $msg, $section=null)
Set header text, inside the form.
Definition HTMLForm.php:824
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:241
bool string $mAction
Form action URL.
Definition HTMLForm.php:220
setAction( $action)
Set the value for the action attribute of the form.
string array $mTokenSalt
Salt for the edit token.
Definition HTMLForm.php:258
string[] $mSubmitFlags
Definition HTMLForm.php:188
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:193
setValidationErrorMessage( $msg)
Set a message to display on a validation error.
Definition HTMLForm.php:737
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.
Definition HTMLForm.php:508
setSubmitName( $name)
setTitle( $t)
Set the title for form submission.
static getClassFromDescriptor( $fieldname, &$descriptor)
Get the HTMLFormField subclass for this descriptor.
Definition HTMLForm.php:479
array $availableSubclassDisplayFormats
Available formats in which to display the form.
Definition HTMLForm.php:291
string $displayFormat
Format in which to display form.
Definition HTMLForm.php:274
__construct( $descriptor, $context=null, $messagePrefix='')
Build a new HTMLForm from an array of field attributes.
Definition HTMLForm.php:325
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:267
setDisplayFormat( $format)
Set format in which to display the form.
Definition HTMLForm.php:423
addButton( $data)
Add a button to the form.
Definition HTMLForm.php:991
loadData()
Construct the form fields from the Descriptor array.
getPreText()
Get the introductory message HTML.
Definition HTMLForm.php:790
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:458
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:594
hasField( $fieldname)
Definition HTMLForm.php:397
$mWrapperAttributes
Definition HTMLForm.php:252
addFooterText( $msg, $section=null)
Add footer text, inside the form.
Definition HTMLForm.php:857
addPostText( $msg)
Add text to the end of the display.
Definition HTMLForm.php:911
getFooterText( $section=null)
Get footer text.
Definition HTMLForm.php:896
addHeaderText( $msg, $section=null)
Add HTML to the header, inside the form.
Definition HTMLForm.php:802
setWrapperAttributes( $attributes)
For internal use only.
prepareForm()
Prepare form for submission.
Definition HTMLForm.php:533
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:709
getHeaderText( $section=null)
Get header text.
Definition HTMLForm.php:841
array[] $mButtons
-var array<array{name:string,value:string,label-message?:string,label?:string,label-raw?...
Definition HTMLForm.php:249
tryAuthorizedSubmit()
Try submitting, with edit token check first.
Definition HTMLForm.php:556
setSubmitTooltip( $name)
displaySection( $fields, $sectionName='', $fieldsetIDPrefix='', &$hasUserVisibleFields=false)
setPreText( $msg)
Set the introductory message HTML, overwriting any existing message.
Definition HTMLForm.php:764
bool $mCollapsible
Whether the form can be collapsed.
Definition HTMLForm.php:227
addHiddenField( $name, $value, array $attribs=[])
Add a hidden field to the output.
Definition HTMLForm.php:939
setFooterText( $msg, $section=null)
Set footer text, inside the form.
Definition HTMLForm.php:879
setMessagePrefix( $p)
Set the prefix for various default messages.
getField( $fieldname)
Definition HTMLForm.php:406
bool $mCollapsed
Whether the form is collapsed by default.
Definition HTMLForm.php:234
wrapForm( $html)
Wrap the form innards in an actual "<form>" element.
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:612
setTokenSalt( $salt)
Set the salt for the edit token.
addPreText( $msg)
Add HTML to introductory message.
Definition HTMLForm.php:777
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:924
static $typeMappings
Definition HTMLForm.php:133
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:280
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:303
setIntro( $msg)
Set the introductory message, overwriting any existing message.
Definition HTMLForm.php:750
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:633
HTMLFormField[] $mFlatFields
Definition HTMLForm.php:183
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:956
addFields( $descriptor)
Add fields to the form.
Definition HTMLForm.php:360
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null)
Returns the attributes for the tooltip and access key.
Definition Linker.php:2195
MediaWiki exception.
The Message class provides methods which fulfil two basic services:
Definition Message.php:162
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition Message.php:427
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:40
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.
$context
Definition load.php:45