diff options
Diffstat (limited to 'includes/htmlform')
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 != ' ' && $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 = ' ' . $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 . ' ' . $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 .= ' ' . 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 ) . + ' ' . + 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"; } |