summaryrefslogtreecommitdiff
path: root/includes/htmlform
diff options
context:
space:
mode:
Diffstat (limited to 'includes/htmlform')
-rw-r--r--includes/htmlform/HTMLAutoCompleteSelectField.php14
-rw-r--r--includes/htmlform/HTMLButtonField.php36
-rw-r--r--includes/htmlform/HTMLCheckField.php62
-rw-r--r--includes/htmlform/HTMLCheckMatrix.php42
-rw-r--r--includes/htmlform/HTMLForm.php185
-rw-r--r--includes/htmlform/HTMLFormField.php193
-rw-r--r--includes/htmlform/HTMLFormFieldWithButton.php73
-rw-r--r--includes/htmlform/HTMLInfoField.php10
-rw-r--r--includes/htmlform/HTMLMultiSelectField.php60
-rw-r--r--includes/htmlform/HTMLRadioField.php17
-rw-r--r--includes/htmlform/HTMLSelectAndOtherField.php5
-rw-r--r--includes/htmlform/HTMLSelectField.php22
-rw-r--r--includes/htmlform/HTMLSelectNamespace.php16
-rw-r--r--includes/htmlform/HTMLSelectNamespaceWithButton.php17
-rw-r--r--includes/htmlform/HTMLSelectOrOtherField.php4
-rw-r--r--includes/htmlform/HTMLSubmitField.php2
-rw-r--r--includes/htmlform/HTMLTextAreaField.php44
-rw-r--r--includes/htmlform/HTMLTextField.php61
-rw-r--r--includes/htmlform/HTMLTextFieldWithButton.php17
-rw-r--r--includes/htmlform/HTMLTitleTextField.php81
-rw-r--r--includes/htmlform/HTMLUserTextField.php47
-rw-r--r--includes/htmlform/OOUIHTMLForm.php221
-rw-r--r--includes/htmlform/VFormHTMLForm.php6
23 files changed, 1131 insertions, 104 deletions
diff --git a/includes/htmlform/HTMLAutoCompleteSelectField.php b/includes/htmlform/HTMLAutoCompleteSelectField.php
index 49053628..55cd5d0c 100644
--- a/includes/htmlform/HTMLAutoCompleteSelectField.php
+++ b/includes/htmlform/HTMLAutoCompleteSelectField.php
@@ -98,11 +98,12 @@ class HTMLAutoCompleteSelectField extends HTMLTextField {
return true;
}
- function getAttributes( array $list ) {
+ // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
+ public function getAttributes( array $list, array $mappings = null ) {
$attribs = array(
'type' => 'text',
'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ),
- ) + parent::getAttributes( $list );
+ ) + parent::getAttributes( $list, $mappings );
if ( $this->getOptions() ) {
$attribs['data-hide-if'] = FormatJson::encode(
@@ -162,4 +163,13 @@ class HTMLAutoCompleteSelectField extends HTMLTextField {
return $ret;
}
+ /**
+ * Get the OOUI version of this input.
+ * @param string $value
+ * @return false
+ */
+ function getInputOOUI( $value ) {
+ // To be implemented, for now override the function from HTMLTextField
+ return false;
+ }
}
diff --git a/includes/htmlform/HTMLButtonField.php b/includes/htmlform/HTMLButtonField.php
index 09c0ad97..56a23ad2 100644
--- a/includes/htmlform/HTMLButtonField.php
+++ b/includes/htmlform/HTMLButtonField.php
@@ -10,20 +10,54 @@
class HTMLButtonField extends HTMLFormField {
protected $buttonType = 'button';
+ /** @var array $mFlags Flags to add to OOUI Button widget */
+ protected $mFlags = array();
+
public function __construct( $info ) {
$info['nodata'] = true;
+ if ( isset( $info['flags'] ) )
+ $this->mFlags = $info['flags'];
parent::__construct( $info );
}
public function getInputHTML( $value ) {
+ $flags = '';
+ $prefix = 'mw-htmlform-';
+ if ( $this->mParent instanceof VFormHTMLForm ||
+ $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' )
+ ) {
+ $prefix = 'mw-ui-';
+ // add mw-ui-button separately, so the descriptor doesn't need to set it
+ $flags .= ' ' . $prefix.'button';
+ }
+ foreach ( $this->mFlags as $flag ) {
+ $flags .= ' ' . $prefix . $flag;
+ }
$attr = array(
- 'class' => 'mw-htmlform-submit ' . $this->mClass,
+ 'class' => 'mw-htmlform-submit ' . $this->mClass . $flags,
'id' => $this->mID,
) + $this->getAttributes( array( 'disabled', 'tabindex' ) );
return Html::input( $this->mName, $value, $this->buttonType, $attr );
}
+ /**
+ * Get the OOUI widget for this field.
+ * @param string $value
+ * @return OOUI\\ButtonInputWidget
+ */
+ public function getInputOOUI( $value ) {
+ return new OOUI\ButtonInputWidget( array(
+ 'name' => $this->mName,
+ 'value' => $value,
+ 'label' => $value,
+ 'type' => $this->buttonType,
+ 'classes' => array( 'mw-htmlform-submit', $this->mClass ),
+ 'id' => $this->mID,
+ 'flags' => $this->mFlags,
+ ) + $this->getAttributes( array( 'disabled', 'tabindex' ), array( 'tabindex' => 'tabIndex' ) ) );
+ }
+
protected function needsLabel() {
return false;
}
diff --git a/includes/htmlform/HTMLCheckField.php b/includes/htmlform/HTMLCheckField.php
index 4942327f..9666c4ea 100644
--- a/includes/htmlform/HTMLCheckField.php
+++ b/includes/htmlform/HTMLCheckField.php
@@ -20,9 +20,15 @@ class HTMLCheckField extends HTMLFormField {
$attr['class'] = $this->mClass;
}
- $chkLabel = Xml::check( $this->mName, $value, $attr )
- . ' '
- . Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
+ $attrLabel = array( 'for' => $this->mID );
+ if ( isset( $attr['title'] ) ) {
+ // propagate tooltip to label
+ $attrLabel['title'] = $attr['title'];
+ }
+
+ $chkLabel = Xml::check( $this->mName, $value, $attr ) .
+ ' ' .
+ Html::rawElement( 'label', $attrLabel, $this->mLabel );
if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
$chkLabel = Html::rawElement(
@@ -36,12 +42,60 @@ class HTMLCheckField extends HTMLFormField {
}
/**
+ * Get the OOUI version of this field.
+ * @since 1.26
+ * @param string $value
+ * @return OOUI\\CheckboxInputWidget The checkbox widget.
+ */
+ public function getInputOOUI( $value ) {
+ if ( !empty( $this->mParams['invert'] ) ) {
+ $value = !$value;
+ }
+
+ $attr = $this->getTooltipAndAccessKey();
+ $attr['id'] = $this->mID;
+ $attr['name'] = $this->mName;
+
+ $attr += $this->getAttributes( array( 'disabled', 'tabindex' ), array( 'tabindex' => 'tabIndex' ) );
+
+ if ( $this->mClass !== '' ) {
+ $attr['classes'] = array( $this->mClass );
+ }
+
+ $attr['selected'] = $value;
+ $attr['value'] = '1'; // Nasty hack, but needed to make this work
+
+ return new OOUI\CheckboxInputWidget( $attr );
+ }
+
+ /**
* For a checkbox, the label goes on the right hand side, and is
* added in getInputHTML(), rather than HTMLFormField::getRow()
+ *
+ * ...unless OOUI is being used, in which case we actually return
+ * the label here.
+ *
* @return string
*/
function getLabel() {
- return ' ';
+ if ( $this->mParent instanceof OOUIHTMLForm ) {
+ return $this->mLabel;
+ } elseif (
+ $this->mParent instanceof HTMLForm &&
+ $this->mParent->getDisplayFormat() === 'div'
+ ) {
+ return '';
+ } else {
+ return ' ';
+ }
+ }
+
+ /**
+ * Get label alignment when generating field for OOUI.
+ * @return string 'left', 'right', 'top' or 'inline'
+ */
+ protected function getLabelAlignOOUI() {
+ return 'inline';
}
/**
diff --git a/includes/htmlform/HTMLCheckMatrix.php b/includes/htmlform/HTMLCheckMatrix.php
index 83f12665..a0566a03 100644
--- a/includes/htmlform/HTMLCheckMatrix.php
+++ b/includes/htmlform/HTMLCheckMatrix.php
@@ -85,7 +85,13 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
$rows = $this->mParams['rows'];
$columns = $this->mParams['columns'];
- $attribs = $this->getAttributes( array( 'disabled', 'tabindex' ) );
+ $mappings = array();
+
+ if ( $this->mParent instanceof OOUIHTMLForm ) {
+ $mappings['tabindex'] = 'tabIndex';
+ }
+
+ $attribs = $this->getAttributes( array( 'disabled', 'tabindex' ), $mappings );
// Build the column headers
$headerContents = Html::rawElement( 'td', array(), ' ' );
@@ -113,9 +119,8 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
foreach ( $columns as $columnTag ) {
$thisTag = "$columnTag-$rowTag";
// Construct the checkbox
- $thisId = "{$this->mID}-$thisTag";
$thisAttribs = array(
- 'id' => $thisId,
+ 'id' => "{$this->mID}-$thisTag",
'value' => $thisTag,
);
$checked = in_array( $thisTag, (array)$value, true );
@@ -126,17 +131,13 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
$checked = true;
$thisAttribs['disabled'] = 1;
}
- $chkBox = Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs );
- if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
- $chkBox = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
- $chkBox .
- Html::element( 'label', array( 'for' => $thisId ) ) .
- Html::closeElement( 'div' );
- }
+
+ $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs );
+
$rowContents .= Html::rawElement(
'td',
array(),
- $chkBox
+ $checkbox
);
}
$tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
@@ -150,6 +151,25 @@ class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
return $html;
}
+ protected function getOneCheckbox( $checked, $attribs ) {
+ if ( $this->mParent instanceof OOUIHTMLForm ) {
+ return new OOUI\CheckboxInputWidget( array(
+ 'name' => "{$this->mName}[]",
+ 'selected' => $checked,
+ 'value' => $attribs['value'],
+ ) + $attribs );
+ } else {
+ $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
+ if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $checkbox = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $checkbox .
+ Html::element( 'label', array( 'for' => $attribs['id'] ) ) .
+ Html::closeElement( 'div' );
+ }
+ return $checkbox;
+ }
+ }
+
protected function isTagForcedOff( $tag ) {
return isset( $this->mParams['force-options-off'] )
&& in_array( $tag, $this->mParams['force-options-off'] );
diff --git a/includes/htmlform/HTMLForm.php b/includes/htmlform/HTMLForm.php
index ce140038..08fa0a91 100644
--- a/includes/htmlform/HTMLForm.php
+++ b/includes/htmlform/HTMLForm.php
@@ -51,6 +51,7 @@
* 'id' -- HTML id attribute
* 'cssclass' -- CSS class
* 'csshelpclass' -- CSS class used to style help text
+ * 'dir' -- Direction of the element.
* 'options' -- associative array mapping labels to values.
* Some field types support multi-level arrays.
* 'options-messages' -- associative array mapping message keys to values.
@@ -75,14 +76,35 @@
* 'size' -- the length of text fields
* 'filter-callback -- a function name to give you the chance to
* massage the inputted value before it's processed.
- * @see HTMLForm::filter()
+ * @see HTMLFormField::filter()
* 'validation-callback' -- a function name to give you the chance
* to impose extra validation on the field input.
- * @see HTMLForm::validate()
+ * @see HTMLFormField::validate()
* 'name' -- By default, the 'name' attribute of the input field
* is "wp{$fieldname}". If you want a different name
* (eg one without the "wp" prefix), specify it here and
* it will be used without modification.
+ * 'hide-if' -- expression given as an array stating when the field
+ * should be hidden. The first array value has to be the
+ * expression's logic operator. Supported expressions:
+ * 'NOT'
+ * [ 'NOT', array $expression ]
+ * To hide a field if a given expression is not true.
+ * '==='
+ * [ '===', string $fieldName, string $value ]
+ * To hide a field if another field identified by
+ * $field has the value $value.
+ * '!=='
+ * [ '!==', string $fieldName, string $value ]
+ * Same as [ 'NOT', [ '===', $fieldName, $value ]
+ * 'OR', 'AND', 'NOR', 'NAND'
+ * [ 'XXX', array $expression1, ..., array $expressionN ]
+ * To hide a field if one or more (OR), all (AND),
+ * neither (NOR) or not all (NAND) given expressions
+ * are evaluated as true.
+ * The expressions will be given to a JavaScript frontend
+ * module which will continually update the field's
+ * visibility.
*
* Since 1.20, you can chain mutators to ease the form generation:
* @par Example:
@@ -103,6 +125,7 @@ class HTMLForm extends ContextSource {
public static $typeMappings = array(
'api' => 'HTMLApiField',
'text' => 'HTMLTextField',
+ 'textwithbutton' => 'HTMLTextFieldWithButton',
'textarea' => 'HTMLTextAreaField',
'select' => 'HTMLSelectField',
'radio' => 'HTMLRadioField',
@@ -116,6 +139,7 @@ class HTMLForm extends ContextSource {
'selectorother' => 'HTMLSelectOrOtherField',
'selectandother' => 'HTMLSelectAndOtherField',
'namespaceselect' => 'HTMLSelectNamespace',
+ 'namespaceselectwithbutton' => 'HTMLSelectNamespaceWithButton',
'tagfilter' => 'HTMLTagFilter',
'submit' => 'HTMLSubmitField',
'hidden' => 'HTMLHiddenField',
@@ -129,6 +153,8 @@ class HTMLForm extends ContextSource {
'email' => 'HTMLTextField',
'password' => 'HTMLTextField',
'url' => 'HTMLTextField',
+ 'title' => 'HTMLTitleTextField',
+ 'user' => 'HTMLUserTextField',
);
public $mFieldData;
@@ -141,7 +167,7 @@ class HTMLForm extends ContextSource {
protected $mFieldTree;
protected $mShowReset = false;
protected $mShowSubmit = true;
- protected $mSubmitModifierClass = 'mw-ui-constructive';
+ protected $mSubmitFlags = array( 'constructive', 'primary' );
protected $mSubmitCallback;
protected $mValidationErrorMessage;
@@ -216,12 +242,12 @@ class HTMLForm extends ContextSource {
*/
protected $availableSubclassDisplayFormats = array(
'vform',
+ 'ooui',
);
/**
* Construct a HTMLForm object for given display type. May return a HTMLForm subclass.
*
- * @throws MWException When the display format requested is not known
* @param string $displayFormat
* @param mixed $arguments... Additional arguments to pass to the constructor.
* @return HTMLForm
@@ -234,6 +260,9 @@ class HTMLForm extends ContextSource {
case 'vform':
$reflector = new ReflectionClass( 'VFormHTMLForm' );
return $reflector->newInstanceArgs( $arguments );
+ case 'ooui':
+ $reflector = new ReflectionClass( 'OOUIHTMLForm' );
+ return $reflector->newInstanceArgs( $arguments );
default:
$reflector = new ReflectionClass( 'HTMLForm' );
$form = $reflector->newInstanceArgs( $arguments );
@@ -266,7 +295,10 @@ class HTMLForm extends ContextSource {
}
// Evil hack for mobile :(
- if ( !$this->getConfig()->get( 'HTMLFormAllowTableFormat' ) && $this->displayFormat === 'table' ) {
+ if (
+ !$this->getConfig()->get( 'HTMLFormAllowTableFormat' )
+ && $this->displayFormat === 'table'
+ ) {
$this->displayFormat = 'div';
}
@@ -405,7 +437,9 @@ class HTMLForm extends ContextSource {
* @throws MWException
* @return HTMLFormField Instance of a subclass of HTMLFormField
*/
- public static function loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent = null ) {
+ public static function loadInputFromParameters( $fieldname, $descriptor,
+ HTMLForm $parent = null
+ ) {
$class = static::getClassFromDescriptor( $fieldname, $descriptor );
$descriptor['fieldname'] = $fieldname;
@@ -677,6 +711,21 @@ class HTMLForm extends ContextSource {
}
/**
+ * Get header text.
+ *
+ * @param string|null $section The section to get the header text for
+ * @since 1.26
+ * @return string
+ */
+ function getHeaderText( $section = null ) {
+ if ( is_null( $section ) ) {
+ return $this->mHeader;
+ } else {
+ return isset( $this->mSectionHeaders[$section] ) ? $this->mSectionHeaders[$section] : '';
+ }
+ }
+
+ /**
* Add footer text, inside the form.
*
* @param string $msg Complete text of message to display
@@ -717,6 +766,21 @@ class HTMLForm extends ContextSource {
}
/**
+ * Get footer text.
+ *
+ * @param string|null $section The section to get the footer text for
+ * @since 1.26
+ * @return string
+ */
+ function getFooterText( $section = null ) {
+ if ( is_null( $section ) ) {
+ return $this->mFooter;
+ } else {
+ return isset( $this->mSectionFooters[$section] ) ? $this->mSectionFooters[$section] : '';
+ }
+ }
+
+ /**
* Add text to the end of the display.
*
* @param string $msg Complete text of message to display
@@ -834,14 +898,15 @@ class HTMLForm extends ContextSource {
# For good measure (it is the default)
$this->getOutput()->preventClickjacking();
$this->getOutput()->addModules( 'mediawiki.htmlform' );
+ $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' );
$html = ''
. $this->getErrors( $submitResult )
- . $this->mHeader
+ . $this->getHeaderText()
. $this->getBody()
. $this->getHiddenFields()
. $this->getButtons()
- . $this->mFooter;
+ . $this->getFooterText();
$html = $this->wrapForm( $html );
@@ -861,7 +926,6 @@ class HTMLForm extends ContextSource {
$attribs = array(
'action' => $this->getAction(),
'method' => $this->getMethod(),
- 'class' => array( 'visualClear' ),
'enctype' => $encType,
);
if ( !empty( $this->mId ) ) {
@@ -880,10 +944,11 @@ class HTMLForm extends ContextSource {
function wrapForm( $html ) {
# Include a <fieldset> wrapper for style, if requested.
if ( $this->mWrapperLegend !== false ) {
- $html = Xml::fieldset( $this->mWrapperLegend, $html );
+ $legend = is_string( $this->mWrapperLegend ) ? $this->mWrapperLegend : false;
+ $html = Xml::fieldset( $legend, $html );
}
- return Html::rawElement( 'form', $this->getFormAttributes(), $html );
+ return Html::rawElement( 'form', $this->getFormAttributes() + array( 'class' => 'visualClear' ), $html );
}
/**
@@ -940,7 +1005,10 @@ class HTMLForm extends ContextSource {
$attribs['class'] = array( 'mw-htmlform-submit' );
if ( $useMediaWikiUIEverywhere ) {
- array_push( $attribs['class'], 'mw-ui-button', $this->mSubmitModifierClass );
+ foreach ( $this->mSubmitFlags as $flag ) {
+ array_push( $attribs['class'], 'mw-ui-' . $flag );
+ }
+ array_push( $attribs['class'], 'mw-ui-button' );
}
$buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
@@ -1067,7 +1135,7 @@ class HTMLForm extends ContextSource {
* @since 1.24
*/
public function setSubmitDestructive() {
- $this->mSubmitModifierClass = 'mw-ui-destructive';
+ $this->mSubmitFlags = array( 'destructive', 'primary' );
}
/**
@@ -1075,7 +1143,7 @@ class HTMLForm extends ContextSource {
* @since 1.25
*/
public function setSubmitProgressive() {
- $this->mSubmitModifierClass = 'mw-ui-progressive';
+ $this->mSubmitFlags = array( 'progressive', 'primary' );
}
/**
@@ -1187,9 +1255,10 @@ class HTMLForm extends ContextSource {
* Prompt the whole form to be wrapped in a "<fieldset>", with
* this text as its "<legend>" element.
*
- * @param string|bool $legend HTML to go inside the "<legend>" element, or
- * false for no <legend>
- * Will be escaped
+ * @param string|bool $legend If false, no wrapper or legend will be displayed.
+ * If true, a wrapper will be displayed, but no legend.
+ * If a string, a wrapper will be displayed with that string as a legend.
+ * The string will be escaped before being output (this doesn't support HTML).
*
* @return HTMLForm $this for chaining calls (since 1.20)
*/
@@ -1263,11 +1332,14 @@ class HTMLForm extends ContextSource {
* @return HTMLForm $this for chaining calls (since 1.20)
*/
public function setMethod( $method = 'post' ) {
- $this->mMethod = $method;
+ $this->mMethod = strtolower( $method );
return $this;
}
+ /**
+ * @return string Always lowercase
+ */
public function getMethod() {
return $this->mMethod;
}
@@ -1291,7 +1363,7 @@ class HTMLForm extends ContextSource {
&$hasUserVisibleFields = false ) {
$displayFormat = $this->getDisplayFormat();
- $html = '';
+ $html = array();
$subsectionHtml = '';
$hasLabel = false;
@@ -1303,7 +1375,7 @@ class HTMLForm extends ContextSource {
$v = empty( $value->mParams['nodata'] )
? $this->mFieldData[$key]
: $value->getDefault();
- $html .= $value->$getFieldHtmlMethod( $v );
+ $html[] = $value->$getFieldHtmlMethod( $v );
$labelValue = trim( $value->getLabel() );
if ( $labelValue != '&#160;' && $labelValue !== '' ) {
@@ -1330,12 +1402,9 @@ class HTMLForm extends ContextSource {
$legend = $this->getLegend( $key );
- if ( isset( $this->mSectionHeaders[$key] ) ) {
- $section = $this->mSectionHeaders[$key] . $section;
- }
- if ( isset( $this->mSectionFooters[$key] ) ) {
- $section .= $this->mSectionFooters[$key];
- }
+ $section = $this->getHeaderText( $key ) .
+ $section .
+ $this->getFooterText( $key );
$attributes = array();
if ( $fieldsetIDPrefix ) {
@@ -1349,36 +1418,56 @@ class HTMLForm extends ContextSource {
}
}
- if ( $displayFormat !== 'raw' ) {
- $classes = array();
+ $html = $this->formatSection( $html, $sectionName, $hasLabel );
- if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
- $classes[] = 'mw-htmlform-nolabel';
+ if ( $subsectionHtml ) {
+ if ( $this->mSubSectionBeforeFields ) {
+ return $subsectionHtml . "\n" . $html;
+ } else {
+ return $html . "\n" . $subsectionHtml;
}
+ } else {
+ return $html;
+ }
+ }
- $attribs = array(
- 'class' => implode( ' ', $classes ),
- );
+ /**
+ * Put a form section together from the individual fields' HTML, merging it and wrapping.
+ * @param array $fieldsHtml
+ * @param string $sectionName
+ * @param bool $anyFieldHasLabel
+ * @return string HTML
+ */
+ protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
+ $displayFormat = $this->getDisplayFormat();
+ $html = implode( '', $fieldsHtml );
- if ( $sectionName ) {
- $attribs['id'] = Sanitizer::escapeId( $sectionName );
- }
+ if ( $displayFormat === 'raw' ) {
+ return $html;
+ }
- if ( $displayFormat === 'table' ) {
- $html = Html::rawElement( 'table',
- $attribs,
- Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
- } elseif ( $displayFormat === 'inline' ) {
- $html = Html::rawElement( 'span', $attribs, "\n$html\n" );
- } else {
- $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
- }
+ $classes = array();
+
+ if ( !$anyFieldHasLabel ) { // Avoid strange spacing when no labels exist
+ $classes[] = 'mw-htmlform-nolabel';
+ }
+
+ $attribs = array(
+ 'class' => implode( ' ', $classes ),
+ );
+
+ if ( $sectionName ) {
+ $attribs['id'] = Sanitizer::escapeId( $sectionName );
}
- if ( $this->mSubSectionBeforeFields ) {
- return $subsectionHtml . "\n" . $html;
+ if ( $displayFormat === 'table' ) {
+ return Html::rawElement( 'table',
+ $attribs,
+ Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
+ } elseif ( $displayFormat === 'inline' ) {
+ return Html::rawElement( 'span', $attribs, "\n$html\n" );
} else {
- return $html . "\n" . $subsectionHtml;
+ return Html::rawElement( 'div', $attribs, "\n$html\n" );
}
}
diff --git a/includes/htmlform/HTMLFormField.php b/includes/htmlform/HTMLFormField.php
index 9576c77c..13756e3d 100644
--- a/includes/htmlform/HTMLFormField.php
+++ b/includes/htmlform/HTMLFormField.php
@@ -10,6 +10,7 @@ abstract class HTMLFormField {
protected $mValidationCallback;
protected $mFilterCallback;
protected $mName;
+ protected $mDir;
protected $mLabel; # String label. Set on construction
protected $mID;
protected $mClass = '';
@@ -44,6 +45,17 @@ abstract class HTMLFormField {
abstract function getInputHTML( $value );
/**
+ * Same as getInputHTML, but returns an OOUI object.
+ * Defaults to false, which getOOUI will interpret as "use the HTML version"
+ *
+ * @param string $value
+ * @return OOUI\\Widget|false
+ */
+ function getInputOOUI( $value ) {
+ return false;
+ }
+
+ /**
* Get a translated interface message
*
* This is a wrapper around $this->mParent->msg() if $this->mParent is set
@@ -377,6 +389,10 @@ abstract class HTMLFormField {
$this->mName = $params['name'];
}
+ if ( isset( $params['dir'] ) ) {
+ $this->mDir = $params['dir'];
+ }
+
$validName = Sanitizer::escapeId( $this->mName );
$validName = str_replace( array( '.5B', '.5D' ), array( '[', ']' ), $validName );
if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
@@ -508,12 +524,20 @@ abstract class HTMLFormField {
'mw-htmlform-nolabel' => ( $label === '' )
);
- $field = Html::rawElement(
- 'div',
- array( 'class' => $outerDivClass ) + $cellAttributes,
- $inputHtml . "\n$errors"
- );
- $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $this->mVFormClass, $errorClass );
+ $horizontalLabel = isset( $this->mParams['horizontal-label'] )
+ ? $this->mParams['horizontal-label'] : false;
+
+ if ( $horizontalLabel ) {
+ $field = '&#160;' . $inputHtml . "\n$errors";
+ } else {
+ $field = Html::rawElement(
+ 'div',
+ array( 'class' => $outerDivClass ) + $cellAttributes,
+ $inputHtml . "\n$errors"
+ );
+ }
+ $divCssClasses = array( "mw-htmlform-field-$fieldType",
+ $this->mClass, $this->mVFormClass, $errorClass );
$wrapperAttributes = array(
'class' => $divCssClasses,
@@ -529,6 +553,75 @@ abstract class HTMLFormField {
}
/**
+ * Get the OOUI version of the div. Falls back to getDiv by default.
+ * @since 1.26
+ *
+ * @param string $value The value to set the input to.
+ *
+ * @return OOUI\\FieldLayout|OOUI\\ActionFieldLayout
+ */
+ public function getOOUI( $value ) {
+ $inputField = $this->getInputOOUI( $value );
+
+ if ( !$inputField ) {
+ // This field doesn't have an OOUI implementation yet at all. Fall back to getDiv() to
+ // generate the whole field, label and errors and all, then wrap it in a Widget.
+ // It might look weird, but it'll work OK.
+ return $this->getFieldLayoutOOUI(
+ new OOUI\Widget( array( 'content' => new OOUI\HtmlSnippet( $this->getDiv( $value ) ) ) ),
+ array( 'infusable' => false )
+ );
+ }
+
+ $infusable = true;
+ if ( is_string( $inputField ) ) {
+ // We have an OOUI implementation, but it's not proper, and we got a load of HTML.
+ // Cheat a little and wrap it in a widget. It won't be infusable, though, since client-side
+ // JavaScript doesn't know how to rebuilt the contents.
+ $inputField = new OOUI\Widget( array( 'content' => new OOUI\HtmlSnippet( $inputField ) ) );
+ $infusable = false;
+ }
+
+ $fieldType = get_class( $this );
+ $helpText = $this->getHelpText();
+ $errors = $this->getErrorsRaw( $value );
+ foreach ( $errors as &$error ) {
+ $error = new OOUI\HtmlSnippet( $error );
+ }
+
+ $config = array(
+ 'classes' => array( "mw-htmlform-field-$fieldType", $this->mClass ),
+ 'align' => $this->getLabelAlignOOUI(),
+ 'label' => $this->getLabel(),
+ 'help' => $helpText !== null ? new OOUI\HtmlSnippet( $helpText ) : null,
+ 'errors' => $errors,
+ 'infusable' => $infusable,
+ );
+
+ return $this->getFieldLayoutOOUI( $inputField, $config );
+ }
+
+ /**
+ * Get label alignment when generating field for OOUI.
+ * @return string 'left', 'right', 'top' or 'inline'
+ */
+ protected function getLabelAlignOOUI() {
+ return 'top';
+ }
+
+ /**
+ * Get a FieldLayout (or subclass thereof) to wrap this field in when using OOUI output.
+ * @return OOUI\\FieldLayout|OOUI\\ActionFieldLayout
+ */
+ protected function getFieldLayoutOOUI( $inputField, $config ) {
+ if ( isset( $this->mClassWithButton ) ) {
+ $buttonWidget = $this->mClassWithButton->getInputOOUI( '' );
+ return new OOUI\ActionFieldLayout( $inputField, $buttonWidget, $config );
+ }
+ return new OOUI\FieldLayout( $inputField, $config );
+ }
+
+ /**
* Get the complete raw fields for the input, including help text,
* labels, and whatever.
* @since 1.20
@@ -657,7 +750,7 @@ abstract class HTMLFormField {
/**
* Determine the help text to display
* @since 1.20
- * @return string
+ * @return string HTML
*/
public function getHelpText() {
$helptext = null;
@@ -692,7 +785,7 @@ abstract class HTMLFormField {
* @since 1.20
*
* @param string $value The value of the input
- * @return array
+ * @return array array( $errors, $errorClass )
*/
public function getErrorsAndErrorClass( $value ) {
$errors = $this->validate( $value, $this->mParent->mFieldData );
@@ -708,6 +801,35 @@ abstract class HTMLFormField {
return array( $errors, $errorClass );
}
+ /**
+ * Determine form errors to display, returning them in an array.
+ *
+ * @since 1.26
+ * @param string $value The value of the input
+ * @return string[] Array of error HTML strings
+ */
+ public function getErrorsRaw( $value ) {
+ $errors = $this->validate( $value, $this->mParent->mFieldData );
+
+ if ( is_bool( $errors ) || !$this->mParent->wasSubmitted() ) {
+ $errors = array();
+ }
+
+ if ( !is_array( $errors ) ) {
+ $errors = array( $errors );
+ }
+ foreach ( $errors as &$error ) {
+ if ( $error instanceof Message ) {
+ $error = $error->parse();
+ }
+ }
+
+ return $errors;
+ }
+
+ /**
+ * @return string
+ */
function getLabel() {
return is_null( $this->mLabel ) ? '' : $this->mLabel;
}
@@ -729,6 +851,8 @@ abstract class HTMLFormField {
$displayFormat = $this->mParent->getDisplayFormat();
$html = '';
+ $horizontalLabel = isset( $this->mParams['horizontal-label'] )
+ ? $this->mParams['horizontal-label'] : false;
if ( $displayFormat === 'table' ) {
$html =
@@ -736,7 +860,7 @@ abstract class HTMLFormField {
array( 'class' => 'mw-label' ) + $cellAttributes,
Html::rawElement( 'label', $for, $labelValue ) );
} elseif ( $hasLabel || $this->mShowEmptyLabels ) {
- if ( $displayFormat === 'div' ) {
+ if ( $displayFormat === 'div' && !$horizontalLabel ) {
$html =
Html::rawElement( 'div',
array( 'class' => 'mw-label' ) + $cellAttributes,
@@ -771,23 +895,43 @@ abstract class HTMLFormField {
}
/**
+ * Get a translated key if necessary.
+ * @param array|null $mappings Array of mappings, 'original' => 'translated'
+ * @param string $key
+ * @return string
+ */
+ protected function getMappedKey( $mappings, $key ) {
+ if ( !is_array( $mappings ) ) {
+ return $key;
+ }
+
+ if ( !empty( $mappings[$key] ) ) {
+ return $mappings[$key];
+ }
+
+ return $key;
+ }
+
+ /**
* Returns the given attributes from the parameters
*
* @param array $list List of attributes to get
+ * @param array $mappings Optional - Key/value map of attribute names to use instead of the ones passed in
* @return array Attributes
*/
- public function getAttributes( array $list ) {
+ public function getAttributes( array $list, array $mappings = null ) {
static $boolAttribs = array( 'disabled', 'required', 'autofocus', 'multiple', 'readonly' );
$ret = array();
-
foreach ( $list as $key ) {
+ $mappedKey = $this->getMappedKey( $mappings, $key );
+
if ( in_array( $key, $boolAttribs ) ) {
if ( !empty( $this->mParams[$key] ) ) {
- $ret[$key] = '';
+ $ret[$mappedKey] = $mappedKey;
}
} elseif ( isset( $this->mParams[$key] ) ) {
- $ret[$key] = $this->mParams[$key];
+ $ret[$mappedKey] = $this->mParams[$key];
}
}
@@ -877,6 +1021,29 @@ abstract class HTMLFormField {
}
/**
+ * Get options and make them into arrays suitable for OOUI.
+ * @return array Options for inclusion in a select or whatever.
+ */
+ public function getOptionsOOUI() {
+ $oldoptions = $this->getOptions();
+
+ if ( $oldoptions === null ) {
+ return null;
+ }
+
+ $options = array();
+
+ foreach ( $oldoptions as $text => $data ) {
+ $options[] = array(
+ 'data' => $data,
+ 'label' => $text,
+ );
+ }
+
+ return $options;
+ }
+
+ /**
* flatten an array of options to a single array, for instance,
* a set of "<options>" inside "<optgroups>".
*
diff --git a/includes/htmlform/HTMLFormFieldWithButton.php b/includes/htmlform/HTMLFormFieldWithButton.php
new file mode 100644
index 00000000..6b02c49d
--- /dev/null
+++ b/includes/htmlform/HTMLFormFieldWithButton.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Enables HTMLFormField elements to be build with a button.
+ */
+class HTMLFormFieldWithButton extends HTMLFormField {
+ /** @var string $mButtonClass CSS class for the button in this field */
+ protected $mButtonClass = '';
+
+ /** @var string|integer $mButtonId Element ID for the button in this field */
+ protected $mButtonId = '';
+
+ /** @var string $mButtonName Name the button in this field */
+ protected $mButtonName = '';
+
+ /** @var string $mButtonType Type of the button in this field (e.g. button or submit) */
+ protected $mButtonType = 'submit';
+
+ /** @var string $mButtonType Value for the button in this field */
+ protected $mButtonValue;
+
+ /** @var string $mButtonType Value for the button in this field */
+ protected $mButtonFlags = array( 'primary', 'progressive' );
+
+ public function __construct( $info ) {
+ if ( isset( $info['buttonclass'] ) ) {
+ $this->mButtonClass = $info['buttonclass'];
+ }
+ if ( isset( $info['buttonid'] ) ) {
+ $this->mButtonId = $info['buttonid'];
+ }
+ if ( isset( $info['buttonname'] ) ) {
+ $this->mButtonName = $info['buttonname'];
+ }
+ if ( isset( $info['buttondefault'] ) ) {
+ $this->mButtonValue = $info['buttondefault'];
+ }
+ if ( isset( $info['buttontype'] ) ) {
+ $this->mButtonType = $info['buttontype'];
+ }
+ if ( isset( $info['buttonflags'] ) ) {
+ $this->mButtonFlags = $info['buttonflags'];
+ }
+ parent::__construct( $info );
+ }
+
+ public function getInputHTML( $value ) {
+ $attr = array(
+ 'class' => 'mw-htmlform-submit ' . $this->mButtonClass,
+ 'id' => $this->mButtonId,
+ ) + $this->getAttributes( array( 'disabled', 'tabindex' ) );
+
+ return Html::input( $this->mButtonName, $this->mButtonValue, $this->mButtonType, $attr );
+ }
+
+ public function getInputOOUI( $value ) {
+ return new OOUI\ButtonInputWidget( array(
+ 'name' => $this->mButtonName,
+ 'value' => $this->mButtonValue,
+ 'type' => $this->mButtonType,
+ 'label' => $this->mButtonValue,
+ 'flags' => $this->mButtonFlags,
+ ) );
+ }
+
+ /**
+ * Combines the passed element with a button.
+ * @param String $element Element to combine the button with.
+ * @return String
+ */
+ public function getElement( $element ) {
+ return $element . '&#160;' . $this->getInputHTML( '' );
+ }
+}
diff --git a/includes/htmlform/HTMLInfoField.php b/includes/htmlform/HTMLInfoField.php
index a422047a..a667653a 100644
--- a/includes/htmlform/HTMLInfoField.php
+++ b/includes/htmlform/HTMLInfoField.php
@@ -14,6 +14,16 @@ class HTMLInfoField extends HTMLFormField {
return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
}
+ public function getInputOOUI( $value ) {
+ if ( !empty( $this->mParams['raw'] ) ) {
+ $value = new OOUI\HtmlSnippet( $value );
+ }
+
+ return new OOUI\LabelWidget( array(
+ 'label' => $value,
+ ) );
+ }
+
public function getTableRow( $value ) {
if ( !empty( $this->mParams['rawrow'] ) ) {
return $value;
diff --git a/includes/htmlform/HTMLMultiSelectField.php b/includes/htmlform/HTMLMultiSelectField.php
index 8d28b59e..523f0455 100644
--- a/includes/htmlform/HTMLMultiSelectField.php
+++ b/includes/htmlform/HTMLMultiSelectField.php
@@ -38,34 +38,19 @@ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable
$html = '';
$attribs = $this->getAttributes( array( 'disabled', 'tabindex' ) );
- $elementFunc = array( 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' );
foreach ( $options as $label => $info ) {
if ( is_array( $info ) ) {
$html .= Html::rawElement( 'h1', array(), $label ) . "\n";
$html .= $this->formatOptions( $info, $value );
} else {
- $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
-
- // @todo: Make this use checkLabel for consistency purposes
- $checkbox = Xml::check(
- $this->mName . '[]',
- in_array( $info, $value, true ),
- $attribs + $thisAttribs
- );
- $checkbox .= '&#160;' . call_user_func( $elementFunc,
- 'label',
- array( 'for' => "{$this->mID}-$info" ),
- $label
+ $thisAttribs = array(
+ 'id' => "{$this->mID}-$info",
+ 'value' => $info,
);
+ $checked = in_array( $info, $value, true );
- if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
- $checkbox = Html::rawElement(
- 'div',
- array( 'class' => 'mw-ui-checkbox' ),
- $checkbox
- );
- }
+ $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs, $label );
$html .= ' ' . Html::rawElement(
'div',
@@ -78,6 +63,41 @@ class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable
return $html;
}
+ protected function getOneCheckbox( $checked, $attribs, $label ) {
+ if ( $this->mParent instanceof OOUIHTMLForm ) {
+ if ( $this->mOptionsLabelsNotFromMessage ) {
+ $label = new OOUI\HtmlSnippet( $label );
+ }
+ return new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => "{$this->mName}[]",
+ 'selected' => $checked,
+ 'value' => $attribs['value'],
+ ) + $attribs ),
+ array(
+ 'label' => $label,
+ 'align' => 'inline',
+ )
+ );
+ } else {
+ $elementFunc = array( 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' );
+ $checkbox =
+ Xml::check( "{$this->mName}[]", $checked, $attribs ) .
+ '&#160;' .
+ call_user_func( $elementFunc,
+ 'label',
+ array( 'for' => $attribs['id'] ),
+ $label
+ );
+ if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+ $checkbox = Html::openElement( 'div', array( 'class' => 'mw-ui-checkbox' ) ) .
+ $checkbox .
+ Html::closeElement( 'div' );
+ }
+ return $checkbox;
+ }
+ }
+
/**
* @param WebRequest $request
*
diff --git a/includes/htmlform/HTMLRadioField.php b/includes/htmlform/HTMLRadioField.php
index 0f005408..2d057042 100644
--- a/includes/htmlform/HTMLRadioField.php
+++ b/includes/htmlform/HTMLRadioField.php
@@ -38,6 +38,23 @@ class HTMLRadioField extends HTMLFormField {
return $html;
}
+ function getInputOOUI( $value ) {
+ $options = array();
+ foreach ( $this->getOptions() as $label => $data ) {
+ $options[] = array(
+ 'data' => $data,
+ 'label' => $this->mOptionsLabelsNotFromMessage ? new OOUI\HtmlSnippet( $label ) : $label,
+ );
+ }
+
+ return new OOUI\RadioSelectInputWidget( array(
+ 'name' => $this->mName,
+ 'value' => $value,
+ 'options' => $options,
+ 'classes' => 'mw-htmlform-flatlist-item',
+ ) + $this->getAttributes( array( 'disabled', 'tabindex' ), array( 'tabindex' => 'tabIndex' ) ) );
+ }
+
function formatOptions( $options, $value ) {
$html = '';
diff --git a/includes/htmlform/HTMLSelectAndOtherField.php b/includes/htmlform/HTMLSelectAndOtherField.php
index a1c0c957..0e4f4f32 100644
--- a/includes/htmlform/HTMLSelectAndOtherField.php
+++ b/includes/htmlform/HTMLSelectAndOtherField.php
@@ -64,6 +64,10 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
return "$select<br />\n$textbox";
}
+ function getInputOOUI( $value ) {
+ return false;
+ }
+
/**
* @param WebRequest $request
*
@@ -71,7 +75,6 @@ class HTMLSelectAndOtherField extends HTMLSelectField {
*/
function loadDataFromRequest( $request ) {
if ( $request->getCheck( $this->mName ) ) {
-
$list = $request->getText( $this->mName );
$text = $request->getText( $this->mName . '-other' );
diff --git a/includes/htmlform/HTMLSelectField.php b/includes/htmlform/HTMLSelectField.php
index a198037a..6ba69666 100644
--- a/includes/htmlform/HTMLSelectField.php
+++ b/includes/htmlform/HTMLSelectField.php
@@ -41,4 +41,26 @@ class HTMLSelectField extends HTMLFormField {
return $select->getHTML();
}
+
+ function getInputOOUI( $value ) {
+ $disabled = false;
+ $allowedParams = array( 'tabindex' );
+ $attribs = $this->getAttributes( $allowedParams, array( 'tabindex' => 'tabIndex' ) );
+
+ if ( $this->mClass !== '' ) {
+ $attribs['classes'] = array( $this->mClass );
+ }
+
+ if ( !empty( $this->mParams['disabled'] ) ) {
+ $disabled = true;
+ }
+
+ return new OOUI\DropdownInputWidget( array(
+ 'name' => $this->mName,
+ 'id' => $this->mID,
+ 'options' => $this->getOptionsOOUI(),
+ 'value' => strval( $value ),
+ 'disabled' => $disabled,
+ ) + $attribs );
+ }
}
diff --git a/includes/htmlform/HTMLSelectNamespace.php b/includes/htmlform/HTMLSelectNamespace.php
index 96381062..4efdfbf3 100644
--- a/includes/htmlform/HTMLSelectNamespace.php
+++ b/includes/htmlform/HTMLSelectNamespace.php
@@ -3,11 +3,16 @@
* Wrapper for Html::namespaceSelector to use in HTMLForm
*/
class HTMLSelectNamespace extends HTMLFormField {
+ public function __construct( $params ) {
+ parent::__construct( $params );
+ $this->mAllValue = isset( $this->mParams['all'] ) ? $this->mParams['all'] : 'all';
+ }
+
function getInputHTML( $value ) {
return Html::namespaceSelector(
array(
'selected' => $value,
- 'all' => 'all'
+ 'all' => $this->mAllValue
), array(
'name' => $this->mName,
'id' => $this->mID,
@@ -15,4 +20,13 @@ class HTMLSelectNamespace extends HTMLFormField {
)
);
}
+
+ public function getInputOOUI( $value ) {
+ return new MediaWiki\Widget\NamespaceInputWidget( array(
+ 'value' => $value,
+ 'name' => $this->mName,
+ 'id' => $this->mID,
+ 'includeAllValue' => $this->mAllValue,
+ ) );
+ }
}
diff --git a/includes/htmlform/HTMLSelectNamespaceWithButton.php b/includes/htmlform/HTMLSelectNamespaceWithButton.php
new file mode 100644
index 00000000..24b15bd7
--- /dev/null
+++ b/includes/htmlform/HTMLSelectNamespaceWithButton.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Creates a Html::namespaceSelector input field with a button assigned to the input field.
+ */
+class HTMLSelectNamespaceWithButton extends HTMLSelectNamespace {
+ /** @var HTMLFormClassWithButton $mClassWithButton */
+ protected $mClassWithButton = null;
+
+ public function __construct( $info ) {
+ $this->mClassWithButton = new HTMLFormFieldWithButton( $info );
+ parent::__construct( $info );
+ }
+
+ public function getInputHTML( $value ) {
+ return $this->mClassWithButton->getElement( parent::getInputHTML( $value ) );
+ }
+}
diff --git a/includes/htmlform/HTMLSelectOrOtherField.php b/includes/htmlform/HTMLSelectOrOtherField.php
index cbf7d122..3e7acdf8 100644
--- a/includes/htmlform/HTMLSelectOrOtherField.php
+++ b/includes/htmlform/HTMLSelectOrOtherField.php
@@ -62,6 +62,10 @@ class HTMLSelectOrOtherField extends HTMLTextField {
return "$select<br />\n$textbox";
}
+ function getInputOOUI( $value ) {
+ return false;
+ }
+
/**
* @param WebRequest $request
*
diff --git a/includes/htmlform/HTMLSubmitField.php b/includes/htmlform/HTMLSubmitField.php
index 653c08c0..938e428a 100644
--- a/includes/htmlform/HTMLSubmitField.php
+++ b/includes/htmlform/HTMLSubmitField.php
@@ -6,4 +6,6 @@
*/
class HTMLSubmitField extends HTMLButtonField {
protected $buttonType = 'submit';
+
+ protected $mFlags = array( 'primary', 'constructive' );
}
diff --git a/includes/htmlform/HTMLTextAreaField.php b/includes/htmlform/HTMLTextAreaField.php
index 21173d2a..aeb4b7c2 100644
--- a/includes/htmlform/HTMLTextAreaField.php
+++ b/includes/htmlform/HTMLTextAreaField.php
@@ -12,11 +12,21 @@ class HTMLTextAreaField extends HTMLFormField {
return isset( $this->mParams['rows'] ) ? $this->mParams['rows'] : static::DEFAULT_ROWS;
}
+ function getSpellCheck() {
+ $val = isset( $this->mParams['spellcheck'] ) ? $this->mParams['spellcheck'] : null;
+ if ( is_bool( $val ) ) {
+ // "spellcheck" attribute literally requires "true" or "false" to work.
+ return $val === true ? 'true' : 'false';
+ }
+ return null;
+ }
+
function getInputHTML( $value ) {
$attribs = array(
'id' => $this->mID,
'cols' => $this->getCols(),
'rows' => $this->getRows(),
+ 'spellcheck' => $this->getSpellCheck(),
) + $this->getTooltipAndAccessKey();
if ( $this->mClass !== '' ) {
@@ -35,4 +45,38 @@ class HTMLTextAreaField extends HTMLFormField {
$attribs += $this->getAttributes( $allowedParams );
return Html::textarea( $this->mName, $value, $attribs );
}
+
+ function getInputOOUI( $value ) {
+ if ( isset( $this->mParams['cols'] ) ) {
+ throw new Exception( "OOUIHTMLForm does not support the 'cols' parameter for textareas" );
+ }
+
+ $attribs = $this->getTooltipAndAccessKey();
+
+ if ( $this->mClass !== '' ) {
+ $attribs['classes'] = array( $this->mClass );
+ }
+
+ $allowedParams = array(
+ 'placeholder',
+ 'tabindex',
+ 'disabled',
+ 'readonly',
+ 'required',
+ 'autofocus',
+ );
+
+ $attribs += $this->getAttributes( $allowedParams, array(
+ 'tabindex' => 'tabIndex',
+ 'readonly' => 'readOnly',
+ ) );
+
+ return new OOUI\TextInputWidget( array(
+ 'id' => $this->mID,
+ 'name' => $this->mName,
+ 'multiline' => true,
+ 'value' => $value,
+ 'rows' => $this->getRows(),
+ ) + $attribs );
+ }
}
diff --git a/includes/htmlform/HTMLTextField.php b/includes/htmlform/HTMLTextField.php
index 88df49db..157116d8 100644
--- a/includes/htmlform/HTMLTextField.php
+++ b/includes/htmlform/HTMLTextField.php
@@ -5,12 +5,23 @@ class HTMLTextField extends HTMLFormField {
return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
}
+ function getSpellCheck() {
+ $val = isset( $this->mParams['spellcheck'] ) ? $this->mParams['spellcheck'] : null;
+ if ( is_bool( $val ) ) {
+ // "spellcheck" attribute literally requires "true" or "false" to work.
+ return $val === true ? 'true' : 'false';
+ }
+ return null;
+ }
+
function getInputHTML( $value ) {
$attribs = array(
'id' => $this->mID,
'name' => $this->mName,
'size' => $this->getSize(),
'value' => $value,
+ 'dir' => $this->mDir,
+ 'spellcheck' => $this->getSpellCheck(),
) + $this->getTooltipAndAccessKey();
if ( $this->mClass !== '' ) {
@@ -40,6 +51,11 @@ class HTMLTextField extends HTMLFormField {
$attribs += $this->getAttributes( $allowedParams );
# Extract 'type'
+ $type = $this->getType( $attribs );
+ return Html::input( $this->mName, $value, $type, $attribs );
+ }
+
+ protected function getType( &$attribs ) {
$type = isset( $attribs['type'] ) ? $attribs['type'] : 'text';
unset( $attribs['type'] );
@@ -65,6 +81,49 @@ class HTMLTextField extends HTMLFormField {
}
}
- return Html::input( $this->mName, $value, $type, $attribs );
+ return $type;
+ }
+
+ function getInputOOUI( $value ) {
+ $attribs = $this->getTooltipAndAccessKey();
+
+ if ( $this->mClass !== '' ) {
+ $attribs['classes'] = array( $this->mClass );
+ }
+
+ # @todo Enforce pattern, step, required, readonly on the server side as
+ # well
+ $allowedParams = array(
+ 'autofocus',
+ 'autosize',
+ 'disabled',
+ 'flags',
+ 'indicator',
+ 'maxlength',
+ 'placeholder',
+ 'readonly',
+ 'required',
+ 'tabindex',
+ 'type',
+ );
+
+ $attribs += $this->getAttributes( $allowedParams, array(
+ 'maxlength' => 'maxLength',
+ 'readonly' => 'readOnly',
+ 'tabindex' => 'tabIndex',
+ ) );
+
+ $type = $this->getType( $attribs );
+
+ return $this->getInputWidget( array(
+ 'id' => $this->mID,
+ 'name' => $this->mName,
+ 'value' => $value,
+ 'type' => $type,
+ ) + $attribs );
+ }
+
+ protected function getInputWidget( $params ) {
+ return new OOUI\TextInputWidget( $params );
}
}
diff --git a/includes/htmlform/HTMLTextFieldWithButton.php b/includes/htmlform/HTMLTextFieldWithButton.php
new file mode 100644
index 00000000..c6dac322
--- /dev/null
+++ b/includes/htmlform/HTMLTextFieldWithButton.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Creates a text input field with a button assigned to the input field.
+ */
+class HTMLTextFieldWithButton extends HTMLTextField {
+ /** @var HTMLFormClassWithButton $mClassWithButton */
+ protected $mClassWithButton = null;
+
+ public function __construct( $info ) {
+ $this->mClassWithButton = new HTMLFormFieldWithButton( $info );
+ parent::__construct( $info );
+ }
+
+ public function getInputHTML( $value ) {
+ return $this->mClassWithButton->getElement( parent::getInputHTML( $value ) );
+ }
+}
diff --git a/includes/htmlform/HTMLTitleTextField.php b/includes/htmlform/HTMLTitleTextField.php
new file mode 100644
index 00000000..a225c67c
--- /dev/null
+++ b/includes/htmlform/HTMLTitleTextField.php
@@ -0,0 +1,81 @@
+<?php
+
+use MediaWiki\Widget\TitleInputWidget;
+
+/**
+ * Implements a text input field for page titles.
+ * Automatically does validation that the title is valid,
+ * as well as autocompletion if using the OOUI display format.
+ *
+ * Note: Forms using GET requests will need to make sure the title value is not
+ * an empty string.
+ *
+ * Optional parameters:
+ * 'namespace' - Namespace the page must be in
+ * 'relative' - If true and 'namespace' given, strip/add the namespace from/to the title as needed
+ * 'creatable' - Whether to validate the title is creatable (not a special page)
+ * 'exists' - Whether to validate that the title already exists
+ *
+ * @since 1.26
+ */
+class HTMLTitleTextField extends HTMLTextField {
+ public function __construct( $params ) {
+ $params += array(
+ 'namespace' => false,
+ 'relative' => false,
+ 'creatable' => false,
+ 'exists' => false,
+ );
+
+ parent::__construct( $params );
+ }
+
+ public function validate( $value, $alldata ) {
+ if ( $this->mParent->getMethod() === 'get' && $value === '' ) {
+ // If the form is a GET form and has no value, assume it hasn't been
+ // submitted yet, and skip validation
+ return parent::validate( $value, $alldata );
+ }
+ try {
+ if ( !$this->mParams['relative'] ) {
+ $title = Title::newFromTextThrow( $value );
+ } else {
+ // Can't use Title::makeTitleSafe(), because it doesn't throw useful exceptions
+ global $wgContLang;
+ $namespaceName = $wgContLang->getNsText( $this->mParams['namespace'] );
+ $title = Title::newFromTextThrow( $namespaceName . ':' . $value );
+ }
+ } catch ( MalformedTitleException $e ) {
+ $msg = $this->msg( $e->getErrorMessage() );
+ $params = $e->getErrorMessageParameters();
+ if ( $params ) {
+ $msg->params( $params );
+ }
+ return $msg->parse();
+ }
+
+ $text = $title->getPrefixedText();
+ if ( $this->mParams['namespace'] !== false && !$title->inNamespace( $this->mParams['namespace'] ) ) {
+ return $this->msg( 'htmlform-title-badnamespace', $this->mParams['namespace'], $text )->parse();
+ }
+
+ if ( $this->mParams['creatable'] && !$title->canExist() ) {
+ return $this->msg( 'htmlform-title-not-creatable', $text )->escaped();
+ }
+
+ if ( $this->mParams['exists'] && !$title->exists() ) {
+ return $this->msg( 'htmlform-title-not-exists', $text )->parse();
+ }
+
+ return parent::validate( $value, $alldata );
+ }
+
+ protected function getInputWidget( $params ) {
+ $this->mParent->getOutput()->addModules( 'mediawiki.widgets' );
+ if ( $this->mParams['namespace'] !== false ) {
+ $params['namespace'] = $this->mParams['namespace'];
+ }
+ $params['relative'] = $this->mParams['relative'];
+ return new TitleInputWidget( $params );
+ }
+}
diff --git a/includes/htmlform/HTMLUserTextField.php b/includes/htmlform/HTMLUserTextField.php
new file mode 100644
index 00000000..9617c0a3
--- /dev/null
+++ b/includes/htmlform/HTMLUserTextField.php
@@ -0,0 +1,47 @@
+<?php
+
+use MediaWiki\Widget\UserInputWidget;
+
+/**
+ * Implements a text input field for user names.
+ * Automatically auto-completes if using the OOUI display format.
+ *
+ * FIXME: Does not work for forms that support GET requests.
+ *
+ * Optional parameters:
+ * 'exists' - Whether to validate that the user already exists
+ *
+ * @since 1.26
+ */
+class HTMLUserTextField extends HTMLTextField {
+ public function __construct( $params ) {
+ $params += array(
+ 'exists' => false,
+ 'ipallowed' => false,
+ );
+
+ parent::__construct( $params );
+ }
+
+ public function validate( $value, $alldata ) {
+ // check, if a user exists with the given username
+ $user = User::newFromName( $value, false );
+
+ if ( !$user ) {
+ return $this->msg( 'htmlform-user-not-valid', $value )->parse();
+ } elseif (
+ ( $this->mParams['exists'] && $user->getId() === 0 ) &&
+ !( $this->mParams['ipallowed'] && User::isIP( $value ) )
+ ) {
+ return $this->msg( 'htmlform-user-not-exists', $user->getName() )->parse();
+ }
+
+ return parent::validate( $value, $alldata );
+ }
+
+ protected function getInputWidget( $params ) {
+ $this->mParent->getOutput()->addModules( 'mediawiki.widgets.UserInputWidget' );
+
+ return new UserInputWidget( $params );
+ }
+}
diff --git a/includes/htmlform/OOUIHTMLForm.php b/includes/htmlform/OOUIHTMLForm.php
new file mode 100644
index 00000000..84d40a14
--- /dev/null
+++ b/includes/htmlform/OOUIHTMLForm.php
@@ -0,0 +1,221 @@
+<?php
+
+/**
+ * HTML form generation and submission handling, OOUI style.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Compact stacked vertical format for forms, implemented using OOUI widgets.
+ */
+class OOUIHTMLForm extends HTMLForm {
+ private $oouiErrors;
+
+ public function __construct( $descriptor, $context = null, $messagePrefix = '' ) {
+ parent::__construct( $descriptor, $context, $messagePrefix );
+ $this->getOutput()->enableOOUI();
+ $this->getOutput()->addModuleStyles( 'mediawiki.htmlform.ooui.styles' );
+ }
+
+ /**
+ * Symbolic display format name.
+ * @var string
+ */
+ protected $displayFormat = 'ooui';
+
+ public static function loadInputFromParameters( $fieldname, $descriptor, HTMLForm $parent = null ) {
+ $field = parent::loadInputFromParameters( $fieldname, $descriptor, $parent );
+ $field->setShowEmptyLabel( false );
+ return $field;
+ }
+
+ function getButtons() {
+ $buttons = '';
+
+ if ( $this->mShowSubmit ) {
+ $attribs = array( 'infusable' => true );
+
+ if ( isset( $this->mSubmitID ) ) {
+ $attribs['id'] = $this->mSubmitID;
+ }
+
+ if ( isset( $this->mSubmitName ) ) {
+ $attribs['name'] = $this->mSubmitName;
+ }
+
+ if ( isset( $this->mSubmitTooltip ) ) {
+ $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
+ }
+
+ $attribs['classes'] = array( 'mw-htmlform-submit' );
+ $attribs['type'] = 'submit';
+ $attribs['label'] = $this->getSubmitText();
+ $attribs['value'] = $this->getSubmitText();
+ $attribs['flags'] = $this->mSubmitFlags;
+
+ $buttons .= new OOUI\ButtonInputWidget( $attribs );
+ }
+
+ if ( $this->mShowReset ) {
+ $buttons .= new OOUI\ButtonInputWidget( array(
+ 'type' => 'reset',
+ 'label' => $this->msg( 'htmlform-reset' )->text(),
+ ) );
+ }
+
+ foreach ( $this->mButtons as $button ) {
+ $attrs = array();
+
+ if ( $button['attribs'] ) {
+ $attrs += $button['attribs'];
+ }
+
+ if ( isset( $button['id'] ) ) {
+ $attrs['id'] = $button['id'];
+ }
+
+ $attrs['classes'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : array();
+
+ $buttons .= new OOUI\ButtonInputWidget( array(
+ 'type' => 'submit',
+ 'name' => $button['name'],
+ 'value' => $button['value'],
+ 'label' => $button['value'],
+ ) + $attrs );
+ }
+
+ $html = Html::rawElement( 'div',
+ array( 'class' => 'mw-htmlform-submit-buttons' ), "\n$buttons" ) . "\n";
+
+ return $html;
+ }
+
+ /**
+ * Put a form section together from the individual fields' HTML, merging it and wrapping.
+ * @param OOUI\\FieldLayout[] $fieldsHtml
+ * @param string $sectionName
+ * @param bool $anyFieldHasLabel Unused
+ * @return string HTML
+ */
+ protected function formatSection( array $fieldsHtml, $sectionName, $anyFieldHasLabel ) {
+ $config = array(
+ 'items' => $fieldsHtml,
+ );
+ if ( $sectionName ) {
+ $config['id'] = Sanitizer::escapeId( $sectionName );
+ }
+ if ( is_string( $this->mWrapperLegend ) ) {
+ $config['label'] = $this->mWrapperLegend;
+ }
+ return new OOUI\FieldsetLayout( $config );
+ }
+
+ /**
+ * @param string|array|Status $err
+ * @return string
+ */
+ function getErrors( $err ) {
+ if ( !$err ) {
+ $errors = array();
+ } else if ( $err instanceof Status ) {
+ if ( $err->isOK() ) {
+ $errors = array();
+ } else {
+ $errors = $err->getErrorsByType( 'error' );
+ foreach ( $errors as &$error ) {
+ // Input: array( 'message' => 'foo', 'errors' => array( 'a', 'b', 'c' ) )
+ // Output: array( 'foo', 'a', 'b', 'c' )
+ $error = array_merge( array( $error['message'] ), $error['params'] );
+ }
+ }
+ } else {
+ $errors = $err;
+ if ( !is_array( $errors ) ) {
+ $errors = array( $errors );
+ }
+ }
+
+ foreach ( $errors as &$error ) {
+ if ( is_array( $error ) ) {
+ $msg = array_shift( $error );
+ } else {
+ $msg = $error;
+ $error = array();
+ }
+ $error = $this->msg( $msg, $error )->parse();
+ $error = new OOUI\HtmlSnippet( $error );
+ }
+
+ // Used in getBody()
+ $this->oouiErrors = $errors;
+ return '';
+ }
+
+ function getHeaderText( $section = null ) {
+ if ( is_null( $section ) ) {
+ // We handle $this->mHeader elsewhere, in getBody()
+ return '';
+ } else {
+ return parent::getHeaderText( $section );
+ }
+ }
+
+ function getBody() {
+ $fieldset = parent::getBody();
+ // FIXME This only works for forms with no subsections
+ if ( $fieldset instanceof OOUI\FieldsetLayout ) {
+ $classes = array( 'mw-htmlform-ooui-header' );
+ if ( !$this->mHeader ) {
+ $classes[] = 'mw-htmlform-ooui-header-empty';
+ }
+ if ( $this->oouiErrors ) {
+ $classes[] = 'mw-htmlform-ooui-header-errors';
+ }
+ $fieldset->addItems( array(
+ new OOUI\FieldLayout(
+ new OOUI\LabelWidget( array( 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ) ),
+ array(
+ 'align' => 'top',
+ 'errors' => $this->oouiErrors,
+ 'classes' => $classes,
+ )
+ )
+ ), 0 );
+ }
+ return $fieldset;
+ }
+
+ function wrapForm( $html ) {
+ $form = new OOUI\FormLayout( $this->getFormAttributes() + array(
+ 'classes' => array( 'mw-htmlform-ooui' ),
+ 'content' => new OOUI\HtmlSnippet( $html ),
+ ) );
+
+ // Include a wrapper for style, if requested.
+ $form = new OOUI\PanelLayout( array(
+ 'classes' => array( 'mw-htmlform-ooui-wrapper' ),
+ 'expanded' => false,
+ 'padded' => $this->mWrapperLegend !== false,
+ 'framed' => $this->mWrapperLegend !== false,
+ 'content' => $form,
+ ) );
+
+ return $form;
+ }
+}
diff --git a/includes/htmlform/VFormHTMLForm.php b/includes/htmlform/VFormHTMLForm.php
index 0c0e4252..3788379d 100644
--- a/includes/htmlform/VFormHTMLForm.php
+++ b/includes/htmlform/VFormHTMLForm.php
@@ -65,7 +65,7 @@ class VFormHTMLForm extends HTMLForm {
protected function getFormAttributes() {
$attribs = parent::getFormAttributes();
- array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
+ $attribs['class'] = array( 'mw-ui-vform', 'mw-ui-container', 'visualClear' );
return $attribs;
}
@@ -95,8 +95,10 @@ class VFormHTMLForm extends HTMLForm {
$attribs['class'] = array(
'mw-htmlform-submit',
'mw-ui-button mw-ui-big mw-ui-block',
- $this->mSubmitModifierClass,
);
+ foreach ( $this->mSubmitFlags as $flag ) {
+ $attribs['class'][] = 'mw-ui-' . $flag;
+ }
$buttons .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
}