3 * The parent class to generate form fields. Any field type should
4 * be a subclass of this.
6 abstract class HTMLFormField
{
8 protected $mValidationCallback;
9 protected $mFilterCallback;
12 protected $mLabel; # String label. Set on construction
14 protected $mClass = '';
18 * @var bool If true will generate an empty div element with no label
21 protected $mShowEmptyLabels = true;
29 * This function must be implemented to return the HTML to generate
30 * the input object itself. It should not implement the surrounding
31 * table cells/rows, or labels/help messages.
33 * @param string $value the value to set the input to; eg a default
34 * text for a text input.
36 * @return String valid HTML.
38 abstract function getInputHTML( $value );
41 * Get a translated interface message
43 * This is a wrapper around $this->mParent->msg() if $this->mParent is set
44 * and wfMessage() otherwise.
46 * Parameters are the same as wfMessage().
48 * @return Message object
51 $args = func_get_args();
53 if ( $this->mParent
) {
54 $callback = array( $this->mParent
, 'msg' );
56 $callback = 'wfMessage';
59 return call_user_func_array( $callback, $args );
63 * Override this function to add specific validation checks on the
64 * field input. Don't forget to call parent::validate() to ensure
65 * that the user-defined callback mValidationCallback is still run
67 * @param string $value the value the field was submitted with
68 * @param array $alldata the data collected from the form
70 * @return Mixed Bool true on success, or String error to display.
72 function validate( $value, $alldata ) {
73 if ( isset( $this->mParams
[ 'required' ] ) && $this->mParams
[ 'required' ] !== false && $value === '' ) {
74 return $this->msg( 'htmlform-required' )->parse();
77 if ( isset( $this->mValidationCallback
) ) {
78 return call_user_func( $this->mValidationCallback
, $value, $alldata, $this->mParent
);
84 function filter( $value, $alldata ) {
85 if ( isset( $this->mFilterCallback
) ) {
86 $value = call_user_func( $this->mFilterCallback
, $value, $alldata, $this->mParent
);
93 * Should this field have a label, or is there no input element with the
94 * appropriate id for the label to point to?
96 * @return bool True to output a label, false to suppress
98 protected function needsLabel() {
103 * Tell the field whether to generate a separate label element if its label
108 * @param bool $show Set to false to not generate a label.
112 public function setShowEmptyLabel( $show ) {
113 $this->mShowEmptyLabels
= $show;
117 * Get the value that this input has been set to from a posted form,
118 * or the input's default value if it has not been set.
120 * @param $request WebRequest
122 * @return String the value
124 function loadDataFromRequest( $request ) {
125 if ( $request->getCheck( $this->mName
) ) {
126 return $request->getText( $this->mName
);
128 return $this->getDefault();
133 * Initialise the object
135 * @param array $params Associative Array. See HTMLForm doc for syntax.
137 * @since 1.22 The 'label' attribute no longer accepts raw HTML, use 'label-raw' instead
138 * @throws MWException
140 function __construct( $params ) {
141 $this->mParams
= $params;
143 # Generate the label from a message, if possible
144 if ( isset( $params[ 'label-message' ] ) ) {
145 $msgInfo = $params[ 'label-message' ];
147 if ( is_array( $msgInfo ) ) {
148 $msg = array_shift( $msgInfo );
154 $this->mLabel
= wfMessage( $msg, $msgInfo )->parse();
155 } elseif ( isset( $params[ 'label' ] ) ) {
156 if ( $params[ 'label' ] === ' ' ) {
157 // Apparently some things set   directly and in an odd format
158 $this->mLabel
= ' ';
160 $this->mLabel
= htmlspecialchars( $params[ 'label' ] );
162 } elseif ( isset( $params[ 'label-raw' ] ) ) {
163 $this->mLabel
= $params[ 'label-raw' ];
166 $this->mName
= "wp{$params['fieldname']}";
167 if ( isset( $params[ 'name' ] ) ) {
168 $this->mName
= $params[ 'name' ];
171 $validName = Sanitizer
::escapeId( $this->mName
);
172 if ( $this->mName
!= $validName && ! isset( $params[ 'nodata' ] ) ) {
173 throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__
);
176 $this->mID
= "mw-input-{$this->mName}";
178 if ( isset( $params[ 'default' ] ) ) {
179 $this->mDefault
= $params[ 'default' ];
182 if ( isset( $params[ 'id' ] ) ) {
183 $id = $params[ 'id' ];
184 $validId = Sanitizer
::escapeId( $id );
186 if ( $id != $validId ) {
187 throw new MWException( "Invalid id '$id' passed to " . __METHOD__
);
193 if ( isset( $params[ 'cssclass' ] ) ) {
194 $this->mClass
= $params[ 'cssclass' ];
197 if ( isset( $params[ 'validation-callback' ] ) ) {
198 $this->mValidationCallback
= $params[ 'validation-callback' ];
201 if ( isset( $params[ 'filter-callback' ] ) ) {
202 $this->mFilterCallback
= $params[ 'filter-callback' ];
205 if ( isset( $params[ 'flatlist' ] ) ) {
206 $this->mClass
.= ' mw-htmlform-flatlist';
209 if ( isset( $params[ 'hidelabel' ] ) ) {
210 $this->mShowEmptyLabels
= false;
215 * Get the complete table row for the input, including help text,
216 * labels, and whatever.
218 * @param string $value the value to set the input to.
220 * @return String complete HTML table row.
222 function getTableRow( $value ) {
223 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
224 $inputHtml = $this->getInputHTML( $value );
225 $fieldType = get_class( $this );
226 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
227 $cellAttributes = array();
229 if ( ! empty( $this->mParams
[ 'vertical-label' ] ) ) {
230 $cellAttributes[ 'colspan' ] = 2;
231 $verticalLabel = true;
233 $verticalLabel = false;
236 $label = $this->getLabelHtml( $cellAttributes );
238 $field = Html
::rawElement( 'td', array( 'class' => 'mw-input' ) +
$cellAttributes, $inputHtml . "\n$errors" );
240 if ( $verticalLabel ) {
241 $html = Html
::rawElement( 'tr', array( 'class' => 'mw-htmlform-vertical-label' ), $label );
242 $html .= Html
::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), $field );
244 $html = Html
::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), $label . $field );
247 return $html . $helptext;
251 * Get the complete div for the input, including help text,
252 * labels, and whatever.
255 * @param string $value the value to set the input to.
257 * @return String complete HTML table row.
259 public function getDiv( $value ) {
260 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
261 $inputHtml = $this->getInputHTML( $value );
262 $fieldType = get_class( $this );
263 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
264 $cellAttributes = array();
265 $label = $this->getLabelHtml( $cellAttributes );
267 $outerDivClass = array(
269 'mw-htmlform-nolabel' => ( $label === '' )
272 $field = Html
::rawElement( 'div', array( 'class' => $outerDivClass ) +
$cellAttributes, $inputHtml . "\n$errors" );
273 $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass
, $errorClass );
274 if ( $this->mParent
->isVForm() ) {
275 $divCssClasses[ ] = 'mw-ui-vform-div';
277 $html = Html
::rawElement( 'div', array( 'class' => $divCssClasses ), $label . $field );
283 * Get the complete raw fields for the input, including help text,
284 * labels, and whatever.
287 * @param string $value the value to set the input to.
289 * @return String complete HTML table row.
291 public function getRaw( $value ) {
292 list( $errors, ) = $this->getErrorsAndErrorClass( $value );
293 $inputHtml = $this->getInputHTML( $value );
294 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
295 $cellAttributes = array();
296 $label = $this->getLabelHtml( $cellAttributes );
306 * Generate help text HTML in table format
309 * @param $helptext String|null
313 public function getHelpTextHtmlTable( $helptext ) {
314 if ( is_null( $helptext ) ) {
318 $row = Html
::rawElement( 'td', array( 'colspan' => 2, 'class' => 'htmlform-tip' ), $helptext );
319 $row = Html
::rawElement( 'tr', array(), $row );
324 * Generate help text HTML in div format
327 * @param $helptext String|null
331 public function getHelpTextHtmlDiv( $helptext ) {
332 if ( is_null( $helptext ) ) {
336 $div = Html
::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
341 * Generate help text HTML formatted for raw output
344 * @param $helptext String|null
348 public function getHelpTextHtmlRaw( $helptext ) {
349 return $this->getHelpTextHtmlDiv( $helptext );
353 * Determine the help text to display
357 public function getHelpText() {
360 if ( isset( $this->mParams
[ 'help-message' ] ) ) {
361 $this->mParams
[ 'help-messages' ] = array( $this->mParams
[ 'help-message' ] );
364 if ( isset( $this->mParams
[ 'help-messages' ] ) ) {
365 foreach ( $this->mParams
[ 'help-messages' ] as $name ) {
366 $helpMessage = (array)$name;
367 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
369 if ( $msg->exists() ) {
370 if ( is_null( $helptext ) ) {
373 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
375 $helptext .= $msg->parse(); // Append message
378 } elseif ( isset( $this->mParams
[ 'help' ] ) ) {
379 $helptext = $this->mParams
[ 'help' ];
385 * Determine form errors to display and their classes
388 * @param string $value the value of the input
392 public function getErrorsAndErrorClass( $value ) {
393 $errors = $this->validate( $value, $this->mParent
->mFieldData
);
395 if ( $errors === true ||
( ! $this->mParent
->getRequest()->wasPosted() && ( $this->mParent
->getMethod() == 'post' ) ) ) {
399 $errors = self
::formatErrors( $errors );
400 $errorClass = 'mw-htmlform-invalid-input';
402 return array( $errors, $errorClass );
405 function getLabel() {
406 return is_null( $this->mLabel
) ?
'' : $this->mLabel
;
409 function getLabelHtml( $cellAttributes = array() ) {
410 # Don't output a for= attribute for labels with no associated input.
411 # Kind of hacky here, possibly we don't want these to be <label>s at all.
414 if ( $this->needsLabel() ) {
415 $for[ 'for' ] = $this->mID
;
418 $labelValue = trim( $this->getLabel() );
420 if ( $labelValue !== ' ' && $labelValue !== '' ) {
424 $displayFormat = $this->mParent
->getDisplayFormat();
427 if ( $displayFormat === 'table' ) {
428 $html = Html
::rawElement( 'td', array( 'class' => 'mw-label' ) +
$cellAttributes, Html
::rawElement( 'label', $for, $labelValue ) );
429 } elseif ( $hasLabel ||
$this->mShowEmptyLabels
) {
430 if ( $displayFormat === 'div' ) {
431 $html = Html
::rawElement( 'div', array( 'class' => 'mw-label' ) +
$cellAttributes, Html
::rawElement( 'label', $for, $labelValue ) );
433 $html = Html
::rawElement( 'label', $for, $labelValue );
440 function getDefault() {
441 if ( isset( $this->mDefault
) ) {
442 return $this->mDefault
;
449 * Returns the attributes required for the tooltip and accesskey.
451 * @return array Attributes
453 public function getTooltipAndAccessKey() {
454 if ( empty( $this->mParams
[ 'tooltip' ] ) ) {
457 return Linker
::tooltipAndAccesskeyAttribs( $this->mParams
[ 'tooltip' ] );
461 * flatten an array of options to a single array, for instance,
462 * a set of "<options>" inside "<optgroups>".
464 * @param array $options Associative Array with values either Strings
467 * @return Array flattened input
469 public static function flattenOptions( $options ) {
472 foreach ( $options as $value ) {
473 if ( is_array( $value ) ) {
474 $flatOpts = array_merge( $flatOpts, self
::flattenOptions( $value ) );
476 $flatOpts[ ] = $value;
484 * Formats one or more errors as accepted by field validation-callback.
486 * @param $errors String|Message|Array of strings or Message instances
488 * @return String html
491 protected static function formatErrors( $errors ) {
492 if ( is_array( $errors ) && count( $errors ) === 1 ) {
493 $errors = array_shift( $errors );
496 if ( is_array( $errors ) ) {
498 foreach ( $errors as $error ) {
499 if ( $error instanceof Message
) {
500 $lines[ ] = Html
::rawElement( 'li', array(), $error->parse() );
502 $lines[ ] = Html
::rawElement( 'li', array(), $error );
505 return Html
::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
507 if ( $errors instanceof Message
) {
508 $errors = $errors->parse();
510 return Html
::rawElement( 'span', array( 'class' => 'error' ), $errors );