MediaWiki  master
HTMLFormField.php
Go to the documentation of this file.
1 <?php
2 
5 
12 abstract class HTMLFormField {
14  public $mParams;
15 
17  protected $mFilterCallback;
18  protected $mName;
19  protected $mDir;
20  protected $mLabel; # String label, as HTML. Set on construction.
21  protected $mID;
22  protected $mClass = '';
23  protected $mVFormClass = '';
24  protected $mHelpClass = false;
25  protected $mDefault;
26  private $mNotices;
27 
31  protected $mOptions = false;
32  protected $mOptionsLabelsNotFromMessage = false;
36  protected $mCondState = [];
37  protected $mCondStateClass = [];
38 
43  protected $mShowEmptyLabels = true;
44 
48  public $mParent;
49 
60  abstract public function getInputHTML( $value );
61 
70  public function getInputOOUI( $value ) {
71  return false;
72  }
73 
80  public function canDisplayErrors() {
81  return $this->hasVisibleOutput();
82  }
83 
96  public function msg( $key, ...$params ) {
97  if ( $this->mParent ) {
98  return $this->mParent->msg( $key, ...$params );
99  }
100  return wfMessage( $key, ...$params );
101  }
102 
110  public function hasVisibleOutput() {
111  return true;
112  }
113 
120  public function getName() {
121  return $this->mName;
122  }
123 
135  protected function getNearestField( $name, $backCompat = false ) {
136  // When the field is belong to a HTMLFormFieldCloner
137  if ( isset( $this->mParams['cloner'] ) ) {
138  $field = $this->mParams['cloner']->findNearestField( $this, $name );
139  if ( $field ) {
140  return $field;
141  }
142  }
143 
144  if ( $backCompat && str_starts_with( $name, 'wp' ) &&
145  !$this->mParent->hasField( $name )
146  ) {
147  // Don't break the existed use cases.
148  return $this->mParent->getField( substr( $name, 2 ) );
149  }
150  return $this->mParent->getField( $name );
151  }
152 
164  protected function getNearestFieldValue( $alldata, $name, $asDisplay = false, $backCompat = false ) {
165  $field = $this->getNearestField( $name, $backCompat );
166  // When the field belongs to a HTMLFormFieldCloner
167  if ( isset( $field->mParams['cloner'] ) ) {
168  $value = $field->mParams['cloner']->extractFieldData( $field, $alldata );
169  } else {
170  // Note $alldata is an empty array when first rendering a form with a formIdentifier.
171  // In that case, $alldata[$field->mParams['fieldname']] is unset and we use the
172  // field's default value
173  $value = $alldata[$field->mParams['fieldname']] ?? $field->getDefault();
174  }
175 
176  // Check invert state for HTMLCheckField
177  if ( $asDisplay && $field instanceof HTMLCheckField && ( $field->mParams['invert'] ?? false ) ) {
178  $value = !$value;
179  }
180 
181  return $value;
182  }
183 
194  protected function getNearestFieldByName( $alldata, $name, $asDisplay = false ) {
195  return (string)$this->getNearestFieldValue( $alldata, $name, $asDisplay );
196  }
197 
205  protected function validateCondState( $params ) {
206  $origParams = $params;
207  $op = array_shift( $params );
208 
209  try {
210  switch ( $op ) {
211  case 'NOT':
212  if ( count( $params ) !== 1 ) {
213  throw new MWException( "NOT takes exactly one parameter" );
214  }
215  // Fall-through intentionally
216 
217  case 'AND':
218  case 'OR':
219  case 'NAND':
220  case 'NOR':
221  foreach ( $params as $i => $p ) {
222  if ( !is_array( $p ) ) {
223  $type = gettype( $p );
224  throw new MWException( "Expected array, found $type at index $i" );
225  }
226  $this->validateCondState( $p );
227  }
228  break;
229 
230  case '===':
231  case '!==':
232  if ( count( $params ) !== 2 ) {
233  throw new MWException( "$op takes exactly two parameters" );
234  }
235  [ $name, $value ] = $params;
236  if ( !is_string( $name ) || !is_string( $value ) ) {
237  throw new MWException( "Parameters for $op must be strings" );
238  }
239  break;
240 
241  default:
242  throw new MWException( "Unknown operation" );
243  }
244  } catch ( MWException $ex ) {
245  throw new MWException(
246  "Invalid hide-if or disable-if specification for $this->mName: " .
247  $ex->getMessage() . " in " . var_export( $origParams, true ),
248  0, $ex
249  );
250  }
251  }
252 
261  protected function checkStateRecurse( array $alldata, array $params ) {
262  $op = array_shift( $params );
263  $valueChk = [ 'AND' => false, 'OR' => true, 'NAND' => false, 'NOR' => true ];
264  $valueRet = [ 'AND' => true, 'OR' => false, 'NAND' => false, 'NOR' => true ];
265 
266  switch ( $op ) {
267  case 'AND':
268  case 'OR':
269  case 'NAND':
270  case 'NOR':
271  foreach ( $params as $p ) {
272  if ( $valueChk[$op] === $this->checkStateRecurse( $alldata, $p ) ) {
273  return !$valueRet[$op];
274  }
275  }
276  return $valueRet[$op];
277 
278  case 'NOT':
279  return !$this->checkStateRecurse( $alldata, $params[0] );
280 
281  case '===':
282  case '!==':
283  [ $field, $value ] = $params;
284  $testValue = (string)$this->getNearestFieldValue( $alldata, $field, true, true );
285  switch ( $op ) {
286  case '===':
287  return ( $value === $testValue );
288  case '!==':
289  return ( $value !== $testValue );
290  }
291  }
292  }
293 
302  protected function parseCondState( $params ) {
303  $op = array_shift( $params );
304 
305  switch ( $op ) {
306  case 'AND':
307  case 'OR':
308  case 'NAND':
309  case 'NOR':
310  $ret = [ $op ];
311  foreach ( $params as $p ) {
312  $ret[] = $this->parseCondState( $p );
313  }
314  return $ret;
315 
316  case 'NOT':
317  return [ 'NOT', $this->parseCondState( $params[0] ) ];
318 
319  case '===':
320  case '!==':
321  [ $name, $value ] = $params;
322  $field = $this->getNearestField( $name, true );
323  return [ $op, $field->getName(), $value ];
324  }
325  }
326 
332  protected function parseCondStateForClient() {
333  $parsed = [];
334  foreach ( $this->mCondState as $type => $params ) {
335  $parsed[$type] = $this->parseCondState( $params );
336  }
337  return $parsed;
338  }
339 
348  public function isHidden( $alldata ) {
349  if ( !( $this->mCondState && isset( $this->mCondState['hide'] ) ) ) {
350  return false;
351  }
352 
353  return $this->checkStateRecurse( $alldata, $this->mCondState['hide'] );
354  }
355 
364  public function isDisabled( $alldata ) {
365  if ( $this->mParams['disabled'] ?? false ) {
366  return true;
367  }
368  $hidden = $this->isHidden( $alldata );
369  if ( !$this->mCondState || !isset( $this->mCondState['disable'] ) ) {
370  return $hidden;
371  }
372 
373  return $hidden || $this->checkStateRecurse( $alldata, $this->mCondState['disable'] );
374  }
375 
387  public function cancelSubmit( $value, $alldata ) {
388  return false;
389  }
390 
403  public function validate( $value, $alldata ) {
404  if ( $this->isHidden( $alldata ) ) {
405  return true;
406  }
407 
408  if ( isset( $this->mParams['required'] )
409  && $this->mParams['required'] !== false
410  && ( $value === '' || $value === false || $value === null )
411  ) {
412  return $this->msg( 'htmlform-required' );
413  }
414 
415  if ( isset( $this->mValidationCallback ) ) {
416  return ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
417  }
418 
419  return true;
420  }
421 
430  public function filter( $value, $alldata ) {
431  if ( isset( $this->mFilterCallback ) ) {
432  $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
433  }
434 
435  return $value;
436  }
437 
445  protected function needsLabel() {
446  return true;
447  }
448 
458  public function setShowEmptyLabel( $show ) {
459  $this->mShowEmptyLabels = $show;
460  }
461 
473  protected function isSubmitAttempt( WebRequest $request ) {
474  // HTMLForm would add a hidden field of edit token for forms that require to be posted.
475  return $request->wasPosted() && $request->getCheck( 'wpEditToken' )
476  // The identifier matching or not has been checked in HTMLForm::prepareForm()
477  || $request->getCheck( 'wpFormIdentifier' );
478  }
479 
488  public function loadDataFromRequest( $request ) {
489  if ( $request->getCheck( $this->mName ) ) {
490  return $request->getText( $this->mName );
491  } else {
492  return $this->getDefault();
493  }
494  }
495 
505  public function __construct( $params ) {
506  $this->mParams = $params;
507 
508  if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
509  $this->mParent = $params['parent'];
510  } else {
511  // Normally parent is added automatically by HTMLForm::factory.
512  // Several field types already assume unconditionally this is always set,
513  // so deprecate manually creating an HTMLFormField without a parent form set.
515  __METHOD__ . ": Constructing an HTMLFormField without a 'parent' parameter",
516  "1.40"
517  );
518  }
519 
520  # Generate the label from a message, if possible
521  if ( isset( $params['label-message'] ) ) {
522  $this->mLabel = $this->getMessage( $params['label-message'] )->parse();
523  } elseif ( isset( $params['label'] ) ) {
524  if ( $params['label'] === '&#160;' || $params['label'] === "\u{00A0}" ) {
525  // Apparently some things set &nbsp directly and in an odd format
526  $this->mLabel = "\u{00A0}";
527  } else {
528  $this->mLabel = htmlspecialchars( $params['label'] );
529  }
530  } elseif ( isset( $params['label-raw'] ) ) {
531  $this->mLabel = $params['label-raw'];
532  }
533 
534  $this->mName = "wp{$params['fieldname']}";
535  if ( isset( $params['name'] ) ) {
536  $this->mName = $params['name'];
537  }
538 
539  if ( isset( $params['dir'] ) ) {
540  $this->mDir = $params['dir'];
541  }
542 
543  $this->mID = "mw-input-{$this->mName}";
544 
545  if ( isset( $params['default'] ) ) {
546  $this->mDefault = $params['default'];
547  }
548 
549  if ( isset( $params['id'] ) ) {
550  $this->mID = $params['id'];
551  }
552 
553  if ( isset( $params['cssclass'] ) ) {
554  $this->mClass = $params['cssclass'];
555  }
556 
557  if ( isset( $params['csshelpclass'] ) ) {
558  $this->mHelpClass = $params['csshelpclass'];
559  }
560 
561  if ( isset( $params['validation-callback'] ) ) {
562  $this->mValidationCallback = $params['validation-callback'];
563  }
564 
565  if ( isset( $params['filter-callback'] ) ) {
566  $this->mFilterCallback = $params['filter-callback'];
567  }
568 
569  if ( isset( $params['hidelabel'] ) ) {
570  $this->mShowEmptyLabels = false;
571  }
572  if ( isset( $params['notices'] ) ) {
573  $this->mNotices = $params['notices'];
574  }
575 
576  if ( isset( $params['hide-if'] ) && $params['hide-if'] ) {
577  $this->validateCondState( $params['hide-if'] );
578  $this->mCondState['hide'] = $params['hide-if'];
579  $this->mCondStateClass[] = 'mw-htmlform-hide-if';
580  }
581  if ( !( isset( $params['disabled'] ) && $params['disabled'] ) &&
582  isset( $params['disable-if'] ) && $params['disable-if']
583  ) {
584  $this->validateCondState( $params['disable-if'] );
585  $this->mCondState['disable'] = $params['disable-if'];
586  $this->mCondStateClass[] = 'mw-htmlform-disable-if';
587  }
588  }
589 
599  public function getTableRow( $value ) {
600  [ $errors, $errorClass ] = $this->getErrorsAndErrorClass( $value );
601  $inputHtml = $this->getInputHTML( $value );
602  $fieldType = $this->getClassName();
603  $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
604  $cellAttributes = [];
605  $rowAttributes = [];
606  $rowClasses = '';
607 
608  if ( !empty( $this->mParams['vertical-label'] ) ) {
609  $cellAttributes['colspan'] = 2;
610  $verticalLabel = true;
611  } else {
612  $verticalLabel = false;
613  }
614 
615  $label = $this->getLabelHtml( $cellAttributes );
616 
617  $field = Html::rawElement(
618  'td',
619  [ 'class' => 'mw-input' ] + $cellAttributes,
620  $inputHtml . "\n$errors"
621  );
622 
623  if ( $this->mCondState ) {
624  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
625  $rowClasses .= implode( ' ', $this->mCondStateClass );
626  }
627 
628  if ( $verticalLabel ) {
629  $html = Html::rawElement( 'tr',
630  $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
631  $html .= Html::rawElement( 'tr',
632  $rowAttributes + [
633  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
634  ],
635  $field );
636  } else {
637  $html = Html::rawElement( 'tr',
638  $rowAttributes + [
639  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
640  ],
641  $label . $field );
642  }
643 
644  return $html . $helptext;
645  }
646 
657  public function getDiv( $value ) {
658  [ $errors, $errorClass ] = $this->getErrorsAndErrorClass( $value );
659  $inputHtml = $this->getInputHTML( $value );
660  $fieldType = $this->getClassName();
661  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
662  $cellAttributes = [];
663  $label = $this->getLabelHtml( $cellAttributes );
664 
665  $outerDivClass = [
666  'mw-input',
667  'mw-htmlform-nolabel' => ( $label === '' )
668  ];
669 
670  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
671 
672  if ( $horizontalLabel ) {
673  $field = "\u{00A0}" . $inputHtml . "\n$errors";
674  } else {
675  $field = Html::rawElement(
676  'div',
677  // @phan-suppress-next-line PhanUselessBinaryAddRight
678  [ 'class' => $outerDivClass ] + $cellAttributes,
679  $inputHtml . "\n$errors"
680  );
681  }
682  $divCssClasses = [ "mw-htmlform-field-$fieldType",
683  $this->mClass, $this->mVFormClass, $errorClass ];
684 
685  $wrapperAttributes = [
686  'class' => $divCssClasses,
687  ];
688  if ( $this->mCondState ) {
689  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
690  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
691  }
692  $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
693  $html .= $helptext;
694 
695  return $html;
696  }
697 
707  public function getOOUI( $value ) {
708  $inputField = $this->getInputOOUI( $value );
709 
710  if ( !$inputField ) {
711  // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
712  // generate the whole field, label and errors and all, then wrap it in a Widget.
713  // It might look weird, but it'll work OK.
714  return $this->getFieldLayoutOOUI(
715  new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
716  [ 'align' => 'top' ]
717  );
718  }
719 
720  $infusable = true;
721  if ( is_string( $inputField ) ) {
722  // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
723  // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
724  // JavaScript doesn't know how to rebuilt the contents.
725  $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
726  $infusable = false;
727  }
728 
729  $fieldType = $this->getClassName();
730  $help = $this->getHelpText();
731  $errors = $this->getErrorsRaw( $value );
732  foreach ( $errors as &$error ) {
733  $error = new OOUI\HtmlSnippet( $error );
734  }
735 
736  $config = [
737  'classes' => [ "mw-htmlform-field-$fieldType" ],
738  'align' => $this->getLabelAlignOOUI(),
739  'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
740  'errors' => $errors,
741  'infusable' => $infusable,
742  'helpInline' => $this->isHelpInline(),
743  'notices' => $this->mNotices ?: [],
744  ];
745  if ( $this->mClass !== '' ) {
746  $config['classes'][] = $this->mClass;
747  }
748 
749  $preloadModules = false;
750 
751  if ( $infusable && $this->shouldInfuseOOUI() ) {
752  $preloadModules = true;
753  $config['classes'][] = 'mw-htmlform-autoinfuse';
754  }
755  if ( $this->mCondState ) {
756  $config['classes'] = array_merge( $config['classes'], $this->mCondStateClass );
757  }
758 
759  // the element could specify, that the label doesn't need to be added
760  $label = $this->getLabel();
761  if ( $label && $label !== "\u{00A0}" && $label !== '&#160;' ) {
762  $config['label'] = new OOUI\HtmlSnippet( $label );
763  }
764 
765  if ( $this->mCondState ) {
766  $preloadModules = true;
767  $config['condState'] = $this->parseCondStateForClient();
768  }
769 
770  $config['modules'] = $this->getOOUIModules();
771 
772  if ( $preloadModules ) {
773  $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
774  $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
775  }
776 
777  return $this->getFieldLayoutOOUI( $inputField, $config );
778  }
779 
787  protected function getClassName() {
788  $name = explode( '\\', static::class );
789  return end( $name );
790  }
791 
797  protected function getLabelAlignOOUI() {
798  return 'top';
799  }
800 
807  protected function getFieldLayoutOOUI( $inputField, $config ) {
808  return new HTMLFormFieldLayout( $inputField, $config );
809  }
810 
819  protected function shouldInfuseOOUI() {
820  // Always infuse fields with popup help text, since the interface for it is nicer with JS
821  return $this->getHelpText() !== null && !$this->isHelpInline();
822  }
823 
831  protected function getOOUIModules() {
832  return [];
833  }
834 
845  public function getRaw( $value ) {
846  [ $errors, ] = $this->getErrorsAndErrorClass( $value );
847  $inputHtml = $this->getInputHTML( $value );
848  $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
849  $cellAttributes = [];
850  $label = $this->getLabelHtml( $cellAttributes );
851 
852  $html = "\n$errors";
853  $html .= $label;
854  $html .= $inputHtml;
855  $html .= $helptext;
856 
857  return $html;
858  }
859 
869  public function getVForm( $value ) {
870  // Ewwww
871  $this->mVFormClass = ' mw-ui-vform-field';
872  return $this->getDiv( $value );
873  }
874 
882  public function getInline( $value ) {
883  [ $errors, ] = $this->getErrorsAndErrorClass( $value );
884  $inputHtml = $this->getInputHTML( $value );
885  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
886  $cellAttributes = [];
887  $label = $this->getLabelHtml( $cellAttributes );
888 
889  $html = "\n" . $errors .
890  $label . "\u{00A0}" .
891  $inputHtml .
892  $helptext;
893 
894  return $html;
895  }
896 
904  public function getHelpTextHtmlTable( $helptext ) {
905  if ( $helptext === null ) {
906  return '';
907  }
908 
909  $rowAttributes = [];
910  if ( $this->mCondState ) {
911  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
912  $rowAttributes['class'] = $this->mCondStateClass;
913  }
914 
915  $tdClasses = [ 'htmlform-tip' ];
916  if ( $this->mHelpClass !== false ) {
917  $tdClasses[] = $this->mHelpClass;
918  }
919  $row = Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext );
920  $row = Html::rawElement( 'tr', $rowAttributes, $row );
921 
922  return $row;
923  }
924 
933  public function getHelpTextHtmlDiv( $helptext ) {
934  if ( $helptext === null ) {
935  return '';
936  }
937 
938  $wrapperAttributes = [
939  'class' => [ 'htmlform-tip' ],
940  ];
941  if ( $this->mHelpClass !== false ) {
942  $wrapperAttributes['class'][] = $this->mHelpClass;
943  }
944  if ( $this->mCondState ) {
945  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
946  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
947  }
948  $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
949 
950  return $div;
951  }
952 
960  public function getHelpTextHtmlRaw( $helptext ) {
961  return $this->getHelpTextHtmlDiv( $helptext );
962  }
963 
970  public function getHelpText() {
971  $helptext = null;
972 
973  if ( isset( $this->mParams['help-message'] ) ) {
974  $this->mParams['help-messages'] = [ $this->mParams['help-message'] ];
975  }
976 
977  if ( isset( $this->mParams['help-messages'] ) ) {
978  foreach ( $this->mParams['help-messages'] as $msg ) {
979  $msg = $this->getMessage( $msg );
980 
981  if ( $msg->exists() ) {
982  if ( $helptext === null ) {
983  $helptext = '';
984  } else {
985  $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
986  }
987  $helptext .= $msg->parse(); // Append message
988  }
989  }
990  } elseif ( isset( $this->mParams['help'] ) ) {
991  $helptext = $this->mParams['help'];
992  }
993 
994  return $helptext;
995  }
996 
1005  public function isHelpInline() {
1006  return $this->mParams['help-inline'] ?? true;
1007  }
1008 
1021  public function getErrorsAndErrorClass( $value ) {
1022  $errors = $this->validate( $value, $this->mParent->mFieldData );
1023 
1024  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1025  $errors = '';
1026  $errorClass = '';
1027  } else {
1028  $errors = self::formatErrors( $errors );
1029  $errorClass = 'mw-htmlform-invalid-input';
1030  }
1031 
1032  return [ $errors, $errorClass ];
1033  }
1034 
1042  public function getErrorsRaw( $value ) {
1043  $errors = $this->validate( $value, $this->mParent->mFieldData );
1044 
1045  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1046  $errors = [];
1047  }
1048 
1049  if ( !is_array( $errors ) ) {
1050  $errors = [ $errors ];
1051  }
1052  foreach ( $errors as &$error ) {
1053  if ( $error instanceof Message ) {
1054  $error = $error->parse();
1055  }
1056  }
1057 
1058  return $errors;
1059  }
1060 
1065  public function getLabel() {
1066  return $this->mLabel ?? '';
1067  }
1068 
1075  public function getLabelHtml( $cellAttributes = [] ) {
1076  # Don't output a for= attribute for labels with no associated input.
1077  # Kind of hacky here, possibly we don't want these to be <label>s at all.
1078  $for = [];
1079 
1080  if ( $this->needsLabel() ) {
1081  $for['for'] = $this->mID;
1082  }
1083 
1084  $labelValue = trim( $this->getLabel() );
1085  $hasLabel = false;
1086  if ( $labelValue !== "\u{00A0}" && $labelValue !== '&#160;' && $labelValue !== '' ) {
1087  $hasLabel = true;
1088  }
1089 
1090  $displayFormat = $this->mParent->getDisplayFormat();
1091  $html = '';
1092  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
1093 
1094  if ( $displayFormat === 'table' ) {
1095  $html =
1096  Html::rawElement( 'td',
1097  [ 'class' => 'mw-label' ] + $cellAttributes,
1098  Html::rawElement( 'label', $for, $labelValue ) );
1099  } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
1100  if ( $displayFormat === 'div' && !$horizontalLabel ) {
1101  $html =
1102  Html::rawElement( 'div',
1103  [ 'class' => 'mw-label' ] + $cellAttributes,
1104  Html::rawElement( 'label', $for, $labelValue ) );
1105  } else {
1106  $html = Html::rawElement( 'label', $for, $labelValue );
1107  }
1108  }
1109 
1110  return $html;
1111  }
1112 
1117  public function getDefault() {
1118  return $this->mDefault ?? null;
1119  }
1120 
1126  public function getTooltipAndAccessKey() {
1127  if ( empty( $this->mParams['tooltip'] ) ) {
1128  return [];
1129  }
1130 
1131  return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
1132  }
1133 
1139  public function getTooltipAndAccessKeyOOUI() {
1140  if ( empty( $this->mParams['tooltip'] ) ) {
1141  return [];
1142  }
1143 
1144  return [
1145  'title' => Linker::titleAttrib( $this->mParams['tooltip'] ),
1146  'accessKey' => Linker::accesskey( $this->mParams['tooltip'] ),
1147  ];
1148  }
1149 
1157  public function getAttributes( array $list ) {
1158  static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
1159 
1160  $ret = [];
1161  foreach ( $list as $key ) {
1162  if ( in_array( $key, $boolAttribs ) ) {
1163  if ( !empty( $this->mParams[$key] ) ) {
1164  $ret[$key] = '';
1165  }
1166  } elseif ( isset( $this->mParams[$key] ) ) {
1167  $ret[$key] = $this->mParams[$key];
1168  }
1169  }
1170 
1171  return $ret;
1172  }
1173 
1183  private function lookupOptionsKeys( $options, $needsParse ) {
1184  $ret = [];
1185  foreach ( $options as $key => $value ) {
1186  $msg = $this->msg( $key );
1187  $key = $needsParse ? $msg->parse() : $msg->plain();
1188  $ret[$key] = is_array( $value )
1189  ? $this->lookupOptionsKeys( $value, $needsParse )
1190  : strval( $value );
1191  }
1192  return $ret;
1193  }
1194 
1202  public static function forceToStringRecursive( $array ) {
1203  if ( is_array( $array ) ) {
1204  return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1205  } else {
1206  return strval( $array );
1207  }
1208  }
1209 
1216  public function getOptions() {
1217  if ( $this->mOptions === false ) {
1218  if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1219  $needsParse = $this->mParams['options-messages-parse'] ?? false;
1220  if ( $needsParse ) {
1221  $this->mOptionsLabelsNotFromMessage = true;
1222  }
1223  $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'], $needsParse );
1224  } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1225  $this->mOptionsLabelsNotFromMessage = true;
1226  $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1227  } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1228  $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1229  $this->mOptions = Xml::listDropDownOptions( $message );
1230  } else {
1231  $this->mOptions = null;
1232  }
1233  }
1234 
1235  return $this->mOptions;
1236  }
1237 
1243  public function getOptionsOOUI() {
1244  $oldoptions = $this->getOptions();
1245 
1246  if ( $oldoptions === null ) {
1247  return null;
1248  }
1249 
1250  return Xml::listDropDownOptionsOoui( $oldoptions );
1251  }
1252 
1260  public static function flattenOptions( $options ) {
1261  $flatOpts = [];
1262 
1263  foreach ( $options as $value ) {
1264  if ( is_array( $value ) ) {
1265  $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1266  } else {
1267  $flatOpts[] = $value;
1268  }
1269  }
1270 
1271  return $flatOpts;
1272  }
1273 
1287  protected static function formatErrors( $errors ) {
1288  if ( is_array( $errors ) && count( $errors ) === 1 ) {
1289  $errors = array_shift( $errors );
1290  }
1291 
1292  if ( is_array( $errors ) ) {
1293  $lines = [];
1294  foreach ( $errors as $error ) {
1295  if ( $error instanceof Message ) {
1296  $lines[] = Html::rawElement( 'li', [], $error->parse() );
1297  } else {
1298  $lines[] = Html::rawElement( 'li', [], $error );
1299  }
1300  }
1301 
1302  $errors = Html::rawElement( 'ul', [], implode( "\n", $lines ) );
1303  } else {
1304  if ( $errors instanceof Message ) {
1305  $errors = $errors->parse();
1306  }
1307  }
1308 
1309  return Html::errorBox( $errors );
1310  }
1311 
1318  protected function getMessage( $value ) {
1319  $message = Message::newFromSpecifier( $value );
1320 
1321  if ( $this->mParent ) {
1322  $message->setContext( $this->mParent );
1323  }
1324 
1325  return $message;
1326  }
1327 
1335  public function skipLoadData( $request ) {
1336  return !empty( $this->mParams['nodata'] );
1337  }
1338 
1346  public function needsJSForHtml5FormValidation() {
1347  if ( $this->mCondState ) {
1348  // This is probably more restrictive than it needs to be, but better safe than sorry
1349  return true;
1350  }
1351  return false;
1352  }
1353 }
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.
static encode( $value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:96
A checkbox field.
The parent class to generate form fields.
getName()
Get the field name that will be used for submission.
getHelpTextHtmlDiv( $helptext)
Generate help text HTML in div format.
needsJSForHtml5FormValidation()
Whether this field requires the user agent to have JavaScript enabled for the client-side HTML5 form ...
parseCondStateForClient()
Parse the cond-state array for client-side.
HTMLForm null $mParent
getLabelAlignOOUI()
Get label alignment when generating field for OOUI.
getInputOOUI( $value)
Same as getInputHTML, but returns an OOUI object.
getHelpTextHtmlRaw( $helptext)
Generate help text HTML formatted for raw output.
isHelpInline()
Determine if the help text should be displayed inline.
isDisabled( $alldata)
Test whether this field is supposed to be disabled, based on the values of the other form fields.
getFieldLayoutOOUI( $inputField, $config)
Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
validateCondState( $params)
Validate the cond-state params, the existence check of fields should be done later.
isHidden( $alldata)
Test whether this field is supposed to be hidden, based on the values of the other form fields.
filter( $value, $alldata)
bool $mShowEmptyLabels
If true will generate an empty div element with no label.
getNearestField( $name, $backCompat=false)
Get the closest field matching a given name.
shouldInfuseOOUI()
Whether the field should be automatically infused.
__construct( $params)
Initialise the object.
getMessage( $value)
Turns a *-message parameter (which could be a MessageSpecifier, or a message name,...
getClassName()
Gets the non namespaced class name.
getOOUIModules()
Get the list of extra ResourceLoader modules which must be loaded client-side before it's possible to...
getNearestFieldByName( $alldata, $name, $asDisplay=false)
Fetch a field value from $alldata for the closest field matching a given name.
skipLoadData( $request)
Skip this field when collecting data.
array $mCondState
Array to hold params for 'hide-if' or 'disable-if' statements.
getVForm( $value)
Get the complete field for the input, including help text, labels, and whatever.
getInputHTML( $value)
This function must be implemented to return the HTML to generate the input object itself.
getErrorsAndErrorClass( $value)
Determine form errors to display and their classes.
validate( $value, $alldata)
Override this function to add specific validation checks on the field input.
getOOUI( $value)
Get the OOUI version of the div.
canDisplayErrors()
True if this field type is able to display errors; false if validation errors need to be displayed in...
checkStateRecurse(array $alldata, array $params)
Helper function for isHidden and isDisabled to handle recursive data structures.
getHelpTextHtmlTable( $helptext)
Generate help text HTML in table format.
array array[] $mParams
static flattenOptions( $options)
flatten an array of options to a single array, for instance, a set of "<options>" inside "<optgroups>...
getRaw( $value)
Get the complete raw fields for the input, including help text, labels, and whatever.
getTooltipAndAccessKeyOOUI()
Returns the attributes required for the tooltip and accesskey, for OOUI widgets' config.
getErrorsRaw( $value)
Determine form errors to display, returning them in an array.
getHelpText()
Determine the help text to display.
getOptions()
Fetch the array of options from the field's parameters.
parseCondState( $params)
Parse the cond-state array to use the field name for submission, since the key in the form descriptor...
getTableRow( $value)
Get the complete table row for the input, including help text, labels, and whatever.
getInline( $value)
Get the complete field as an inline element.
isSubmitAttempt(WebRequest $request)
Can we assume that the request is an attempt to submit a HTMLForm, as opposed to an attempt to just v...
msg( $key,... $params)
Get a translated interface message.
array null false $mOptions
getLabelHtml( $cellAttributes=[])
loadDataFromRequest( $request)
Get the value that this input has been set to from a posted form, or the input's default value if it ...
getAttributes(array $list)
Returns the given attributes from the parameters.
getNearestFieldValue( $alldata, $name, $asDisplay=false, $backCompat=false)
Fetch a field value from $alldata for the closest field matching a given name.
setShowEmptyLabel( $show)
Tell the field whether to generate a separate label element if its label is blank.
needsLabel()
Should this field have a label, or is there no input element with the appropriate id for the label to...
getDiv( $value)
Get the complete div for the input, including help text, labels, and whatever.
cancelSubmit( $value, $alldata)
Override this function if the control can somehow trigger a form submission that shouldn't actually s...
getOptionsOOUI()
Get options and make them into arrays suitable for OOUI.
hasVisibleOutput()
If this field has a user-visible output or not.
getTooltipAndAccessKey()
Returns the attributes required for the tooltip and accesskey, for Html::element() etc.
static formatErrors( $errors)
Formats one or more errors as accepted by field validation-callback.
static forceToStringRecursive( $array)
Recursively forces values in an array to strings, because issues arise with integer 0 as a value.
Object handling generic submission, CSRF protection, layout and other logic for UI forms in a reusabl...
Definition: HTMLForm.php:155
MediaWiki exception.
Definition: MWException.php:32
This class is a collection of static functions that serve two purposes:
Definition: Html.php:55
Some internal bits split of from Skin.php.
Definition: Linker.php:67
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:144
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:426
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:50
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
Definition: WebRequest.php:690
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
Definition: WebRequest.php:819
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:548
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition: Xml.php:598
return true
Definition: router.php:90
if(!file_exists( $CREDITS)) $lines