4 * The parent class to generate form fields. Any field type should
5 * be a subclass of this.
7 abstract class HTMLFormField
{
10 protected $mValidationCallback;
11 protected $mFilterCallback;
13 protected $mLabel; # String label. Set on construction
15 protected $mClass = '';
19 * @var bool If true will generate an empty div element with no label
22 protected $mShowEmptyLabels = true;
30 * This function must be implemented to return the HTML to generate
31 * the input object itself. It should not implement the surrounding
32 * table cells/rows, or labels/help messages.
34 * @param string $value the value to set the input to; eg a default
35 * text for a text input.
37 * @return string Valid HTML.
39 abstract function getInputHTML( $value );
42 * Get a translated interface message
44 * This is a wrapper around $this->mParent->msg() if $this->mParent is set
45 * and wfMessage() otherwise.
47 * Parameters are the same as wfMessage().
49 * @return Message object
52 $args = func_get_args();
54 if ( $this->mParent
) {
55 $callback = array( $this->mParent
, 'msg' );
57 $callback = 'wfMessage';
60 return call_user_func_array( $callback, $args );
64 * Override this function to add specific validation checks on the
65 * field input. Don't forget to call parent::validate() to ensure
66 * that the user-defined callback mValidationCallback is still run
68 * @param string $value The value the field was submitted with
69 * @param array $alldata The data collected from the form
71 * @return Mixed Bool true on success, or String error to display.
73 function validate( $value, $alldata ) {
74 if ( isset( $this->mParams
['required'] )
75 && $this->mParams
['required'] !== false
78 return $this->msg( 'htmlform-required' )->parse();
81 if ( isset( $this->mValidationCallback
) ) {
82 return call_user_func( $this->mValidationCallback
, $value, $alldata, $this->mParent
);
88 function filter( $value, $alldata ) {
89 if ( isset( $this->mFilterCallback
) ) {
90 $value = call_user_func( $this->mFilterCallback
, $value, $alldata, $this->mParent
);
97 * Should this field have a label, or is there no input element with the
98 * appropriate id for the label to point to?
100 * @return bool True to output a label, false to suppress
102 protected function needsLabel() {
107 * Tell the field whether to generate a separate label element if its label
112 * @param bool $show Set to false to not generate a label.
115 public function setShowEmptyLabel( $show ) {
116 $this->mShowEmptyLabels
= $show;
120 * Get the value that this input has been set to from a posted form,
121 * or the input's default value if it has not been set.
123 * @param WebRequest $request
124 * @return String the value
126 function loadDataFromRequest( $request ) {
127 if ( $request->getCheck( $this->mName
) ) {
128 return $request->getText( $this->mName
);
130 return $this->getDefault();
135 * Initialise the object
137 * @param array $params Associative Array. See HTMLForm doc for syntax.
139 * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
140 * @throws MWException
142 function __construct( $params ) {
143 $this->mParams
= $params;
145 # Generate the label from a message, if possible
146 if ( isset( $params['label-message'] ) ) {
147 $msgInfo = $params['label-message'];
149 if ( is_array( $msgInfo ) ) {
150 $msg = array_shift( $msgInfo );
156 $this->mLabel
= wfMessage( $msg, $msgInfo )->parse();
157 } elseif ( isset( $params['label'] ) ) {
158 if ( $params['label'] === ' ' ) {
159 // Apparently some things set   directly and in an odd format
160 $this->mLabel
= ' ';
162 $this->mLabel
= htmlspecialchars( $params['label'] );
164 } elseif ( isset( $params['label-raw'] ) ) {
165 $this->mLabel
= $params['label-raw'];
168 $this->mName
= "wp{$params['fieldname']}";
169 if ( isset( $params['name'] ) ) {
170 $this->mName
= $params['name'];
173 $validName = Sanitizer
::escapeId( $this->mName
);
174 if ( $this->mName
!= $validName && !isset( $params['nodata'] ) ) {
175 throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__
);
178 $this->mID
= "mw-input-{$this->mName}";
180 if ( isset( $params['default'] ) ) {
181 $this->mDefault
= $params['default'];
184 if ( isset( $params['id'] ) ) {
186 $validId = Sanitizer
::escapeId( $id );
188 if ( $id != $validId ) {
189 throw new MWException( "Invalid id '$id' passed to " . __METHOD__
);
195 if ( isset( $params['cssclass'] ) ) {
196 $this->mClass
= $params['cssclass'];
199 if ( isset( $params['validation-callback'] ) ) {
200 $this->mValidationCallback
= $params['validation-callback'];
203 if ( isset( $params['filter-callback'] ) ) {
204 $this->mFilterCallback
= $params['filter-callback'];
207 if ( isset( $params['flatlist'] ) ) {
208 $this->mClass
.= ' mw-htmlform-flatlist';
211 if ( isset( $params['hidelabel'] ) ) {
212 $this->mShowEmptyLabels
= false;
217 * Get the complete table row for the input, including help text,
218 * labels, and whatever.
220 * @param string $value The value to set the input to.
222 * @return string Complete HTML table row.
224 function getTableRow( $value ) {
225 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
226 $inputHtml = $this->getInputHTML( $value );
227 $fieldType = get_class( $this );
228 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
229 $cellAttributes = array();
231 if ( !empty( $this->mParams
['vertical-label'] ) ) {
232 $cellAttributes['colspan'] = 2;
233 $verticalLabel = true;
235 $verticalLabel = false;
238 $label = $this->getLabelHtml( $cellAttributes );
240 $field = Html
::rawElement(
242 array( 'class' => 'mw-input' ) +
$cellAttributes,
243 $inputHtml . "\n$errors"
246 if ( $verticalLabel ) {
247 $html = Html
::rawElement( 'tr', array( 'class' => 'mw-htmlform-vertical-label' ), $label );
248 $html .= Html
::rawElement( 'tr',
249 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
253 Html
::rawElement( 'tr',
254 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
258 return $html . $helptext;
262 * Get the complete div for the input, including help text,
263 * labels, and whatever.
266 * @param string $value The value to set the input to.
268 * @return string Complete HTML table row.
270 public function getDiv( $value ) {
271 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
272 $inputHtml = $this->getInputHTML( $value );
273 $fieldType = get_class( $this );
274 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
275 $cellAttributes = array();
276 $label = $this->getLabelHtml( $cellAttributes );
278 $outerDivClass = array(
280 'mw-htmlform-nolabel' => ( $label === '' )
283 $field = Html
::rawElement(
285 array( 'class' => $outerDivClass ) +
$cellAttributes,
286 $inputHtml . "\n$errors"
288 $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass
, $errorClass );
289 if ( $this->mParent
->isVForm() ) {
290 $divCssClasses[] = 'mw-ui-vform-div';
292 $html = Html
::rawElement( 'div', array( 'class' => $divCssClasses ), $label . $field );
299 * Get the complete raw fields for the input, including help text,
300 * labels, and whatever.
303 * @param string $value The value to set the input to.
305 * @return string Complete HTML table row.
307 public function getRaw( $value ) {
308 list( $errors, ) = $this->getErrorsAndErrorClass( $value );
309 $inputHtml = $this->getInputHTML( $value );
310 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
311 $cellAttributes = array();
312 $label = $this->getLabelHtml( $cellAttributes );
323 * Generate help text HTML in table format
326 * @param string|null $helptext
329 public function getHelpTextHtmlTable( $helptext ) {
330 if ( is_null( $helptext ) ) {
334 $row = Html
::rawElement( 'td', array( 'colspan' => 2, 'class' => 'htmlform-tip' ), $helptext );
335 $row = Html
::rawElement( 'tr', array(), $row );
341 * Generate help text HTML in div format
344 * @param string|null $helptext
348 public function getHelpTextHtmlDiv( $helptext ) {
349 if ( is_null( $helptext ) ) {
353 $div = Html
::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
359 * Generate help text HTML formatted for raw output
362 * @param string|null $helptext
365 public function getHelpTextHtmlRaw( $helptext ) {
366 return $this->getHelpTextHtmlDiv( $helptext );
370 * Determine the help text to display
374 public function getHelpText() {
377 if ( isset( $this->mParams
['help-message'] ) ) {
378 $this->mParams
['help-messages'] = array( $this->mParams
['help-message'] );
381 if ( isset( $this->mParams
['help-messages'] ) ) {
382 foreach ( $this->mParams
['help-messages'] as $name ) {
383 $helpMessage = (array)$name;
384 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
386 if ( $msg->exists() ) {
387 if ( is_null( $helptext ) ) {
390 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
392 $helptext .= $msg->parse(); // Append message
395 } elseif ( isset( $this->mParams
['help'] ) ) {
396 $helptext = $this->mParams
['help'];
403 * Determine form errors to display and their classes
406 * @param string $value The value of the input
409 public function getErrorsAndErrorClass( $value ) {
410 $errors = $this->validate( $value, $this->mParent
->mFieldData
);
412 if ( $errors === true ||
413 ( !$this->mParent
->getRequest()->wasPosted() && $this->mParent
->getMethod() === 'post' )
418 $errors = self
::formatErrors( $errors );
419 $errorClass = 'mw-htmlform-invalid-input';
422 return array( $errors, $errorClass );
425 function getLabel() {
426 return is_null( $this->mLabel
) ?
'' : $this->mLabel
;
429 function getLabelHtml( $cellAttributes = array() ) {
430 # Don't output a for= attribute for labels with no associated input.
431 # Kind of hacky here, possibly we don't want these to be <label>s at all.
434 if ( $this->needsLabel() ) {
435 $for['for'] = $this->mID
;
438 $labelValue = trim( $this->getLabel() );
440 if ( $labelValue !== ' ' && $labelValue !== '' ) {
444 $displayFormat = $this->mParent
->getDisplayFormat();
447 if ( $displayFormat === 'table' ) {
449 Html
::rawElement( 'td',
450 array( 'class' => 'mw-label' ) +
$cellAttributes,
451 Html
::rawElement( 'label', $for, $labelValue ) );
452 } elseif ( $hasLabel ||
$this->mShowEmptyLabels
) {
453 if ( $displayFormat === 'div' ) {
455 Html
::rawElement( 'div',
456 array( 'class' => 'mw-label' ) +
$cellAttributes,
457 Html
::rawElement( 'label', $for, $labelValue ) );
459 $html = Html
::rawElement( 'label', $for, $labelValue );
466 function getDefault() {
467 if ( isset( $this->mDefault
) ) {
468 return $this->mDefault
;
475 * Returns the attributes required for the tooltip and accesskey.
477 * @return array Attributes
479 public function getTooltipAndAccessKey() {
480 if ( empty( $this->mParams
['tooltip'] ) ) {
484 return Linker
::tooltipAndAccesskeyAttribs( $this->mParams
['tooltip'] );
488 * flatten an array of options to a single array, for instance,
489 * a set of "<options>" inside "<optgroups>".
491 * @param array $options Associative Array with values either Strings
493 * @return array Flattened input
495 public static function flattenOptions( $options ) {
498 foreach ( $options as $value ) {
499 if ( is_array( $value ) ) {
500 $flatOpts = array_merge( $flatOpts, self
::flattenOptions( $value ) );
502 $flatOpts[] = $value;
510 * Formats one or more errors as accepted by field validation-callback.
512 * @param string|Message|array $errors String|Message|Array of strings or Message instances
513 * @return string HTML
516 protected static function formatErrors( $errors ) {
517 if ( is_array( $errors ) && count( $errors ) === 1 ) {
518 $errors = array_shift( $errors );
521 if ( is_array( $errors ) ) {
523 foreach ( $errors as $error ) {
524 if ( $error instanceof Message
) {
525 $lines[] = Html
::rawElement( 'li', array(), $error->parse() );
527 $lines[] = Html
::rawElement( 'li', array(), $error );
531 return Html
::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
533 if ( $errors instanceof Message
) {
534 $errors = $errors->parse();
537 return Html
::rawElement( 'span', array( 'class' => 'error' ), $errors );