When there's an error in one of the fields "below the fold" or in another tab, it's hard notice that it wasn't saved, especially since the success message is very subtle. In normal forms, there's a red error message at the top (in addition to messages next to invalid fields), which was missing from the preferences form. I ran into this while experimenting with signature field validation. Change-Id: If365e35d15a52939397f2093b1b8f5113a62e22b
320 lines
8.7 KiB
PHP
320 lines
8.7 KiB
PHP
<?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;
|
|
private $oouiWarnings;
|
|
|
|
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;
|
|
}
|
|
|
|
public function getButtons() {
|
|
$buttons = '';
|
|
|
|
// IE<8 has bugs with <button>, so we'll need to avoid them.
|
|
$isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) );
|
|
|
|
if ( $this->mShowSubmit ) {
|
|
$attribs = [ 'infusable' => true ];
|
|
|
|
if ( isset( $this->mSubmitID ) ) {
|
|
$attribs['id'] = $this->mSubmitID;
|
|
}
|
|
|
|
if ( isset( $this->mSubmitName ) ) {
|
|
$attribs['name'] = $this->mSubmitName;
|
|
}
|
|
|
|
if ( isset( $this->mSubmitTooltip ) ) {
|
|
$attribs += [
|
|
'title' => Linker::titleAttrib( $this->mSubmitTooltip ),
|
|
'accessKey' => Linker::accesskey( $this->mSubmitTooltip ),
|
|
];
|
|
}
|
|
|
|
$attribs['classes'] = [ 'mw-htmlform-submit' ];
|
|
$attribs['type'] = 'submit';
|
|
$attribs['label'] = $this->getSubmitText();
|
|
$attribs['value'] = $this->getSubmitText();
|
|
$attribs['flags'] = $this->mSubmitFlags;
|
|
$attribs['useInputTag'] = $isBadIE;
|
|
|
|
$buttons .= new OOUI\ButtonInputWidget( $attribs );
|
|
}
|
|
|
|
if ( $this->mShowReset ) {
|
|
$buttons .= new OOUI\ButtonInputWidget( [
|
|
'type' => 'reset',
|
|
'label' => $this->msg( 'htmlform-reset' )->text(),
|
|
'useInputTag' => $isBadIE,
|
|
] );
|
|
}
|
|
|
|
if ( $this->mShowCancel ) {
|
|
$target = $this->mCancelTarget ?: Title::newMainPage();
|
|
if ( $target instanceof Title ) {
|
|
$target = $target->getLocalURL();
|
|
}
|
|
$buttons .= new OOUI\ButtonWidget( [
|
|
'label' => $this->msg( 'cancel' )->text(),
|
|
'href' => $target,
|
|
] );
|
|
}
|
|
|
|
foreach ( $this->mButtons as $button ) {
|
|
$attrs = [];
|
|
|
|
if ( $button['attribs'] ) {
|
|
$attrs += $button['attribs'];
|
|
}
|
|
|
|
if ( isset( $button['id'] ) ) {
|
|
$attrs['id'] = $button['id'];
|
|
}
|
|
|
|
if ( $isBadIE ) {
|
|
$label = $button['value'];
|
|
} elseif ( isset( $button['label-message'] ) ) {
|
|
$label = new OOUI\HtmlSnippet( $this->getMessage( $button['label-message'] )->parse() );
|
|
} elseif ( isset( $button['label'] ) ) {
|
|
$label = $button['label'];
|
|
} elseif ( isset( $button['label-raw'] ) ) {
|
|
$label = new OOUI\HtmlSnippet( $button['label-raw'] );
|
|
} else {
|
|
$label = $button['value'];
|
|
}
|
|
|
|
$attrs['classes'] = isset( $attrs['class'] ) ? (array)$attrs['class'] : [];
|
|
|
|
$buttons .= new OOUI\ButtonInputWidget( [
|
|
'type' => 'submit',
|
|
'name' => $button['name'],
|
|
'value' => $button['value'],
|
|
'label' => $label,
|
|
'flags' => $button['flags'],
|
|
'framed' => $button['framed'],
|
|
'useInputTag' => $isBadIE,
|
|
] + $attrs );
|
|
}
|
|
|
|
if ( !$buttons ) {
|
|
return '';
|
|
}
|
|
|
|
return Html::rawElement( 'div',
|
|
[ 'class' => 'mw-htmlform-submit-buttons' ], "\n$buttons" ) . "\n";
|
|
}
|
|
|
|
/**
|
|
* @inheritDoc
|
|
* @return OOUI\PanelLayout
|
|
*/
|
|
protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) {
|
|
// to get a user visible effect, wrap the fieldset into a framed panel layout
|
|
$layout = new OOUI\PanelLayout( [
|
|
'expanded' => false,
|
|
'padded' => true,
|
|
'framed' => true,
|
|
] );
|
|
|
|
$layout->appendContent(
|
|
new OOUI\FieldsetLayout( [
|
|
'label' => $legend,
|
|
'items' => [
|
|
new OOUI\Widget( [
|
|
'content' => new OOUI\HtmlSnippet( $section )
|
|
] ),
|
|
],
|
|
] + $attributes )
|
|
);
|
|
return $layout;
|
|
}
|
|
|
|
/**
|
|
* 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 ) {
|
|
if ( !$fieldsHtml ) {
|
|
// Do not generate any wrappers for empty sections. Sections may be empty if they only have
|
|
// subsections, but no fields. A legend will still be added in wrapFieldSetSection().
|
|
return '';
|
|
}
|
|
|
|
$html = implode( '', $fieldsHtml );
|
|
|
|
if ( $sectionName ) {
|
|
$html = Html::rawElement(
|
|
'div',
|
|
[ 'id' => Sanitizer::escapeIdForAttribute( $sectionName ) ],
|
|
$html
|
|
);
|
|
}
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* @param string|array|Status $elements
|
|
* @param string $elementsType
|
|
* @return string
|
|
*/
|
|
public function getErrorsOrWarnings( $elements, $elementsType ) {
|
|
if ( $elements === '' ) {
|
|
return '';
|
|
}
|
|
|
|
if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
|
|
throw new DomainException( $elementsType . ' is not a valid type.' );
|
|
}
|
|
$errors = [];
|
|
if ( $elements instanceof Status ) {
|
|
if ( !$elements->isGood() ) {
|
|
$errors = $elements->getErrorsByType( $elementsType );
|
|
foreach ( $errors as &$error ) {
|
|
// Input: [ 'message' => 'foo', 'errors' => [ 'a', 'b', 'c' ] ]
|
|
// Output: [ 'foo', 'a', 'b', 'c' ]
|
|
$error = array_merge( [ $error['message'] ], $error['params'] );
|
|
}
|
|
}
|
|
} elseif ( $elementsType === 'error' ) {
|
|
if ( is_array( $elements ) ) {
|
|
$errors = $elements;
|
|
} elseif ( is_string( $elements ) ) {
|
|
$errors = [ $elements ];
|
|
}
|
|
}
|
|
|
|
foreach ( $errors as &$error ) {
|
|
$error = $this->getMessage( $error )->parse();
|
|
$error = new OOUI\HtmlSnippet( $error );
|
|
}
|
|
|
|
// Used in formatFormHeader()
|
|
if ( $elementsType === 'error' ) {
|
|
$this->oouiErrors = $errors;
|
|
} else {
|
|
$this->oouiWarnings = $errors;
|
|
}
|
|
return '';
|
|
}
|
|
|
|
public function getHeaderText( $section = null ) {
|
|
if ( is_null( $section ) ) {
|
|
// We handle $this->mHeader elsewhere, in getBody()
|
|
return '';
|
|
} else {
|
|
return parent::getHeaderText( $section );
|
|
}
|
|
}
|
|
|
|
protected function formatFormHeader() {
|
|
if ( !( $this->mHeader || $this->oouiErrors || $this->oouiWarnings ) ) {
|
|
return '';
|
|
}
|
|
$classes = [ 'mw-htmlform-ooui-header' ];
|
|
if ( $this->oouiErrors ) {
|
|
$classes[] = 'mw-htmlform-ooui-header-errors';
|
|
}
|
|
if ( $this->oouiWarnings ) {
|
|
$classes[] = 'mw-htmlform-ooui-header-warnings';
|
|
}
|
|
// if there's no header, don't create an (empty) LabelWidget, simply use a placeholder
|
|
if ( $this->mHeader ) {
|
|
$element = new OOUI\LabelWidget( [ 'label' => new OOUI\HtmlSnippet( $this->mHeader ) ] );
|
|
} else {
|
|
$element = new OOUI\Widget( [] );
|
|
}
|
|
return new OOUI\FieldLayout(
|
|
$element,
|
|
[
|
|
'align' => 'top',
|
|
'errors' => $this->oouiErrors,
|
|
'notices' => $this->oouiWarnings,
|
|
'classes' => $classes,
|
|
]
|
|
);
|
|
}
|
|
|
|
public function getBody() {
|
|
$html = parent::getBody();
|
|
$html = $this->formatFormHeader() . $html;
|
|
return $html;
|
|
}
|
|
|
|
public function wrapForm( $html ) {
|
|
if ( is_string( $this->mWrapperLegend ) ) {
|
|
$phpClass = $this->mCollapsible ? CollapsibleFieldsetLayout::class : OOUI\FieldsetLayout::class;
|
|
$content = new $phpClass( [
|
|
'label' => $this->mWrapperLegend,
|
|
'collapsed' => $this->mCollapsed,
|
|
'items' => [
|
|
new OOUI\Widget( [
|
|
'content' => new OOUI\HtmlSnippet( $html )
|
|
] ),
|
|
],
|
|
] + OOUI\Element::configFromHtmlAttributes( $this->mWrapperAttributes ) );
|
|
} else {
|
|
$content = new OOUI\HtmlSnippet( $html );
|
|
}
|
|
|
|
$classes = [ 'mw-htmlform', 'mw-htmlform-ooui' ];
|
|
$form = new OOUI\FormLayout( $this->getFormAttributes() + [
|
|
'classes' => $classes,
|
|
'content' => $content,
|
|
] );
|
|
|
|
// Include a wrapper for style, if requested.
|
|
$form = new OOUI\PanelLayout( [
|
|
'classes' => [ 'mw-htmlform-ooui-wrapper' ],
|
|
'expanded' => false,
|
|
'padded' => $this->mWrapperLegend !== false,
|
|
'framed' => $this->mWrapperLegend !== false,
|
|
'content' => $form,
|
|
] );
|
|
|
|
return $form;
|
|
}
|
|
}
|