MediaWiki  master
HTMLFormField.php
Go to the documentation of this file.
1 <?php
2 
9 abstract class HTMLFormField {
11  public $mParams;
12 
14  protected $mFilterCallback;
15  protected $mName;
16  protected $mDir;
17  protected $mLabel; # String label, as HTML. Set on construction.
18  protected $mID;
19  protected $mClass = '';
20  protected $mVFormClass = '';
21  protected $mHelpClass = false;
22  protected $mDefault;
26  protected $mOptions = false;
27  protected $mOptionsLabelsNotFromMessage = false;
31  protected $mCondState = [];
32  protected $mCondStateClass = [];
33 
38  protected $mShowEmptyLabels = true;
39 
43  public $mParent;
44 
55  abstract public function getInputHTML( $value );
56 
65  public function getInputOOUI( $value ) {
66  return false;
67  }
68 
75  public function canDisplayErrors() {
76  return $this->hasVisibleOutput();
77  }
78 
91  public function msg( $key, ...$params ) {
92  if ( $this->mParent ) {
93  return $this->mParent->msg( $key, ...$params );
94  }
95  return wfMessage( $key, ...$params );
96  }
97 
105  public function hasVisibleOutput() {
106  return true;
107  }
108 
115  public function getName() {
116  return $this->mName;
117  }
118 
130  protected function getNearestField( $name, $backCompat = false ) {
131  // When the field is belong to a HTMLFormFieldCloner
132  if ( isset( $this->mParams['cloner'] ) ) {
133  $field = $this->mParams['cloner']->findNearestField( $this, $name );
134  if ( $field ) {
135  return $field;
136  }
137  }
138 
139  if ( $backCompat && substr( $name, 0, 2 ) === 'wp' &&
140  !$this->mParent->hasField( $name )
141  ) {
142  // Don't break the existed use cases.
143  return $this->mParent->getField( substr( $name, 2 ) );
144  }
145  return $this->mParent->getField( $name );
146  }
147 
159  protected function getNearestFieldValue( $alldata, $name, $asDisplay = false, $backCompat = false ) {
160  $field = $this->getNearestField( $name, $backCompat );
161  // When the field is belong to a HTMLFormFieldCloner
162  if ( isset( $field->mParams['cloner'] ) ) {
163  $value = $field->mParams['cloner']->extractFieldData( $field, $alldata );
164  } else {
165  $value = $alldata[$field->mParams['fieldname']];
166  }
167 
168  // Check invert state for HTMLCheckField
169  if ( $asDisplay && $field instanceof HTMLCheckField && ( $field->mParams['invert'] ?? false ) ) {
170  $value = !$value;
171  }
172 
173  return $value;
174  }
175 
186  protected function getNearestFieldByName( $alldata, $name, $asDisplay = false ) {
187  return (string)$this->getNearestFieldValue( $alldata, $name, $asDisplay );
188  }
189 
197  protected function validateCondState( $params ) {
198  $origParams = $params;
199  $op = array_shift( $params );
200 
201  try {
202  switch ( $op ) {
203  case 'NOT':
204  if ( count( $params ) !== 1 ) {
205  throw new MWException( "NOT takes exactly one parameter" );
206  }
207  // Fall-through intentionally
208 
209  case 'AND':
210  case 'OR':
211  case 'NAND':
212  case 'NOR':
213  foreach ( $params as $i => $p ) {
214  if ( !is_array( $p ) ) {
215  $type = gettype( $p );
216  throw new MWException( "Expected array, found $type at index $i" );
217  }
218  $this->validateCondState( $p );
219  }
220  break;
221 
222  case '===':
223  case '!==':
224  if ( count( $params ) !== 2 ) {
225  throw new MWException( "$op takes exactly two parameters" );
226  }
227  list( $name, $value ) = $params;
228  if ( !is_string( $name ) || !is_string( $value ) ) {
229  throw new MWException( "Parameters for $op must be strings" );
230  }
231  break;
232 
233  default:
234  throw new MWException( "Unknown operation" );
235  }
236  } catch ( MWException $ex ) {
237  throw new MWException(
238  "Invalid hide-if or disable-if specification for $this->mName: " .
239  $ex->getMessage() . " in " . var_export( $origParams, true ),
240  0, $ex
241  );
242  }
243  }
244 
253  protected function checkStateRecurse( array $alldata, array $params ) {
254  $origParams = $params;
255  $op = array_shift( $params );
256  $valueChk = [ 'AND' => false, 'OR' => true, 'NAND' => false, 'NOR' => true ];
257  $valueRet = [ 'AND' => true, 'OR' => false, 'NAND' => false, 'NOR' => true ];
258 
259  switch ( $op ) {
260  case 'AND':
261  case 'OR':
262  case 'NAND':
263  case 'NOR':
264  foreach ( $params as $i => $p ) {
265  if ( $valueChk[$op] === $this->checkStateRecurse( $alldata, $p ) ) {
266  return !$valueRet[$op];
267  }
268  }
269  return $valueRet[$op];
270 
271  case 'NOT':
272  return !$this->checkStateRecurse( $alldata, $params[0] );
273 
274  case '===':
275  case '!==':
276  list( $field, $value ) = $params;
277  $testValue = (string)$this->getNearestFieldValue( $alldata, $field, true, true );
278  switch ( $op ) {
279  case '===':
280  return ( $value === $testValue );
281  case '!==':
282  return ( $value !== $testValue );
283  }
284  }
285  }
286 
295  protected function parseCondState( $params ) {
296  $origParams = $params;
297  $op = array_shift( $params );
298 
299  switch ( $op ) {
300  case 'AND':
301  case 'OR':
302  case 'NAND':
303  case 'NOR':
304  $ret = [ $op ];
305  foreach ( $params as $i => $p ) {
306  $ret[] = $this->parseCondState( $p );
307  }
308  return $ret;
309 
310  case 'NOT':
311  return [ 'NOT', $this->parseCondState( $params[0] ) ];
312 
313  case '===':
314  case '!==':
315  list( $name, $value ) = $params;
316  $field = $this->getNearestField( $name, true );
317  return [ $op, $field->getName(), $value ];
318  }
319  }
320 
326  protected function parseCondStateForClient() {
327  $parsed = [];
328  foreach ( $this->mCondState as $type => $params ) {
329  $parsed[$type] = $this->parseCondState( $params );
330  }
331  return $parsed;
332  }
333 
342  public function isHidden( $alldata ) {
343  if ( !( $this->mCondState && isset( $this->mCondState['hide'] ) ) ) {
344  return false;
345  }
346 
347  return $this->checkStateRecurse( $alldata, $this->mCondState['hide'] );
348  }
349 
358  public function isDisabled( $alldata ) {
359  if ( $this->mParams['disabled'] ?? false ) {
360  return true;
361  }
362  $hidden = $this->isHidden( $alldata );
363  if ( !$this->mCondState || !isset( $this->mCondState['disable'] ) ) {
364  return $hidden;
365  }
366 
367  return $hidden || $this->checkStateRecurse( $alldata, $this->mCondState['disable'] );
368  }
369 
381  public function cancelSubmit( $value, $alldata ) {
382  return false;
383  }
384 
397  public function validate( $value, $alldata ) {
398  if ( $this->isHidden( $alldata ) ) {
399  return true;
400  }
401 
402  if ( isset( $this->mParams['required'] )
403  && $this->mParams['required'] !== false
404  && ( $value === '' || $value === false )
405  ) {
406  return $this->msg( 'htmlform-required' );
407  }
408 
409  if ( isset( $this->mValidationCallback ) ) {
410  return ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
411  }
412 
413  return true;
414  }
415 
424  public function filter( $value, $alldata ) {
425  if ( isset( $this->mFilterCallback ) ) {
426  $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
427  }
428 
429  return $value;
430  }
431 
439  protected function needsLabel() {
440  return true;
441  }
442 
452  public function setShowEmptyLabel( $show ) {
453  $this->mShowEmptyLabels = $show;
454  }
455 
467  protected function isSubmitAttempt( WebRequest $request ) {
468  // HTMLForm would add a hidden field of edit token for forms that require to be posted.
469  return $request->wasPosted() && $request->getCheck( 'wpEditToken' )
470  // The identifier matching or not has been checked in HTMLForm::prepareForm()
471  || $request->getCheck( 'wpFormIdentifier' );
472  }
473 
482  public function loadDataFromRequest( $request ) {
483  if ( $request->getCheck( $this->mName ) ) {
484  return $request->getText( $this->mName );
485  } else {
486  return $this->getDefault();
487  }
488  }
489 
499  public function __construct( $params ) {
500  $this->mParams = $params;
501 
502  if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
503  $this->mParent = $params['parent'];
504  }
505 
506  # Generate the label from a message, if possible
507  if ( isset( $params['label-message'] ) ) {
508  $this->mLabel = $this->getMessage( $params['label-message'] )->parse();
509  } elseif ( isset( $params['label'] ) ) {
510  if ( $params['label'] === '&#160;' || $params['label'] === "\u{00A0}" ) {
511  // Apparently some things set &nbsp directly and in an odd format
512  $this->mLabel = "\u{00A0}";
513  } else {
514  $this->mLabel = htmlspecialchars( $params['label'] );
515  }
516  } elseif ( isset( $params['label-raw'] ) ) {
517  $this->mLabel = $params['label-raw'];
518  }
519 
520  $this->mName = "wp{$params['fieldname']}";
521  if ( isset( $params['name'] ) ) {
522  $this->mName = $params['name'];
523  }
524 
525  if ( isset( $params['dir'] ) ) {
526  $this->mDir = $params['dir'];
527  }
528 
529  $this->mID = "mw-input-{$this->mName}";
530 
531  if ( isset( $params['default'] ) ) {
532  $this->mDefault = $params['default'];
533  }
534 
535  if ( isset( $params['id'] ) ) {
536  $this->mID = $params['id'];
537  }
538 
539  if ( isset( $params['cssclass'] ) ) {
540  $this->mClass = $params['cssclass'];
541  }
542 
543  if ( isset( $params['csshelpclass'] ) ) {
544  $this->mHelpClass = $params['csshelpclass'];
545  }
546 
547  if ( isset( $params['validation-callback'] ) ) {
548  $this->mValidationCallback = $params['validation-callback'];
549  }
550 
551  if ( isset( $params['filter-callback'] ) ) {
552  $this->mFilterCallback = $params['filter-callback'];
553  }
554 
555  if ( isset( $params['hidelabel'] ) ) {
556  $this->mShowEmptyLabels = false;
557  }
558 
559  if ( isset( $params['hide-if'] ) && $params['hide-if'] ) {
560  $this->validateCondState( $params['hide-if'] );
561  $this->mCondState['hide'] = $params['hide-if'];
562  $this->mCondStateClass[] = 'mw-htmlform-hide-if';
563  }
564  if ( !( isset( $params['disabled'] ) && $params['disabled'] ) &&
565  isset( $params['disable-if'] ) && $params['disable-if']
566  ) {
567  $this->validateCondState( $params['disable-if'] );
568  $this->mCondState['disable'] = $params['disable-if'];
569  $this->mCondStateClass[] = 'mw-htmlform-disable-if';
570  }
571  }
572 
582  public function getTableRow( $value ) {
583  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
584  $inputHtml = $this->getInputHTML( $value );
585  $fieldType = $this->getClassName();
586  $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
587  $cellAttributes = [];
588  $rowAttributes = [];
589  $rowClasses = '';
590 
591  if ( !empty( $this->mParams['vertical-label'] ) ) {
592  $cellAttributes['colspan'] = 2;
593  $verticalLabel = true;
594  } else {
595  $verticalLabel = false;
596  }
597 
598  $label = $this->getLabelHtml( $cellAttributes );
599 
600  $field = Html::rawElement(
601  'td',
602  [ 'class' => 'mw-input' ] + $cellAttributes,
603  $inputHtml . "\n$errors"
604  );
605 
606  if ( $this->mCondState ) {
607  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
608  $rowClasses .= implode( ' ', $this->mCondStateClass );
609  }
610 
611  if ( $verticalLabel ) {
612  $html = Html::rawElement( 'tr',
613  $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
614  $html .= Html::rawElement( 'tr',
615  $rowAttributes + [
616  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
617  ],
618  $field );
619  } else {
620  $html = Html::rawElement( 'tr',
621  $rowAttributes + [
622  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
623  ],
624  $label . $field );
625  }
626 
627  return $html . $helptext;
628  }
629 
640  public function getDiv( $value ) {
641  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
642  $inputHtml = $this->getInputHTML( $value );
643  $fieldType = $this->getClassName();
644  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
645  $cellAttributes = [];
646  $label = $this->getLabelHtml( $cellAttributes );
647 
648  $outerDivClass = [
649  'mw-input',
650  'mw-htmlform-nolabel' => ( $label === '' )
651  ];
652 
653  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
654 
655  if ( $horizontalLabel ) {
656  $field = "\u{00A0}" . $inputHtml . "\n$errors";
657  } else {
658  $field = Html::rawElement(
659  'div',
660  // @phan-suppress-next-line PhanUselessBinaryAddRight
661  [ 'class' => $outerDivClass ] + $cellAttributes,
662  $inputHtml . "\n$errors"
663  );
664  }
665  $divCssClasses = [ "mw-htmlform-field-$fieldType",
666  $this->mClass, $this->mVFormClass, $errorClass ];
667 
668  $wrapperAttributes = [
669  'class' => $divCssClasses,
670  ];
671  if ( $this->mCondState ) {
672  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
673  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
674  }
675  $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
676  $html .= $helptext;
677 
678  return $html;
679  }
680 
690  public function getOOUI( $value ) {
691  $inputField = $this->getInputOOUI( $value );
692 
693  if ( !$inputField ) {
694  // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
695  // generate the whole field, label and errors and all, then wrap it in a Widget.
696  // It might look weird, but it'll work OK.
697  return $this->getFieldLayoutOOUI(
698  new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
699  [ 'align' => 'top' ]
700  );
701  }
702 
703  $infusable = true;
704  if ( is_string( $inputField ) ) {
705  // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
706  // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
707  // JavaScript doesn't know how to rebuilt the contents.
708  $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
709  $infusable = false;
710  }
711 
712  $fieldType = $this->getClassName();
713  $help = $this->getHelpText();
714  $errors = $this->getErrorsRaw( $value );
715  foreach ( $errors as &$error ) {
716  $error = new OOUI\HtmlSnippet( $error );
717  }
718 
719  $config = [
720  'classes' => [ "mw-htmlform-field-$fieldType" ],
721  'align' => $this->getLabelAlignOOUI(),
722  'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
723  'errors' => $errors,
724  'infusable' => $infusable,
725  'helpInline' => $this->isHelpInline(),
726  ];
727  if ( $this->mClass !== '' ) {
728  $config['classes'][] = $this->mClass;
729  }
730 
731  $preloadModules = false;
732 
733  if ( $infusable && $this->shouldInfuseOOUI() ) {
734  $preloadModules = true;
735  $config['classes'][] = 'mw-htmlform-autoinfuse';
736  }
737  if ( $this->mCondState ) {
738  $config['classes'] = array_merge( $config['classes'], $this->mCondStateClass );
739  }
740 
741  // the element could specify, that the label doesn't need to be added
742  $label = $this->getLabel();
743  if ( $label && $label !== "\u{00A0}" && $label !== '&#160;' ) {
744  $config['label'] = new OOUI\HtmlSnippet( $label );
745  }
746 
747  if ( $this->mCondState ) {
748  $preloadModules = true;
749  $config['condState'] = $this->parseCondStateForClient();
750  }
751 
752  $config['modules'] = $this->getOOUIModules();
753 
754  if ( $preloadModules ) {
755  $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
756  $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
757  }
758 
759  return $this->getFieldLayoutOOUI( $inputField, $config );
760  }
761 
769  protected function getClassName() {
770  $name = explode( '\\', static::class );
771  return end( $name );
772  }
773 
779  protected function getLabelAlignOOUI() {
780  return 'top';
781  }
782 
789  protected function getFieldLayoutOOUI( $inputField, $config ) {
790  return new HTMLFormFieldLayout( $inputField, $config );
791  }
792 
801  protected function shouldInfuseOOUI() {
802  // Always infuse fields with popup help text, since the interface for it is nicer with JS
803  return $this->getHelpText() !== null && !$this->isHelpInline();
804  }
805 
813  protected function getOOUIModules() {
814  return [];
815  }
816 
827  public function getRaw( $value ) {
828  list( $errors, ) = $this->getErrorsAndErrorClass( $value );
829  $inputHtml = $this->getInputHTML( $value );
830  $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
831  $cellAttributes = [];
832  $label = $this->getLabelHtml( $cellAttributes );
833 
834  $html = "\n$errors";
835  $html .= $label;
836  $html .= $inputHtml;
837  $html .= $helptext;
838 
839  return $html;
840  }
841 
851  public function getVForm( $value ) {
852  // Ewwww
853  $this->mVFormClass = ' mw-ui-vform-field';
854  return $this->getDiv( $value );
855  }
856 
864  public function getInline( $value ) {
865  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
866  $inputHtml = $this->getInputHTML( $value );
867  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
868  $cellAttributes = [];
869  $label = $this->getLabelHtml( $cellAttributes );
870 
871  $html = "\n" . $errors .
872  $label . "\u{00A0}" .
873  $inputHtml .
874  $helptext;
875 
876  return $html;
877  }
878 
886  public function getHelpTextHtmlTable( $helptext ) {
887  if ( $helptext === null ) {
888  return '';
889  }
890 
891  $rowAttributes = [];
892  if ( $this->mCondState ) {
893  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
894  $rowAttributes['class'] = $this->mCondStateClass;
895  }
896 
897  $tdClasses = [ 'htmlform-tip' ];
898  if ( $this->mHelpClass !== false ) {
899  $tdClasses[] = $this->mHelpClass;
900  }
901  $row = Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext );
902  $row = Html::rawElement( 'tr', $rowAttributes, $row );
903 
904  return $row;
905  }
906 
915  public function getHelpTextHtmlDiv( $helptext ) {
916  if ( $helptext === null ) {
917  return '';
918  }
919 
920  $wrapperAttributes = [
921  'class' => [ 'htmlform-tip' ],
922  ];
923  if ( $this->mHelpClass !== false ) {
924  $wrapperAttributes['class'][] = $this->mHelpClass;
925  }
926  if ( $this->mCondState ) {
927  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
928  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
929  }
930  $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
931 
932  return $div;
933  }
934 
942  public function getHelpTextHtmlRaw( $helptext ) {
943  return $this->getHelpTextHtmlDiv( $helptext );
944  }
945 
952  public function getHelpText() {
953  $helptext = null;
954 
955  if ( isset( $this->mParams['help-message'] ) ) {
956  $this->mParams['help-messages'] = [ $this->mParams['help-message'] ];
957  }
958 
959  if ( isset( $this->mParams['help-messages'] ) ) {
960  foreach ( $this->mParams['help-messages'] as $msg ) {
961  $msg = $this->getMessage( $msg );
962 
963  if ( $msg->exists() ) {
964  if ( $helptext === null ) {
965  $helptext = '';
966  } else {
967  $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
968  }
969  $helptext .= $msg->parse(); // Append message
970  }
971  }
972  } elseif ( isset( $this->mParams['help'] ) ) {
973  $helptext = $this->mParams['help'];
974  }
975 
976  return $helptext;
977  }
978 
987  public function isHelpInline() {
988  return $this->mParams['help-inline'] ?? true;
989  }
990 
1003  public function getErrorsAndErrorClass( $value ) {
1004  $errors = $this->validate( $value, $this->mParent->mFieldData );
1005 
1006  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1007  $errors = '';
1008  $errorClass = '';
1009  } else {
1010  $errors = self::formatErrors( $errors );
1011  $errorClass = 'mw-htmlform-invalid-input';
1012  }
1013 
1014  return [ $errors, $errorClass ];
1015  }
1016 
1024  public function getErrorsRaw( $value ) {
1025  $errors = $this->validate( $value, $this->mParent->mFieldData );
1026 
1027  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1028  $errors = [];
1029  }
1030 
1031  if ( !is_array( $errors ) ) {
1032  $errors = [ $errors ];
1033  }
1034  foreach ( $errors as &$error ) {
1035  if ( $error instanceof Message ) {
1036  $error = $error->parse();
1037  }
1038  }
1039 
1040  return $errors;
1041  }
1042 
1047  public function getLabel() {
1048  return $this->mLabel ?? '';
1049  }
1050 
1057  public function getLabelHtml( $cellAttributes = [] ) {
1058  # Don't output a for= attribute for labels with no associated input.
1059  # Kind of hacky here, possibly we don't want these to be <label>s at all.
1060  $for = [];
1061 
1062  if ( $this->needsLabel() ) {
1063  $for['for'] = $this->mID;
1064  }
1065 
1066  $labelValue = trim( $this->getLabel() );
1067  $hasLabel = false;
1068  if ( $labelValue !== "\u{00A0}" && $labelValue !== '&#160;' && $labelValue !== '' ) {
1069  $hasLabel = true;
1070  }
1071 
1072  $displayFormat = $this->mParent->getDisplayFormat();
1073  $html = '';
1074  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
1075 
1076  if ( $displayFormat === 'table' ) {
1077  $html =
1078  Html::rawElement( 'td',
1079  [ 'class' => 'mw-label' ] + $cellAttributes,
1080  Html::rawElement( 'label', $for, $labelValue ) );
1081  } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
1082  if ( $displayFormat === 'div' && !$horizontalLabel ) {
1083  $html =
1084  Html::rawElement( 'div',
1085  [ 'class' => 'mw-label' ] + $cellAttributes,
1086  Html::rawElement( 'label', $for, $labelValue ) );
1087  } else {
1088  $html = Html::rawElement( 'label', $for, $labelValue );
1089  }
1090  }
1091 
1092  return $html;
1093  }
1094 
1099  public function getDefault() {
1100  return $this->mDefault ?? null;
1101  }
1102 
1108  public function getTooltipAndAccessKey() {
1109  if ( empty( $this->mParams['tooltip'] ) ) {
1110  return [];
1111  }
1112 
1113  return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
1114  }
1115 
1121  public function getTooltipAndAccessKeyOOUI() {
1122  if ( empty( $this->mParams['tooltip'] ) ) {
1123  return [];
1124  }
1125 
1126  return [
1127  'title' => Linker::titleAttrib( $this->mParams['tooltip'] ),
1128  'accessKey' => Linker::accesskey( $this->mParams['tooltip'] ),
1129  ];
1130  }
1131 
1139  public function getAttributes( array $list ) {
1140  static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
1141 
1142  $ret = [];
1143  foreach ( $list as $key ) {
1144  if ( in_array( $key, $boolAttribs ) ) {
1145  if ( !empty( $this->mParams[$key] ) ) {
1146  $ret[$key] = '';
1147  }
1148  } elseif ( isset( $this->mParams[$key] ) ) {
1149  $ret[$key] = $this->mParams[$key];
1150  }
1151  }
1152 
1153  return $ret;
1154  }
1155 
1165  private function lookupOptionsKeys( $options, $needsParse ) {
1166  $ret = [];
1167  foreach ( $options as $key => $value ) {
1168  $msg = $this->msg( $key );
1169  $key = $needsParse ? $msg->parse() : $msg->plain();
1170  $ret[$key] = is_array( $value )
1171  ? $this->lookupOptionsKeys( $value, $needsParse )
1172  : strval( $value );
1173  }
1174  return $ret;
1175  }
1176 
1184  public static function forceToStringRecursive( $array ) {
1185  if ( is_array( $array ) ) {
1186  return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1187  } else {
1188  return strval( $array );
1189  }
1190  }
1191 
1198  public function getOptions() {
1199  if ( $this->mOptions === false ) {
1200  if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1201  $needsParse = $this->mParams['options-messages-parse'] ?? false;
1202  if ( $needsParse ) {
1203  $this->mOptionsLabelsNotFromMessage = true;
1204  }
1205  $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'], $needsParse );
1206  } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1207  $this->mOptionsLabelsNotFromMessage = true;
1208  $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1209  } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1210  $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1211  $this->mOptions = Xml::listDropDownOptions( $message );
1212  } else {
1213  $this->mOptions = null;
1214  }
1215  }
1216 
1217  return $this->mOptions;
1218  }
1219 
1225  public function getOptionsOOUI() {
1226  $oldoptions = $this->getOptions();
1227 
1228  if ( $oldoptions === null ) {
1229  return null;
1230  }
1231 
1232  return Xml::listDropDownOptionsOoui( $oldoptions );
1233  }
1234 
1242  public static function flattenOptions( $options ) {
1243  $flatOpts = [];
1244 
1245  foreach ( $options as $value ) {
1246  if ( is_array( $value ) ) {
1247  $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1248  } else {
1249  $flatOpts[] = $value;
1250  }
1251  }
1252 
1253  return $flatOpts;
1254  }
1255 
1269  protected static function formatErrors( $errors ) {
1270  if ( is_array( $errors ) && count( $errors ) === 1 ) {
1271  $errors = array_shift( $errors );
1272  }
1273 
1274  if ( is_array( $errors ) ) {
1275  $lines = [];
1276  foreach ( $errors as $error ) {
1277  if ( $error instanceof Message ) {
1278  $lines[] = Html::rawElement( 'li', [], $error->parse() );
1279  } else {
1280  $lines[] = Html::rawElement( 'li', [], $error );
1281  }
1282  }
1283 
1284  $errors = Html::rawElement( 'ul', [], implode( "\n", $lines ) );
1285  } else {
1286  if ( $errors instanceof Message ) {
1287  $errors = $errors->parse();
1288  }
1289  }
1290 
1291  return Html::errorBox( $errors );
1292  }
1293 
1300  protected function getMessage( $value ) {
1301  $message = Message::newFromSpecifier( $value );
1302 
1303  if ( $this->mParent ) {
1304  $message->setContext( $this->mParent );
1305  }
1306 
1307  return $message;
1308  }
1309 
1317  public function skipLoadData( $request ) {
1318  return !empty( $this->mParams['nodata'] );
1319  }
1320 
1328  public function needsJSForHtml5FormValidation() {
1329  if ( $this->mCondState ) {
1330  // This is probably more restrictive than it needs to be, but better safe than sorry
1331  return true;
1332  }
1333  return false;
1334  }
1335 }
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...
array bool null $mOptions
msg( $key,... $params)
Get a translated interface message.
lookupOptionsKeys( $options, $needsParse)
Given an array of msg-key => value mappings, returns an array with keys being the message texts.
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:150
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static errorBox( $html, $heading='', $className='')
Return an error box.
Definition: Html.php:788
static accesskey( $name, $localizer=null)
Given the id of an interface element, constructs the appropriate accesskey attribute from the system ...
Definition: Linker.php:2087
static titleAttrib( $name, $options=null, array $msgParams=[], $localizer=null)
Given the id of an interface element, constructs the appropriate title attribute from the system mess...
Definition: Linker.php:2039
static tooltipAndAccesskeyAttribs( $name, array $msgParams=[], $options=null, $localizer=null, $user=null, $config=null, $relevantTitle=null)
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2258
MediaWiki exception.
Definition: MWException.php:29
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition: Message.php:141
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:423
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:43
getCheck( $name)
Return true if the named value is set in the input, whatever that value is (even "0").
Definition: WebRequest.php:689
wasPosted()
Returns true if the present request was reached by a POST operation, false otherwise (GET,...
Definition: WebRequest.php:820
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:550
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition: Xml.php:600
$help
Definition: mcc.php:32
return true
Definition: router.php:90
if(!file_exists( $CREDITS)) $lines