MediaWiki  1.28.0
HTMLFormField.php
Go to the documentation of this file.
1 <?php
2 
7 abstract class HTMLFormField {
8  public $mParams;
9 
11  protected $mFilterCallback;
12  protected $mName;
13  protected $mDir;
14  protected $mLabel; # String label, as HTML. Set on construction.
15  protected $mID;
16  protected $mClass = '';
17  protected $mVFormClass = '';
18  protected $mHelpClass = false;
19  protected $mDefault;
20  protected $mOptions = false;
21  protected $mOptionsLabelsNotFromMessage = false;
22  protected $mHideIf = null;
23 
28  protected $mShowEmptyLabels = true;
29 
33  public $mParent;
34 
45  abstract function getInputHTML( $value );
46 
54  function getInputOOUI( $value ) {
55  return false;
56  }
57 
63  public function canDisplayErrors() {
64  return $this->hasVisibleOutput();
65  }
66 
77  function msg() {
78  $args = func_get_args();
79 
80  if ( $this->mParent ) {
81  $callback = [ $this->mParent, 'msg' ];
82  } else {
83  $callback = 'wfMessage';
84  }
85 
86  return call_user_func_array( $callback, $args );
87  }
88 
95  public function hasVisibleOutput() {
96  return true;
97  }
98 
112  protected function getNearestFieldByName( $alldata, $name ) {
113  $tmp = $this->mName;
114  $thisKeys = [];
115  while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) {
116  array_unshift( $thisKeys, $m[2] );
117  $tmp = $m[1];
118  }
119  if ( substr( $tmp, 0, 2 ) == 'wp' &&
120  !array_key_exists( $tmp, $alldata ) &&
121  array_key_exists( substr( $tmp, 2 ), $alldata )
122  ) {
123  // Adjust for name mangling.
124  $tmp = substr( $tmp, 2 );
125  }
126  array_unshift( $thisKeys, $tmp );
127 
128  $tmp = $name;
129  $nameKeys = [];
130  while ( preg_match( '/^(.+)\[([^\]]+)\]$/', $tmp, $m ) ) {
131  array_unshift( $nameKeys, $m[2] );
132  $tmp = $m[1];
133  }
134  array_unshift( $nameKeys, $tmp );
135 
136  $testValue = '';
137  for ( $i = count( $thisKeys ) - 1; $i >= 0; $i-- ) {
138  $keys = array_merge( array_slice( $thisKeys, 0, $i ), $nameKeys );
139  $data = $alldata;
140  while ( $keys ) {
141  $key = array_shift( $keys );
142  if ( !is_array( $data ) || !array_key_exists( $key, $data ) ) {
143  continue 2;
144  }
145  $data = $data[$key];
146  }
147  $testValue = (string)$data;
148  break;
149  }
150 
151  return $testValue;
152  }
153 
162  protected function isHiddenRecurse( array $alldata, array $params ) {
163  $origParams = $params;
164  $op = array_shift( $params );
165 
166  try {
167  switch ( $op ) {
168  case 'AND':
169  foreach ( $params as $i => $p ) {
170  if ( !is_array( $p ) ) {
171  throw new MWException(
172  "Expected array, found " . gettype( $p ) . " at index $i"
173  );
174  }
175  if ( !$this->isHiddenRecurse( $alldata, $p ) ) {
176  return false;
177  }
178  }
179  return true;
180 
181  case 'OR':
182  foreach ( $params as $i => $p ) {
183  if ( !is_array( $p ) ) {
184  throw new MWException(
185  "Expected array, found " . gettype( $p ) . " at index $i"
186  );
187  }
188  if ( $this->isHiddenRecurse( $alldata, $p ) ) {
189  return true;
190  }
191  }
192  return false;
193 
194  case 'NAND':
195  foreach ( $params as $i => $p ) {
196  if ( !is_array( $p ) ) {
197  throw new MWException(
198  "Expected array, found " . gettype( $p ) . " at index $i"
199  );
200  }
201  if ( !$this->isHiddenRecurse( $alldata, $p ) ) {
202  return true;
203  }
204  }
205  return false;
206 
207  case 'NOR':
208  foreach ( $params as $i => $p ) {
209  if ( !is_array( $p ) ) {
210  throw new MWException(
211  "Expected array, found " . gettype( $p ) . " at index $i"
212  );
213  }
214  if ( $this->isHiddenRecurse( $alldata, $p ) ) {
215  return false;
216  }
217  }
218  return true;
219 
220  case 'NOT':
221  if ( count( $params ) !== 1 ) {
222  throw new MWException( "NOT takes exactly one parameter" );
223  }
224  $p = $params[0];
225  if ( !is_array( $p ) ) {
226  throw new MWException(
227  "Expected array, found " . gettype( $p ) . " at index 0"
228  );
229  }
230  return !$this->isHiddenRecurse( $alldata, $p );
231 
232  case '===':
233  case '!==':
234  if ( count( $params ) !== 2 ) {
235  throw new MWException( "$op takes exactly two parameters" );
236  }
237  list( $field, $value ) = $params;
238  if ( !is_string( $field ) || !is_string( $value ) ) {
239  throw new MWException( "Parameters for $op must be strings" );
240  }
241  $testValue = $this->getNearestFieldByName( $alldata, $field );
242  switch ( $op ) {
243  case '===':
244  return ( $value === $testValue );
245  case '!==':
246  return ( $value !== $testValue );
247  }
248 
249  default:
250  throw new MWException( "Unknown operation" );
251  }
252  } catch ( Exception $ex ) {
253  throw new MWException(
254  "Invalid hide-if specification for $this->mName: " .
255  $ex->getMessage() . " in " . var_export( $origParams, true ),
256  0, $ex
257  );
258  }
259  }
260 
269  function isHidden( $alldata ) {
270  if ( !$this->mHideIf ) {
271  return false;
272  }
273 
274  return $this->isHiddenRecurse( $alldata, $this->mHideIf );
275  }
276 
287  function cancelSubmit( $value, $alldata ) {
288  return false;
289  }
290 
302  function validate( $value, $alldata ) {
303  if ( $this->isHidden( $alldata ) ) {
304  return true;
305  }
306 
307  if ( isset( $this->mParams['required'] )
308  && $this->mParams['required'] !== false
309  && $value === ''
310  ) {
311  return $this->msg( 'htmlform-required' )->parse();
312  }
313 
314  if ( isset( $this->mValidationCallback ) ) {
315  return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
316  }
317 
318  return true;
319  }
320 
321  function filter( $value, $alldata ) {
322  if ( isset( $this->mFilterCallback ) ) {
323  $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
324  }
325 
326  return $value;
327  }
328 
335  protected function needsLabel() {
336  return true;
337  }
338 
348  public function setShowEmptyLabel( $show ) {
349  $this->mShowEmptyLabels = $show;
350  }
351 
362  protected function isSubmitAttempt( WebRequest $request ) {
363  return $request->getCheck( 'wpEditToken' ) || $request->getCheck( 'wpFormIdentifier' );
364  }
365 
374  if ( $request->getCheck( $this->mName ) ) {
375  return $request->getText( $this->mName );
376  } else {
377  return $this->getDefault();
378  }
379  }
380 
389  function __construct( $params ) {
390  $this->mParams = $params;
391 
392  if ( isset( $params['parent'] ) && $params['parent'] instanceof HTMLForm ) {
393  $this->mParent = $params['parent'];
394  }
395 
396  # Generate the label from a message, if possible
397  if ( isset( $params['label-message'] ) ) {
398  $this->mLabel = $this->getMessage( $params['label-message'] )->parse();
399  } elseif ( isset( $params['label'] ) ) {
400  if ( $params['label'] === '&#160;' ) {
401  // Apparently some things set &nbsp directly and in an odd format
402  $this->mLabel = '&#160;';
403  } else {
404  $this->mLabel = htmlspecialchars( $params['label'] );
405  }
406  } elseif ( isset( $params['label-raw'] ) ) {
407  $this->mLabel = $params['label-raw'];
408  }
409 
410  $this->mName = "wp{$params['fieldname']}";
411  if ( isset( $params['name'] ) ) {
412  $this->mName = $params['name'];
413  }
414 
415  if ( isset( $params['dir'] ) ) {
416  $this->mDir = $params['dir'];
417  }
418 
419  $validName = Sanitizer::escapeId( $this->mName );
420  $validName = str_replace( [ '.5B', '.5D' ], [ '[', ']' ], $validName );
421  if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
422  throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
423  }
424 
425  $this->mID = "mw-input-{$this->mName}";
426 
427  if ( isset( $params['default'] ) ) {
428  $this->mDefault = $params['default'];
429  }
430 
431  if ( isset( $params['id'] ) ) {
432  $id = $params['id'];
433  $validId = Sanitizer::escapeId( $id );
434 
435  if ( $id != $validId ) {
436  throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
437  }
438 
439  $this->mID = $id;
440  }
441 
442  if ( isset( $params['cssclass'] ) ) {
443  $this->mClass = $params['cssclass'];
444  }
445 
446  if ( isset( $params['csshelpclass'] ) ) {
447  $this->mHelpClass = $params['csshelpclass'];
448  }
449 
450  if ( isset( $params['validation-callback'] ) ) {
451  $this->mValidationCallback = $params['validation-callback'];
452  }
453 
454  if ( isset( $params['filter-callback'] ) ) {
455  $this->mFilterCallback = $params['filter-callback'];
456  }
457 
458  if ( isset( $params['hidelabel'] ) ) {
459  $this->mShowEmptyLabels = false;
460  }
461 
462  if ( isset( $params['hide-if'] ) ) {
463  $this->mHideIf = $params['hide-if'];
464  }
465  }
466 
475  function getTableRow( $value ) {
476  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
477  $inputHtml = $this->getInputHTML( $value );
478  $fieldType = get_class( $this );
479  $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
480  $cellAttributes = [];
481  $rowAttributes = [];
482  $rowClasses = '';
483 
484  if ( !empty( $this->mParams['vertical-label'] ) ) {
485  $cellAttributes['colspan'] = 2;
486  $verticalLabel = true;
487  } else {
488  $verticalLabel = false;
489  }
490 
491  $label = $this->getLabelHtml( $cellAttributes );
492 
493  $field = Html::rawElement(
494  'td',
495  [ 'class' => 'mw-input' ] + $cellAttributes,
496  $inputHtml . "\n$errors"
497  );
498 
499  if ( $this->mHideIf ) {
500  $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
501  $rowClasses .= ' mw-htmlform-hide-if';
502  }
503 
504  if ( $verticalLabel ) {
505  $html = Html::rawElement( 'tr',
506  $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
507  $html .= Html::rawElement( 'tr',
508  $rowAttributes + [
509  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
510  ],
511  $field );
512  } else {
513  $html =
514  Html::rawElement( 'tr',
515  $rowAttributes + [
516  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
517  ],
518  $label . $field );
519  }
520 
521  return $html . $helptext;
522  }
523 
533  public function getDiv( $value ) {
534  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
535  $inputHtml = $this->getInputHTML( $value );
536  $fieldType = get_class( $this );
537  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
538  $cellAttributes = [];
539  $label = $this->getLabelHtml( $cellAttributes );
540 
541  $outerDivClass = [
542  'mw-input',
543  'mw-htmlform-nolabel' => ( $label === '' )
544  ];
545 
546  $horizontalLabel = isset( $this->mParams['horizontal-label'] )
547  ? $this->mParams['horizontal-label'] : false;
548 
549  if ( $horizontalLabel ) {
550  $field = '&#160;' . $inputHtml . "\n$errors";
551  } else {
552  $field = Html::rawElement(
553  'div',
554  [ 'class' => $outerDivClass ] + $cellAttributes,
555  $inputHtml . "\n$errors"
556  );
557  }
558  $divCssClasses = [ "mw-htmlform-field-$fieldType",
559  $this->mClass, $this->mVFormClass, $errorClass ];
560 
561  $wrapperAttributes = [
562  'class' => $divCssClasses,
563  ];
564  if ( $this->mHideIf ) {
565  $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
566  $wrapperAttributes['class'][] = ' mw-htmlform-hide-if';
567  }
568  $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
569  $html .= $helptext;
570 
571  return $html;
572  }
573 
582  public function getOOUI( $value ) {
583  $inputField = $this->getInputOOUI( $value );
584 
585  if ( !$inputField ) {
586  // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
587  // generate the whole field, label and errors and all, then wrap it in a Widget.
588  // It might look weird, but it'll work OK.
589  return $this->getFieldLayoutOOUI(
590  new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
591  [ 'infusable' => false, 'align' => 'top' ]
592  );
593  }
594 
595  $infusable = true;
596  if ( is_string( $inputField ) ) {
597  // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
598  // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
599  // JavaScript doesn't know how to rebuilt the contents.
600  $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
601  $infusable = false;
602  }
603 
604  $fieldType = get_class( $this );
605  $help = $this->getHelpText();
606  $errors = $this->getErrorsRaw( $value );
607  foreach ( $errors as &$error ) {
608  $error = new OOUI\HtmlSnippet( $error );
609  }
610 
611  $notices = $this->getNotices();
612  foreach ( $notices as &$notice ) {
613  $notice = new OOUI\HtmlSnippet( $notice );
614  }
615 
616  $config = [
617  'classes' => [ "mw-htmlform-field-$fieldType", $this->mClass ],
618  'align' => $this->getLabelAlignOOUI(),
619  'help' => ( $help !== null && $help !== '' ) ? new OOUI\HtmlSnippet( $help ) : null,
620  'errors' => $errors,
621  'notices' => $notices,
622  'infusable' => $infusable,
623  ];
624 
625  $preloadModules = false;
626 
627  if ( $infusable && $this->shouldInfuseOOUI() ) {
628  $preloadModules = true;
629  $config['classes'][] = 'mw-htmlform-field-autoinfuse';
630  }
631 
632  // the element could specify, that the label doesn't need to be added
633  $label = $this->getLabel();
634  if ( $label ) {
635  $config['label'] = new OOUI\HtmlSnippet( $label );
636  }
637 
638  if ( $this->mHideIf ) {
639  $preloadModules = true;
640  $config['hideIf'] = $this->mHideIf;
641  }
642 
643  $config['modules'] = $this->getOOUIModules();
644 
645  if ( $preloadModules ) {
646  $this->mParent->getOutput()->addModules( 'mediawiki.htmlform.ooui' );
647  $this->mParent->getOutput()->addModules( $this->getOOUIModules() );
648  }
649 
650  return $this->getFieldLayoutOOUI( $inputField, $config );
651  }
652 
657  protected function getLabelAlignOOUI() {
658  return 'top';
659  }
660 
665  protected function getFieldLayoutOOUI( $inputField, $config ) {
666  if ( isset( $this->mClassWithButton ) ) {
667  $buttonWidget = $this->mClassWithButton->getInputOOUI( '' );
668  return new HTMLFormActionFieldLayout( $inputField, $buttonWidget, $config );
669  }
670  return new HTMLFormFieldLayout( $inputField, $config );
671  }
672 
680  protected function shouldInfuseOOUI() {
681  // Always infuse fields with help text, since the interface for it is nicer with JS
682  return $this->getHelpText() !== null;
683  }
684 
691  protected function getOOUIModules() {
692  return [];
693  }
694 
704  public function getRaw( $value ) {
705  list( $errors, ) = $this->getErrorsAndErrorClass( $value );
706  $inputHtml = $this->getInputHTML( $value );
707  $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
708  $cellAttributes = [];
709  $label = $this->getLabelHtml( $cellAttributes );
710 
711  $html = "\n$errors";
712  $html .= $label;
713  $html .= $inputHtml;
714  $html .= $helptext;
715 
716  return $html;
717  }
718 
727  public function getVForm( $value ) {
728  // Ewwww
729  $this->mVFormClass = ' mw-ui-vform-field';
730  return $this->getDiv( $value );
731  }
732 
739  public function getInline( $value ) {
740  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
741  $inputHtml = $this->getInputHTML( $value );
742  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
743  $cellAttributes = [];
744  $label = $this->getLabelHtml( $cellAttributes );
745 
746  $html = "\n" . $errors .
747  $label . '&#160;' .
748  $inputHtml .
749  $helptext;
750 
751  return $html;
752  }
753 
761  public function getHelpTextHtmlTable( $helptext ) {
762  if ( is_null( $helptext ) ) {
763  return '';
764  }
765 
766  $rowAttributes = [];
767  if ( $this->mHideIf ) {
768  $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
769  $rowAttributes['class'] = 'mw-htmlform-hide-if';
770  }
771 
772  $tdClasses = [ 'htmlform-tip' ];
773  if ( $this->mHelpClass !== false ) {
774  $tdClasses[] = $this->mHelpClass;
775  }
776  $row = Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext );
777  $row = Html::rawElement( 'tr', $rowAttributes, $row );
778 
779  return $row;
780  }
781 
790  public function getHelpTextHtmlDiv( $helptext ) {
791  if ( is_null( $helptext ) ) {
792  return '';
793  }
794 
795  $wrapperAttributes = [
796  'class' => 'htmlform-tip',
797  ];
798  if ( $this->mHelpClass !== false ) {
799  $wrapperAttributes['class'] .= " {$this->mHelpClass}";
800  }
801  if ( $this->mHideIf ) {
802  $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
803  $wrapperAttributes['class'] .= ' mw-htmlform-hide-if';
804  }
805  $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
806 
807  return $div;
808  }
809 
817  public function getHelpTextHtmlRaw( $helptext ) {
818  return $this->getHelpTextHtmlDiv( $helptext );
819  }
820 
826  public function getHelpText() {
827  $helptext = null;
828 
829  if ( isset( $this->mParams['help-message'] ) ) {
830  $this->mParams['help-messages'] = [ $this->mParams['help-message'] ];
831  }
832 
833  if ( isset( $this->mParams['help-messages'] ) ) {
834  foreach ( $this->mParams['help-messages'] as $msg ) {
835  $msg = $this->getMessage( $msg );
836 
837  if ( $msg->exists() ) {
838  if ( is_null( $helptext ) ) {
839  $helptext = '';
840  } else {
841  $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
842  }
843  $helptext .= $msg->parse(); // Append message
844  }
845  }
846  } elseif ( isset( $this->mParams['help'] ) ) {
847  $helptext = $this->mParams['help'];
848  }
849 
850  return $helptext;
851  }
852 
860  public function getErrorsAndErrorClass( $value ) {
861  $errors = $this->validate( $value, $this->mParent->mFieldData );
862 
863  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
864  $errors = '';
865  $errorClass = '';
866  } else {
867  $errors = self::formatErrors( $errors );
868  $errorClass = 'mw-htmlform-invalid-input';
869  }
870 
871  return [ $errors, $errorClass ];
872  }
873 
881  public function getErrorsRaw( $value ) {
882  $errors = $this->validate( $value, $this->mParent->mFieldData );
883 
884  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
885  $errors = [];
886  }
887 
888  if ( !is_array( $errors ) ) {
889  $errors = [ $errors ];
890  }
891  foreach ( $errors as &$error ) {
892  if ( $error instanceof Message ) {
893  $error = $error->parse();
894  }
895  }
896 
897  return $errors;
898  }
899 
906  function getNotices() {
907  $notices = [];
908 
909  if ( isset( $this->mParams['notice-message'] ) ) {
910  $notices[] = $this->getMessage( $this->mParams['notice-message'] )->parse();
911  }
912 
913  if ( isset( $this->mParams['notice-messages'] ) ) {
914  foreach ( $this->mParams['notice-messages'] as $msg ) {
915  $notices[] = $this->getMessage( $msg )->parse();
916  }
917  } elseif ( isset( $this->mParams['notice'] ) ) {
918  $notices[] = $this->mParams['notice'];
919  }
920 
921  return $notices;
922  }
923 
927  function getLabel() {
928  return is_null( $this->mLabel ) ? '' : $this->mLabel;
929  }
930 
931  function getLabelHtml( $cellAttributes = [] ) {
932  # Don't output a for= attribute for labels with no associated input.
933  # Kind of hacky here, possibly we don't want these to be <label>s at all.
934  $for = [];
935 
936  if ( $this->needsLabel() ) {
937  $for['for'] = $this->mID;
938  }
939 
940  $labelValue = trim( $this->getLabel() );
941  $hasLabel = false;
942  if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
943  $hasLabel = true;
944  }
945 
946  $displayFormat = $this->mParent->getDisplayFormat();
947  $html = '';
948  $horizontalLabel = isset( $this->mParams['horizontal-label'] )
949  ? $this->mParams['horizontal-label'] : false;
950 
951  if ( $displayFormat === 'table' ) {
952  $html =
953  Html::rawElement( 'td',
954  [ 'class' => 'mw-label' ] + $cellAttributes,
955  Html::rawElement( 'label', $for, $labelValue ) );
956  } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
957  if ( $displayFormat === 'div' && !$horizontalLabel ) {
958  $html =
959  Html::rawElement( 'div',
960  [ 'class' => 'mw-label' ] + $cellAttributes,
961  Html::rawElement( 'label', $for, $labelValue ) );
962  } else {
963  $html = Html::rawElement( 'label', $for, $labelValue );
964  }
965  }
966 
967  return $html;
968  }
969 
970  function getDefault() {
971  if ( isset( $this->mDefault ) ) {
972  return $this->mDefault;
973  } else {
974  return null;
975  }
976  }
977 
983  public function getTooltipAndAccessKey() {
984  if ( empty( $this->mParams['tooltip'] ) ) {
985  return [];
986  }
987 
988  return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
989  }
990 
997  public function getAttributes( array $list ) {
998  static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
999 
1000  $ret = [];
1001  foreach ( $list as $key ) {
1002  if ( in_array( $key, $boolAttribs ) ) {
1003  if ( !empty( $this->mParams[$key] ) ) {
1004  $ret[$key] = '';
1005  }
1006  } elseif ( isset( $this->mParams[$key] ) ) {
1007  $ret[$key] = $this->mParams[$key];
1008  }
1009  }
1010 
1011  return $ret;
1012  }
1013 
1021  private function lookupOptionsKeys( $options ) {
1022  $ret = [];
1023  foreach ( $options as $key => $value ) {
1024  $key = $this->msg( $key )->plain();
1025  $ret[$key] = is_array( $value )
1026  ? $this->lookupOptionsKeys( $value )
1027  : strval( $value );
1028  }
1029  return $ret;
1030  }
1031 
1039  static function forceToStringRecursive( $array ) {
1040  if ( is_array( $array ) ) {
1041  return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1042  } else {
1043  return strval( $array );
1044  }
1045  }
1046 
1053  public function getOptions() {
1054  if ( $this->mOptions === false ) {
1055  if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1056  $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'] );
1057  } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1058  $this->mOptionsLabelsNotFromMessage = true;
1059  $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1060  } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1062  $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1063 
1064  $optgroup = false;
1065  $this->mOptions = [];
1066  foreach ( explode( "\n", $message ) as $option ) {
1067  $value = trim( $option );
1068  if ( $value == '' ) {
1069  continue;
1070  } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
1071  # A new group is starting...
1072  $value = trim( substr( $value, 1 ) );
1073  $optgroup = $value;
1074  } elseif ( substr( $value, 0, 2 ) == '**' ) {
1075  # groupmember
1076  $opt = trim( substr( $value, 2 ) );
1077  if ( $optgroup === false ) {
1078  $this->mOptions[$opt] = $opt;
1079  } else {
1080  $this->mOptions[$optgroup][$opt] = $opt;
1081  }
1082  } else {
1083  # groupless reason list
1084  $optgroup = false;
1085  $this->mOptions[$option] = $option;
1086  }
1087  }
1088  } else {
1089  $this->mOptions = null;
1090  }
1091  }
1092 
1093  return $this->mOptions;
1094  }
1095 
1100  public function getOptionsOOUI() {
1101  $oldoptions = $this->getOptions();
1102 
1103  if ( $oldoptions === null ) {
1104  return null;
1105  }
1106 
1107  $options = [];
1108 
1109  foreach ( $oldoptions as $text => $data ) {
1110  $options[] = [
1111  'data' => (string)$data,
1112  'label' => (string)$text,
1113  ];
1114  }
1115 
1116  return $options;
1117  }
1118 
1126  public static function flattenOptions( $options ) {
1127  $flatOpts = [];
1128 
1129  foreach ( $options as $value ) {
1130  if ( is_array( $value ) ) {
1131  $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1132  } else {
1133  $flatOpts[] = $value;
1134  }
1135  }
1136 
1137  return $flatOpts;
1138  }
1139 
1147  protected static function formatErrors( $errors ) {
1148  if ( is_array( $errors ) && count( $errors ) === 1 ) {
1149  $errors = array_shift( $errors );
1150  }
1151 
1152  if ( is_array( $errors ) ) {
1153  $lines = [];
1154  foreach ( $errors as $error ) {
1155  if ( $error instanceof Message ) {
1156  $lines[] = Html::rawElement( 'li', [], $error->parse() );
1157  } else {
1158  $lines[] = Html::rawElement( 'li', [], $error );
1159  }
1160  }
1161 
1162  return Html::rawElement( 'ul', [ 'class' => 'error' ], implode( "\n", $lines ) );
1163  } else {
1164  if ( $errors instanceof Message ) {
1165  $errors = $errors->parse();
1166  }
1167 
1168  return Html::rawElement( 'span', [ 'class' => 'error' ], $errors );
1169  }
1170  }
1171 
1178  protected function getMessage( $value ) {
1179  $message = Message::newFromSpecifier( $value );
1180 
1181  if ( $this->mParent ) {
1182  $message->setContext( $this->mParent );
1183  }
1184 
1185  return $message;
1186  }
1187 
1194  public function skipLoadData( $request ) {
1195  return !empty( $this->mParams['nodata'] );
1196  }
1197 }
getInline($value)
Get the complete field as an inline element.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1936
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
static formatErrors($errors)
Formats one or more errors as accepted by field validation-callback.
getRaw($value)
Get the complete raw fields for the input, including help text, labels, and whatever.
HTMLForm null $mParent
skipLoadData($request)
Skip this field when collecting data.
the array() calling protocol came about after MediaWiki 1.4rc1.
getOptions()
Fetch the array of options from the field's parameters.
loadDataFromRequest($request)
Get the value that this input has been set to from a posted form, or the input's default value if it ...
lookupOptionsKeys($options)
Given an array of msg-key => value mappings, returns an array with keys being the message texts...
getMessage($value)
Turns a *-message parameter (which could be a MessageSpecifier, or a message name, or a name + parameters array) into a Message.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1936
getLabelHtml($cellAttributes=[])
msg()
Get a translated interface message.
isHidden($alldata)
Test whether this field is supposed to be hidden, based on the values of the other form fields...
getVForm($value)
Get the complete field for the input, including help text, labels, and whatever.
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:209
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:177
getTooltipAndAccessKey()
Returns the attributes required for the tooltip and accesskey.
$value
setShowEmptyLabel($show)
Tell the field whether to generate a separate label element if its label is blank.
getOOUIModules()
Get the list of extra ResourceLoader modules which must be loaded client-side before it's possible to...
getHelpText()
Determine the help text to display.
static tooltipAndAccesskeyAttribs($name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2182
static forceToStringRecursive($array)
Recursively forces values in an array to strings, because issues arise with integer 0 as a value...
getErrorsRaw($value)
Determine form errors to display, returning them in an array.
filter($value, $alldata)
getOOUI($value)
Get the OOUI version of the div.
if($line===false) $args
Definition: cdb.php:64
__construct($params)
Initialise the object.
validate($value, $alldata)
Override this function to add specific validation checks on the field input.
bool $mShowEmptyLabels
If true will generate an empty div element with no label.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1046
getNearestFieldByName($alldata, $name)
Fetch a field value from $alldata for the closest field matching a given name.
getTableRow($value)
Get the complete table row for the input, including help text, labels, and whatever.
getCheck($name)
Return true if the named value is set in the input, whatever that value is (even "0").
Definition: WebRequest.php:591
static encode($value, $pretty=false, $escaping=0)
Returns the JSON representation of a value.
Definition: FormatJson.php:127
$params
getInputOOUI($value)
Same as getInputHTML, but returns an OOUI object.
getHelpTextHtmlTable($helptext)
Generate help text HTML in table format.
Object handling generic submission, CSRF protection, layout and other logic for UI forms...
Definition: HTMLForm.php:128
getDiv($value)
Get the complete div for the input, including help text, labels, and whatever.
getLabelAlignOOUI()
Get label alignment when generating field for OOUI.
$help
Definition: mcc.php:32
getFieldLayoutOOUI($inputField, $config)
Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
canDisplayErrors()
True if this field type is able to display errors; false if validation errors need to be displayed in...
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getOptionsOOUI()
Get options and make them into arrays suitable for OOUI.
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1170
needsLabel()
Should this field have a label, or is there no input element with the appropriate id for the label to...
getInputHTML($value)
This function must be implemented to return the HTML to generate the input object itself...
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
$lines
Definition: router.php:67
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2573
The parent class to generate form fields.
cancelSubmit($value, $alldata)
Override this function if the control can somehow trigger a form submission that shouldn't actually s...
getHelpTextHtmlDiv($helptext)
Generate help text HTML in div format.
We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going on
Definition: hooks.txt:86
getErrorsAndErrorClass($value)
Determine form errors to display and their classes.
getHelpTextHtmlRaw($helptext)
Generate help text HTML formatted for raw output.
isSubmitAttempt(WebRequest $request)
Can we assume that the request is an attempt to submit a HTMLForm, as opposed to an attempt to just v...
hasVisibleOutput()
If this field has a user-visible output or not.
static flattenOptions($options)
flatten an array of options to a single array, for instance, a set of "" inside "...
getAttributes(array $list)
Returns the given attributes from the parameters.
isHiddenRecurse(array $alldata, array $params)
Helper function for isHidden to handle recursive data structures.
getNotices()
Determine notices to display for the field.
shouldInfuseOOUI()
Whether the field should be automatically infused.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:300
static newFromSpecifier($value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:398