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  $op = array_shift( $params );
255  $valueChk = [ 'AND' => false, 'OR' => true, 'NAND' => false, 'NOR' => true ];
256  $valueRet = [ 'AND' => true, 'OR' => false, 'NAND' => false, 'NOR' => true ];
257 
258  switch ( $op ) {
259  case 'AND':
260  case 'OR':
261  case 'NAND':
262  case 'NOR':
263  foreach ( $params as $p ) {
264  if ( $valueChk[$op] === $this->checkStateRecurse( $alldata, $p ) ) {
265  return !$valueRet[$op];
266  }
267  }
268  return $valueRet[$op];
269 
270  case 'NOT':
271  return !$this->checkStateRecurse( $alldata, $params[0] );
272 
273  case '===':
274  case '!==':
275  list( $field, $value ) = $params;
276  $testValue = (string)$this->getNearestFieldValue( $alldata, $field, true, true );
277  switch ( $op ) {
278  case '===':
279  return ( $value === $testValue );
280  case '!==':
281  return ( $value !== $testValue );
282  }
283  }
284  }
285 
294  protected function parseCondState( $params ) {
295  $op = array_shift( $params );
296 
297  switch ( $op ) {
298  case 'AND':
299  case 'OR':
300  case 'NAND':
301  case 'NOR':
302  $ret = [ $op ];
303  foreach ( $params as $p ) {
304  $ret[] = $this->parseCondState( $p );
305  }
306  return $ret;
307 
308  case 'NOT':
309  return [ 'NOT', $this->parseCondState( $params[0] ) ];
310 
311  case '===':
312  case '!==':
313  list( $name, $value ) = $params;
314  $field = $this->getNearestField( $name, true );
315  return [ $op, $field->getName(), $value ];
316  }
317  }
318 
324  protected function parseCondStateForClient() {
325  $parsed = [];
326  foreach ( $this->mCondState as $type => $params ) {
327  $parsed[$type] = $this->parseCondState( $params );
328  }
329  return $parsed;
330  }
331 
340  public function isHidden( $alldata ) {
341  if ( !( $this->mCondState && isset( $this->mCondState['hide'] ) ) ) {
342  return false;
343  }
344 
345  return $this->checkStateRecurse( $alldata, $this->mCondState['hide'] );
346  }
347 
356  public function isDisabled( $alldata ) {
357  if ( $this->mParams['disabled'] ?? false ) {
358  return true;
359  }
360  $hidden = $this->isHidden( $alldata );
361  if ( !$this->mCondState || !isset( $this->mCondState['disable'] ) ) {
362  return $hidden;
363  }
364 
365  return $hidden || $this->checkStateRecurse( $alldata, $this->mCondState['disable'] );
366  }
367 
379  public function cancelSubmit( $value, $alldata ) {
380  return false;
381  }
382 
395  public function validate( $value, $alldata ) {
396  if ( $this->isHidden( $alldata ) ) {
397  return true;
398  }
399 
400  if ( isset( $this->mParams['required'] )
401  && $this->mParams['required'] !== false
402  && ( $value === '' || $value === false )
403  ) {
404  return $this->msg( 'htmlform-required' );
405  }
406 
407  if ( isset( $this->mValidationCallback ) ) {
408  return ( $this->mValidationCallback )( $value, $alldata, $this->mParent );
409  }
410 
411  return true;
412  }
413 
422  public function filter( $value, $alldata ) {
423  if ( isset( $this->mFilterCallback ) ) {
424  $value = ( $this->mFilterCallback )( $value, $alldata, $this->mParent );
425  }
426 
427  return $value;
428  }
429 
437  protected function needsLabel() {
438  return true;
439  }
440 
450  public function setShowEmptyLabel( $show ) {
451  $this->mShowEmptyLabels = $show;
452  }
453 
465  protected function isSubmitAttempt( WebRequest $request ) {
466  // HTMLForm would add a hidden field of edit token for forms that require to be posted.
467  return $request->wasPosted() && $request->getCheck( 'wpEditToken' )
468  // The identifier matching or not has been checked in HTMLForm::prepareForm()
469  || $request->getCheck( 'wpFormIdentifier' );
470  }
471 
480  public function loadDataFromRequest( $request ) {
481  if ( $request->getCheck( $this->mName ) ) {
482  return $request->getText( $this->mName );
483  } else {
484  return $this->getDefault();
485  }
486  }
487 
497  public function __construct( $params ) {
498  $this->mParams = $params;
499 
500  if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
501  $this->mParent = $params['parent'];
502  }
503 
504  # Generate the label from a message, if possible
505  if ( isset( $params['label-message'] ) ) {
506  $this->mLabel = $this->getMessage( $params['label-message'] )->parse();
507  } elseif ( isset( $params['label'] ) ) {
508  if ( $params['label'] === '&#160;' || $params['label'] === "\u{00A0}" ) {
509  // Apparently some things set &nbsp directly and in an odd format
510  $this->mLabel = "\u{00A0}";
511  } else {
512  $this->mLabel = htmlspecialchars( $params['label'] );
513  }
514  } elseif ( isset( $params['label-raw'] ) ) {
515  $this->mLabel = $params['label-raw'];
516  }
517 
518  $this->mName = "wp{$params['fieldname']}";
519  if ( isset( $params['name'] ) ) {
520  $this->mName = $params['name'];
521  }
522 
523  if ( isset( $params['dir'] ) ) {
524  $this->mDir = $params['dir'];
525  }
526 
527  $this->mID = "mw-input-{$this->mName}";
528 
529  if ( isset( $params['default'] ) ) {
530  $this->mDefault = $params['default'];
531  }
532 
533  if ( isset( $params['id'] ) ) {
534  $this->mID = $params['id'];
535  }
536 
537  if ( isset( $params['cssclass'] ) ) {
538  $this->mClass = $params['cssclass'];
539  }
540 
541  if ( isset( $params['csshelpclass'] ) ) {
542  $this->mHelpClass = $params['csshelpclass'];
543  }
544 
545  if ( isset( $params['validation-callback'] ) ) {
546  $this->mValidationCallback = $params['validation-callback'];
547  }
548 
549  if ( isset( $params['filter-callback'] ) ) {
550  $this->mFilterCallback = $params['filter-callback'];
551  }
552 
553  if ( isset( $params['hidelabel'] ) ) {
554  $this->mShowEmptyLabels = false;
555  }
556 
557  if ( isset( $params['hide-if'] ) && $params['hide-if'] ) {
558  $this->validateCondState( $params['hide-if'] );
559  $this->mCondState['hide'] = $params['hide-if'];
560  $this->mCondStateClass[] = 'mw-htmlform-hide-if';
561  }
562  if ( !( isset( $params['disabled'] ) && $params['disabled'] ) &&
563  isset( $params['disable-if'] ) && $params['disable-if']
564  ) {
565  $this->validateCondState( $params['disable-if'] );
566  $this->mCondState['disable'] = $params['disable-if'];
567  $this->mCondStateClass[] = 'mw-htmlform-disable-if';
568  }
569  }
570 
580  public function getTableRow( $value ) {
581  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
582  $inputHtml = $this->getInputHTML( $value );
583  $fieldType = $this->getClassName();
584  $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
585  $cellAttributes = [];
586  $rowAttributes = [];
587  $rowClasses = '';
588 
589  if ( !empty( $this->mParams['vertical-label'] ) ) {
590  $cellAttributes['colspan'] = 2;
591  $verticalLabel = true;
592  } else {
593  $verticalLabel = false;
594  }
595 
596  $label = $this->getLabelHtml( $cellAttributes );
597 
598  $field = Html::rawElement(
599  'td',
600  [ 'class' => 'mw-input' ] + $cellAttributes,
601  $inputHtml . "\n$errors"
602  );
603 
604  if ( $this->mCondState ) {
605  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
606  $rowClasses .= implode( ' ', $this->mCondStateClass );
607  }
608 
609  if ( $verticalLabel ) {
610  $html = Html::rawElement( 'tr',
611  $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
612  $html .= Html::rawElement( 'tr',
613  $rowAttributes + [
614  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
615  ],
616  $field );
617  } else {
618  $html = Html::rawElement( 'tr',
619  $rowAttributes + [
620  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
621  ],
622  $label . $field );
623  }
624 
625  return $html . $helptext;
626  }
627 
638  public function getDiv( $value ) {
639  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
640  $inputHtml = $this->getInputHTML( $value );
641  $fieldType = $this->getClassName();
642  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
643  $cellAttributes = [];
644  $label = $this->getLabelHtml( $cellAttributes );
645 
646  $outerDivClass = [
647  'mw-input',
648  'mw-htmlform-nolabel' => ( $label === '' )
649  ];
650 
651  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
652 
653  if ( $horizontalLabel ) {
654  $field = "\u{00A0}" . $inputHtml . "\n$errors";
655  } else {
656  $field = Html::rawElement(
657  'div',
658  // @phan-suppress-next-line PhanUselessBinaryAddRight
659  [ 'class' => $outerDivClass ] + $cellAttributes,
660  $inputHtml . "\n$errors"
661  );
662  }
663  $divCssClasses = [ "mw-htmlform-field-$fieldType",
664  $this->mClass, $this->mVFormClass, $errorClass ];
665 
666  $wrapperAttributes = [
667  'class' => $divCssClasses,
668  ];
669  if ( $this->mCondState ) {
670  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
671  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
672  }
673  $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
674  $html .= $helptext;
675 
676  return $html;
677  }
678 
688  public function getOOUI( $value ) {
689  $inputField = $this->getInputOOUI( $value );
690 
691  if ( !$inputField ) {
692  // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
693  // generate the whole field, label and errors and all, then wrap it in a Widget.
694  // It might look weird, but it'll work OK.
695  return $this->getFieldLayoutOOUI(
696  new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
697  [ 'align' => 'top' ]
698  );
699  }
700 
701  $infusable = true;
702  if ( is_string( $inputField ) ) {
703  // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
704  // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
705  // JavaScript doesn't know how to rebuilt the contents.
706  $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
707  $infusable = false;
708  }
709 
710  $fieldType = $this->getClassName();
711  $help = $this->getHelpText();
712  $errors = $this->getErrorsRaw( $value );
713  foreach ( $errors as &$error ) {
714  $error = new OOUI\HtmlSnippet( $error );
715  }
716 
717  $config = [
718  'classes' => [ "mw-htmlform-field-$fieldType" ],
719  'align' => $this->getLabelAlignOOUI(),
720  'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
721  'errors' => $errors,
722  'infusable' => $infusable,
723  'helpInline' => $this->isHelpInline(),
724  ];
725  if ( $this->mClass !== '' ) {
726  $config['classes'][] = $this->mClass;
727  }
728 
729  $preloadModules = false;
730 
731  if ( $infusable && $this->shouldInfuseOOUI() ) {
732  $preloadModules = true;
733  $config['classes'][] = 'mw-htmlform-autoinfuse';
734  }
735  if ( $this->mCondState ) {
736  $config['classes'] = array_merge( $config['classes'], $this->mCondStateClass );
737  }
738 
739  // the element could specify, that the label doesn't need to be added
740  $label = $this->getLabel();
741  if ( $label && $label !== "\u{00A0}" && $label !== '&#160;' ) {
742  $config['label'] = new OOUI\HtmlSnippet( $label );
743  }
744 
745  if ( $this->mCondState ) {
746  $preloadModules = true;
747  $config['condState'] = $this->parseCondStateForClient();
748  }
749 
750  $config['modules'] = $this->getOOUIModules();
751 
752  if ( $preloadModules ) {
753  $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
754  $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
755  }
756 
757  return $this->getFieldLayoutOOUI( $inputField, $config );
758  }
759 
767  protected function getClassName() {
768  $name = explode( '\\', static::class );
769  return end( $name );
770  }
771 
777  protected function getLabelAlignOOUI() {
778  return 'top';
779  }
780 
787  protected function getFieldLayoutOOUI( $inputField, $config ) {
788  return new HTMLFormFieldLayout( $inputField, $config );
789  }
790 
799  protected function shouldInfuseOOUI() {
800  // Always infuse fields with popup help text, since the interface for it is nicer with JS
801  return $this->getHelpText() !== null && !$this->isHelpInline();
802  }
803 
811  protected function getOOUIModules() {
812  return [];
813  }
814 
825  public function getRaw( $value ) {
826  list( $errors, ) = $this->getErrorsAndErrorClass( $value );
827  $inputHtml = $this->getInputHTML( $value );
828  $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
829  $cellAttributes = [];
830  $label = $this->getLabelHtml( $cellAttributes );
831 
832  $html = "\n$errors";
833  $html .= $label;
834  $html .= $inputHtml;
835  $html .= $helptext;
836 
837  return $html;
838  }
839 
849  public function getVForm( $value ) {
850  // Ewwww
851  $this->mVFormClass = ' mw-ui-vform-field';
852  return $this->getDiv( $value );
853  }
854 
862  public function getInline( $value ) {
863  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
864  $inputHtml = $this->getInputHTML( $value );
865  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
866  $cellAttributes = [];
867  $label = $this->getLabelHtml( $cellAttributes );
868 
869  $html = "\n" . $errors .
870  $label . "\u{00A0}" .
871  $inputHtml .
872  $helptext;
873 
874  return $html;
875  }
876 
884  public function getHelpTextHtmlTable( $helptext ) {
885  if ( $helptext === null ) {
886  return '';
887  }
888 
889  $rowAttributes = [];
890  if ( $this->mCondState ) {
891  $rowAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
892  $rowAttributes['class'] = $this->mCondStateClass;
893  }
894 
895  $tdClasses = [ 'htmlform-tip' ];
896  if ( $this->mHelpClass !== false ) {
897  $tdClasses[] = $this->mHelpClass;
898  }
899  $row = Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext );
900  $row = Html::rawElement( 'tr', $rowAttributes, $row );
901 
902  return $row;
903  }
904 
913  public function getHelpTextHtmlDiv( $helptext ) {
914  if ( $helptext === null ) {
915  return '';
916  }
917 
918  $wrapperAttributes = [
919  'class' => [ 'htmlform-tip' ],
920  ];
921  if ( $this->mHelpClass !== false ) {
922  $wrapperAttributes['class'][] = $this->mHelpClass;
923  }
924  if ( $this->mCondState ) {
925  $wrapperAttributes['data-cond-state'] = FormatJson::encode( $this->parseCondStateForClient() );
926  $wrapperAttributes['class'] = array_merge( $wrapperAttributes['class'], $this->mCondStateClass );
927  }
928  $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
929 
930  return $div;
931  }
932 
940  public function getHelpTextHtmlRaw( $helptext ) {
941  return $this->getHelpTextHtmlDiv( $helptext );
942  }
943 
950  public function getHelpText() {
951  $helptext = null;
952 
953  if ( isset( $this->mParams['help-message'] ) ) {
954  $this->mParams['help-messages'] = [ $this->mParams['help-message'] ];
955  }
956 
957  if ( isset( $this->mParams['help-messages'] ) ) {
958  foreach ( $this->mParams['help-messages'] as $msg ) {
959  $msg = $this->getMessage( $msg );
960 
961  if ( $msg->exists() ) {
962  if ( $helptext === null ) {
963  $helptext = '';
964  } else {
965  $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
966  }
967  $helptext .= $msg->parse(); // Append message
968  }
969  }
970  } elseif ( isset( $this->mParams['help'] ) ) {
971  $helptext = $this->mParams['help'];
972  }
973 
974  return $helptext;
975  }
976 
985  public function isHelpInline() {
986  return $this->mParams['help-inline'] ?? true;
987  }
988 
1001  public function getErrorsAndErrorClass( $value ) {
1002  $errors = $this->validate( $value, $this->mParent->mFieldData );
1003 
1004  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1005  $errors = '';
1006  $errorClass = '';
1007  } else {
1008  $errors = self::formatErrors( $errors );
1009  $errorClass = 'mw-htmlform-invalid-input';
1010  }
1011 
1012  return [ $errors, $errorClass ];
1013  }
1014 
1022  public function getErrorsRaw( $value ) {
1023  $errors = $this->validate( $value, $this->mParent->mFieldData );
1024 
1025  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
1026  $errors = [];
1027  }
1028 
1029  if ( !is_array( $errors ) ) {
1030  $errors = [ $errors ];
1031  }
1032  foreach ( $errors as &$error ) {
1033  if ( $error instanceof Message ) {
1034  $error = $error->parse();
1035  }
1036  }
1037 
1038  return $errors;
1039  }
1040 
1045  public function getLabel() {
1046  return $this->mLabel ?? '';
1047  }
1048 
1055  public function getLabelHtml( $cellAttributes = [] ) {
1056  # Don't output a for= attribute for labels with no associated input.
1057  # Kind of hacky here, possibly we don't want these to be <label>s at all.
1058  $for = [];
1059 
1060  if ( $this->needsLabel() ) {
1061  $for['for'] = $this->mID;
1062  }
1063 
1064  $labelValue = trim( $this->getLabel() );
1065  $hasLabel = false;
1066  if ( $labelValue !== "\u{00A0}" && $labelValue !== '&#160;' && $labelValue !== '' ) {
1067  $hasLabel = true;
1068  }
1069 
1070  $displayFormat = $this->mParent->getDisplayFormat();
1071  $html = '';
1072  $horizontalLabel = $this->mParams['horizontal-label'] ?? false;
1073 
1074  if ( $displayFormat === 'table' ) {
1075  $html =
1076  Html::rawElement( 'td',
1077  [ 'class' => 'mw-label' ] + $cellAttributes,
1078  Html::rawElement( 'label', $for, $labelValue ) );
1079  } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
1080  if ( $displayFormat === 'div' && !$horizontalLabel ) {
1081  $html =
1082  Html::rawElement( 'div',
1083  [ 'class' => 'mw-label' ] + $cellAttributes,
1084  Html::rawElement( 'label', $for, $labelValue ) );
1085  } else {
1086  $html = Html::rawElement( 'label', $for, $labelValue );
1087  }
1088  }
1089 
1090  return $html;
1091  }
1092 
1097  public function getDefault() {
1098  return $this->mDefault ?? null;
1099  }
1100 
1106  public function getTooltipAndAccessKey() {
1107  if ( empty( $this->mParams['tooltip'] ) ) {
1108  return [];
1109  }
1110 
1111  return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
1112  }
1113 
1119  public function getTooltipAndAccessKeyOOUI() {
1120  if ( empty( $this->mParams['tooltip'] ) ) {
1121  return [];
1122  }
1123 
1124  return [
1125  'title' => Linker::titleAttrib( $this->mParams['tooltip'] ),
1126  'accessKey' => Linker::accesskey( $this->mParams['tooltip'] ),
1127  ];
1128  }
1129 
1137  public function getAttributes( array $list ) {
1138  static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
1139 
1140  $ret = [];
1141  foreach ( $list as $key ) {
1142  if ( in_array( $key, $boolAttribs ) ) {
1143  if ( !empty( $this->mParams[$key] ) ) {
1144  $ret[$key] = '';
1145  }
1146  } elseif ( isset( $this->mParams[$key] ) ) {
1147  $ret[$key] = $this->mParams[$key];
1148  }
1149  }
1150 
1151  return $ret;
1152  }
1153 
1163  private function lookupOptionsKeys( $options, $needsParse ) {
1164  $ret = [];
1165  foreach ( $options as $key => $value ) {
1166  $msg = $this->msg( $key );
1167  $key = $needsParse ? $msg->parse() : $msg->plain();
1168  $ret[$key] = is_array( $value )
1169  ? $this->lookupOptionsKeys( $value, $needsParse )
1170  : strval( $value );
1171  }
1172  return $ret;
1173  }
1174 
1182  public static function forceToStringRecursive( $array ) {
1183  if ( is_array( $array ) ) {
1184  return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1185  } else {
1186  return strval( $array );
1187  }
1188  }
1189 
1196  public function getOptions() {
1197  if ( $this->mOptions === false ) {
1198  if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1199  $needsParse = $this->mParams['options-messages-parse'] ?? false;
1200  if ( $needsParse ) {
1201  $this->mOptionsLabelsNotFromMessage = true;
1202  }
1203  $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'], $needsParse );
1204  } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1205  $this->mOptionsLabelsNotFromMessage = true;
1206  $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1207  } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1208  $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1209  $this->mOptions = Xml::listDropDownOptions( $message );
1210  } else {
1211  $this->mOptions = null;
1212  }
1213  }
1214 
1215  return $this->mOptions;
1216  }
1217 
1223  public function getOptionsOOUI() {
1224  $oldoptions = $this->getOptions();
1225 
1226  if ( $oldoptions === null ) {
1227  return null;
1228  }
1229 
1230  return Xml::listDropDownOptionsOoui( $oldoptions );
1231  }
1232 
1240  public static function flattenOptions( $options ) {
1241  $flatOpts = [];
1242 
1243  foreach ( $options as $value ) {
1244  if ( is_array( $value ) ) {
1245  $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1246  } else {
1247  $flatOpts[] = $value;
1248  }
1249  }
1250 
1251  return $flatOpts;
1252  }
1253 
1267  protected static function formatErrors( $errors ) {
1268  if ( is_array( $errors ) && count( $errors ) === 1 ) {
1269  $errors = array_shift( $errors );
1270  }
1271 
1272  if ( is_array( $errors ) ) {
1273  $lines = [];
1274  foreach ( $errors as $error ) {
1275  if ( $error instanceof Message ) {
1276  $lines[] = Html::rawElement( 'li', [], $error->parse() );
1277  } else {
1278  $lines[] = Html::rawElement( 'li', [], $error );
1279  }
1280  }
1281 
1282  $errors = Html::rawElement( 'ul', [], implode( "\n", $lines ) );
1283  } else {
1284  if ( $errors instanceof Message ) {
1285  $errors = $errors->parse();
1286  }
1287  }
1288 
1289  return Html::errorBox( $errors );
1290  }
1291 
1298  protected function getMessage( $value ) {
1299  $message = Message::newFromSpecifier( $value );
1300 
1301  if ( $this->mParent ) {
1302  $message->setContext( $this->mParent );
1303  }
1304 
1305  return $message;
1306  }
1307 
1315  public function skipLoadData( $request ) {
1316  return !empty( $this->mParams['nodata'] );
1317  }
1318 
1326  public function needsJSForHtml5FormValidation() {
1327  if ( $this->mCondState ) {
1328  // This is probably more restrictive than it needs to be, but better safe than sorry
1329  return true;
1330  }
1331  return false;
1332  }
1333 }
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.
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:2084
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:2036
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:2255
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:44
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:821
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