MediaWiki  master
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 true;
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['flatlist'] ) ) {
459  $this->mClass .= ' mw-htmlform-flatlist';
460  }
461 
462  if ( isset( $params['hidelabel'] ) ) {
463  $this->mShowEmptyLabels = false;
464  }
465 
466  if ( isset( $params['hide-if'] ) ) {
467  $this->mHideIf = $params['hide-if'];
468  }
469  }
470 
479  function getTableRow( $value ) {
480  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
481  $inputHtml = $this->getInputHTML( $value );
482  $fieldType = get_class( $this );
483  $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
484  $cellAttributes = [];
485  $rowAttributes = [];
486  $rowClasses = '';
487 
488  if ( !empty( $this->mParams['vertical-label'] ) ) {
489  $cellAttributes['colspan'] = 2;
490  $verticalLabel = true;
491  } else {
492  $verticalLabel = false;
493  }
494 
495  $label = $this->getLabelHtml( $cellAttributes );
496 
497  $field = Html::rawElement(
498  'td',
499  [ 'class' => 'mw-input' ] + $cellAttributes,
500  $inputHtml . "\n$errors"
501  );
502 
503  if ( $this->mHideIf ) {
504  $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
505  $rowClasses .= ' mw-htmlform-hide-if';
506  }
507 
508  if ( $verticalLabel ) {
509  $html = Html::rawElement( 'tr',
510  $rowAttributes + [ 'class' => "mw-htmlform-vertical-label $rowClasses" ], $label );
511  $html .= Html::rawElement( 'tr',
512  $rowAttributes + [
513  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
514  ],
515  $field );
516  } else {
517  $html =
518  Html::rawElement( 'tr',
519  $rowAttributes + [
520  'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $rowClasses"
521  ],
522  $label . $field );
523  }
524 
525  return $html . $helptext;
526  }
527 
537  public function getDiv( $value ) {
538  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
539  $inputHtml = $this->getInputHTML( $value );
540  $fieldType = get_class( $this );
541  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
542  $cellAttributes = [];
543  $label = $this->getLabelHtml( $cellAttributes );
544 
545  $outerDivClass = [
546  'mw-input',
547  'mw-htmlform-nolabel' => ( $label === '' )
548  ];
549 
550  $horizontalLabel = isset( $this->mParams['horizontal-label'] )
551  ? $this->mParams['horizontal-label'] : false;
552 
553  if ( $horizontalLabel ) {
554  $field = '&#160;' . $inputHtml . "\n$errors";
555  } else {
556  $field = Html::rawElement(
557  'div',
558  [ 'class' => $outerDivClass ] + $cellAttributes,
559  $inputHtml . "\n$errors"
560  );
561  }
562  $divCssClasses = [ "mw-htmlform-field-$fieldType",
563  $this->mClass, $this->mVFormClass, $errorClass ];
564 
565  $wrapperAttributes = [
566  'class' => $divCssClasses,
567  ];
568  if ( $this->mHideIf ) {
569  $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
570  $wrapperAttributes['class'][] = ' mw-htmlform-hide-if';
571  }
572  $html = Html::rawElement( 'div', $wrapperAttributes, $label . $field );
573  $html .= $helptext;
574 
575  return $html;
576  }
577 
586  public function getOOUI( $value ) {
587  $inputField = $this->getInputOOUI( $value );
588 
589  if ( !$inputField ) {
590  // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
591  // generate the whole field, label and errors and all, then wrap it in a Widget.
592  // It might look weird, but it'll work OK.
593  return $this->getFieldLayoutOOUI(
594  new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ] ),
595  [ 'infusable' => false, 'align' => 'top' ]
596  );
597  }
598 
599  $infusable = true;
600  if ( is_string( $inputField ) ) {
601  // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
602  // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
603  // JavaScript doesn't know how to rebuilt the contents.
604  $inputField = new OOUI\Widget( [ 'content' => new OOUI\HtmlSnippet( $inputField ) ] );
605  $infusable = false;
606  }
607 
608  $fieldType = get_class( $this );
609  $helpText = $this->getHelpText();
610  $errors = $this->getErrorsRaw( $value );
611  foreach ( $errors as &$error ) {
612  $error = new OOUI\HtmlSnippet( $error );
613  }
614 
615  $notices = $this->getNotices();
616  foreach ( $notices as &$notice ) {
617  $notice = new OOUI\HtmlSnippet( $notice );
618  }
619 
620  $config = [
621  'classes' => [ "mw-htmlform-field-$fieldType", $this->mClass ],
622  'align' => $this->getLabelAlignOOUI(),
623  'help' => $helpText !== null ? new OOUI\HtmlSnippet( $helpText ) : null,
624  'errors' => $errors,
625  'notices' => $notices,
626  'infusable' => $infusable,
627  ];
628 
629  // the element could specify, that the label doesn't need to be added
630  $label = $this->getLabel();
631  if ( $label ) {
632  $config['label'] = new OOUI\HtmlSnippet( $label );
633  }
634 
635  return $this->getFieldLayoutOOUI( $inputField, $config );
636  }
637 
642  protected function getLabelAlignOOUI() {
643  return 'top';
644  }
645 
650  protected function getFieldLayoutOOUI( $inputField, $config ) {
651  if ( isset( $this->mClassWithButton ) ) {
652  $buttonWidget = $this->mClassWithButton->getInputOOUI( '' );
653  return new OOUI\ActionFieldLayout( $inputField, $buttonWidget, $config );
654  }
655  return new OOUI\FieldLayout( $inputField, $config );
656  }
657 
667  public function getRaw( $value ) {
668  list( $errors, ) = $this->getErrorsAndErrorClass( $value );
669  $inputHtml = $this->getInputHTML( $value );
670  $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
671  $cellAttributes = [];
672  $label = $this->getLabelHtml( $cellAttributes );
673 
674  $html = "\n$errors";
675  $html .= $label;
676  $html .= $inputHtml;
677  $html .= $helptext;
678 
679  return $html;
680  }
681 
690  public function getVForm( $value ) {
691  // Ewwww
692  $this->mVFormClass = ' mw-ui-vform-field';
693  return $this->getDiv( $value );
694  }
695 
702  public function getInline( $value ) {
703  list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
704  $inputHtml = $this->getInputHTML( $value );
705  $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
706  $cellAttributes = [];
707  $label = $this->getLabelHtml( $cellAttributes );
708 
709  $html = "\n" . $errors .
710  $label . '&#160;' .
711  $inputHtml .
712  $helptext;
713 
714  return $html;
715  }
716 
724  public function getHelpTextHtmlTable( $helptext ) {
725  if ( is_null( $helptext ) ) {
726  return '';
727  }
728 
729  $rowAttributes = [];
730  if ( $this->mHideIf ) {
731  $rowAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
732  $rowAttributes['class'] = 'mw-htmlform-hide-if';
733  }
734 
735  $tdClasses = [ 'htmlform-tip' ];
736  if ( $this->mHelpClass !== false ) {
737  $tdClasses[] = $this->mHelpClass;
738  }
739  $row = Html::rawElement( 'td', [ 'colspan' => 2, 'class' => $tdClasses ], $helptext );
740  $row = Html::rawElement( 'tr', $rowAttributes, $row );
741 
742  return $row;
743  }
744 
753  public function getHelpTextHtmlDiv( $helptext ) {
754  if ( is_null( $helptext ) ) {
755  return '';
756  }
757 
758  $wrapperAttributes = [
759  'class' => 'htmlform-tip',
760  ];
761  if ( $this->mHelpClass !== false ) {
762  $wrapperAttributes['class'] .= " {$this->mHelpClass}";
763  }
764  if ( $this->mHideIf ) {
765  $wrapperAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
766  $wrapperAttributes['class'] .= ' mw-htmlform-hide-if';
767  }
768  $div = Html::rawElement( 'div', $wrapperAttributes, $helptext );
769 
770  return $div;
771  }
772 
780  public function getHelpTextHtmlRaw( $helptext ) {
781  return $this->getHelpTextHtmlDiv( $helptext );
782  }
783 
789  public function getHelpText() {
790  $helptext = null;
791 
792  if ( isset( $this->mParams['help-message'] ) ) {
793  $this->mParams['help-messages'] = [ $this->mParams['help-message'] ];
794  }
795 
796  if ( isset( $this->mParams['help-messages'] ) ) {
797  foreach ( $this->mParams['help-messages'] as $msg ) {
798  $msg = $this->getMessage( $msg );
799 
800  if ( $msg->exists() ) {
801  if ( is_null( $helptext ) ) {
802  $helptext = '';
803  } else {
804  $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
805  }
806  $helptext .= $msg->parse(); // Append message
807  }
808  }
809  } elseif ( isset( $this->mParams['help'] ) ) {
810  $helptext = $this->mParams['help'];
811  }
812 
813  return $helptext;
814  }
815 
823  public function getErrorsAndErrorClass( $value ) {
824  $errors = $this->validate( $value, $this->mParent->mFieldData );
825 
826  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
827  $errors = '';
828  $errorClass = '';
829  } else {
830  $errors = self::formatErrors( $errors );
831  $errorClass = 'mw-htmlform-invalid-input';
832  }
833 
834  return [ $errors, $errorClass ];
835  }
836 
844  public function getErrorsRaw( $value ) {
845  $errors = $this->validate( $value, $this->mParent->mFieldData );
846 
847  if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
848  $errors = [];
849  }
850 
851  if ( !is_array( $errors ) ) {
852  $errors = [ $errors ];
853  }
854  foreach ( $errors as &$error ) {
855  if ( $error instanceof Message ) {
856  $error = $error->parse();
857  }
858  }
859 
860  return $errors;
861  }
862 
869  function getNotices() {
870  $notices = [];
871 
872  if ( isset( $this->mParams['notice-message'] ) ) {
873  $notices[] = $this->getMessage( $this->mParams['notice-message'] )->parse();
874  }
875 
876  if ( isset( $this->mParams['notice-messages'] ) ) {
877  foreach ( $this->mParams['notice-messages'] as $msg ) {
878  $notices[] = $this->getMessage( $msg )->parse();
879  }
880  } elseif ( isset( $this->mParams['notice'] ) ) {
881  $notices[] = $this->mParams['notice'];
882  }
883 
884  return $notices;
885  }
886 
890  function getLabel() {
891  return is_null( $this->mLabel ) ? '' : $this->mLabel;
892  }
893 
894  function getLabelHtml( $cellAttributes = [] ) {
895  # Don't output a for= attribute for labels with no associated input.
896  # Kind of hacky here, possibly we don't want these to be <label>s at all.
897  $for = [];
898 
899  if ( $this->needsLabel() ) {
900  $for['for'] = $this->mID;
901  }
902 
903  $labelValue = trim( $this->getLabel() );
904  $hasLabel = false;
905  if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
906  $hasLabel = true;
907  }
908 
909  $displayFormat = $this->mParent->getDisplayFormat();
910  $html = '';
911  $horizontalLabel = isset( $this->mParams['horizontal-label'] )
912  ? $this->mParams['horizontal-label'] : false;
913 
914  if ( $displayFormat === 'table' ) {
915  $html =
916  Html::rawElement( 'td',
917  [ 'class' => 'mw-label' ] + $cellAttributes,
918  Html::rawElement( 'label', $for, $labelValue ) );
919  } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
920  if ( $displayFormat === 'div' && !$horizontalLabel ) {
921  $html =
922  Html::rawElement( 'div',
923  [ 'class' => 'mw-label' ] + $cellAttributes,
924  Html::rawElement( 'label', $for, $labelValue ) );
925  } else {
926  $html = Html::rawElement( 'label', $for, $labelValue );
927  }
928  }
929 
930  return $html;
931  }
932 
933  function getDefault() {
934  if ( isset( $this->mDefault ) ) {
935  return $this->mDefault;
936  } else {
937  return null;
938  }
939  }
940 
946  public function getTooltipAndAccessKey() {
947  if ( empty( $this->mParams['tooltip'] ) ) {
948  return [];
949  }
950 
951  return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
952  }
953 
960  public function getAttributes( array $list ) {
961  static $boolAttribs = [ 'disabled', 'required', 'autofocus', 'multiple', 'readonly' ];
962 
963  $ret = [];
964  foreach ( $list as $key ) {
965  if ( in_array( $key, $boolAttribs ) ) {
966  if ( !empty( $this->mParams[$key] ) ) {
967  $ret[$key] = '';
968  }
969  } elseif ( isset( $this->mParams[$key] ) ) {
970  $ret[$key] = $this->mParams[$key];
971  }
972  }
973 
974  return $ret;
975  }
976 
984  private function lookupOptionsKeys( $options ) {
985  $ret = [];
986  foreach ( $options as $key => $value ) {
987  $key = $this->msg( $key )->plain();
988  $ret[$key] = is_array( $value )
989  ? $this->lookupOptionsKeys( $value )
990  : strval( $value );
991  }
992  return $ret;
993  }
994 
1002  static function forceToStringRecursive( $array ) {
1003  if ( is_array( $array ) ) {
1004  return array_map( [ __CLASS__, 'forceToStringRecursive' ], $array );
1005  } else {
1006  return strval( $array );
1007  }
1008  }
1009 
1016  public function getOptions() {
1017  if ( $this->mOptions === false ) {
1018  if ( array_key_exists( 'options-messages', $this->mParams ) ) {
1019  $this->mOptions = $this->lookupOptionsKeys( $this->mParams['options-messages'] );
1020  } elseif ( array_key_exists( 'options', $this->mParams ) ) {
1021  $this->mOptionsLabelsNotFromMessage = true;
1022  $this->mOptions = self::forceToStringRecursive( $this->mParams['options'] );
1023  } elseif ( array_key_exists( 'options-message', $this->mParams ) ) {
1025  $message = $this->getMessage( $this->mParams['options-message'] )->inContentLanguage()->plain();
1026 
1027  $optgroup = false;
1028  $this->mOptions = [];
1029  foreach ( explode( "\n", $message ) as $option ) {
1030  $value = trim( $option );
1031  if ( $value == '' ) {
1032  continue;
1033  } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
1034  # A new group is starting...
1035  $value = trim( substr( $value, 1 ) );
1036  $optgroup = $value;
1037  } elseif ( substr( $value, 0, 2 ) == '**' ) {
1038  # groupmember
1039  $opt = trim( substr( $value, 2 ) );
1040  if ( $optgroup === false ) {
1041  $this->mOptions[$opt] = $opt;
1042  } else {
1043  $this->mOptions[$optgroup][$opt] = $opt;
1044  }
1045  } else {
1046  # groupless reason list
1047  $optgroup = false;
1048  $this->mOptions[$option] = $option;
1049  }
1050  }
1051  } else {
1052  $this->mOptions = null;
1053  }
1054  }
1055 
1056  return $this->mOptions;
1057  }
1058 
1063  public function getOptionsOOUI() {
1064  $oldoptions = $this->getOptions();
1065 
1066  if ( $oldoptions === null ) {
1067  return null;
1068  }
1069 
1070  $options = [];
1071 
1072  foreach ( $oldoptions as $text => $data ) {
1073  $options[] = [
1074  'data' => (string)$data,
1075  'label' => (string)$text,
1076  ];
1077  }
1078 
1079  return $options;
1080  }
1081 
1089  public static function flattenOptions( $options ) {
1090  $flatOpts = [];
1091 
1092  foreach ( $options as $value ) {
1093  if ( is_array( $value ) ) {
1094  $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
1095  } else {
1096  $flatOpts[] = $value;
1097  }
1098  }
1099 
1100  return $flatOpts;
1101  }
1102 
1110  protected static function formatErrors( $errors ) {
1111  if ( is_array( $errors ) && count( $errors ) === 1 ) {
1112  $errors = array_shift( $errors );
1113  }
1114 
1115  if ( is_array( $errors ) ) {
1116  $lines = [];
1117  foreach ( $errors as $error ) {
1118  if ( $error instanceof Message ) {
1119  $lines[] = Html::rawElement( 'li', [], $error->parse() );
1120  } else {
1121  $lines[] = Html::rawElement( 'li', [], $error );
1122  }
1123  }
1124 
1125  return Html::rawElement( 'ul', [ 'class' => 'error' ], implode( "\n", $lines ) );
1126  } else {
1127  if ( $errors instanceof Message ) {
1128  $errors = $errors->parse();
1129  }
1130 
1131  return Html::rawElement( 'span', [ 'class' => 'error' ], $errors );
1132  }
1133  }
1134 
1141  protected function getMessage( $value ) {
1142  $message = Message::newFromSpecifier( $value );
1143 
1144  if ( $this->mParent ) {
1145  $message->setContext( $this->mParent );
1146  }
1147 
1148  return $message;
1149  }
1150 
1157  public function skipLoadData( $request ) {
1158  return !empty( $this->mParams['nodata'] );
1159  }
1160 }
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:1816
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.
getLabelHtml($cellAttributes=[])
The Message class provides methods which fulfil two basic services:
Definition: Message.php:159
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:210
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Definition: WebRequest.php:37
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.
getHelpText()
Determine the help text to display.
static tooltipAndAccesskeyAttribs($name, array $msgParams=[])
Returns the attributes for the tooltip and access key.
Definition: Linker.php:2240
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:1020
getNearestFieldByName($alldata, $name)
Fetch a field value from $alldata for the closest field matching a given name.
MediaWiki exception.
Definition: MWException.php:26
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:560
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.
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...
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:1816
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:1169
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:66
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2458
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 "<options>" inside "<optgroups>...
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.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310
static newFromSpecifier($value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:398