Move HTMLFormField subclasses to a separate directory
authorBartosz Dziewoński <matma.rex@gmail.com>
Sat, 30 Jul 2016 21:59:24 +0000 (23:59 +0200)
committerBartosz Dziewoński <matma.rex@gmail.com>
Mon, 1 Aug 2016 07:58:56 +0000 (07:58 +0000)
It's getting more difficult to navigate the files in includes/htmlform/
with every new field and every new helper class that is being added.

Change-Id: I92ce2356baf6151f17b2440970d5abdf86503820

57 files changed:
autoload.php
includes/htmlform/HTMLApiField.php [deleted file]
includes/htmlform/HTMLAutoCompleteSelectField.php [deleted file]
includes/htmlform/HTMLButtonField.php [deleted file]
includes/htmlform/HTMLCheckField.php [deleted file]
includes/htmlform/HTMLCheckMatrix.php [deleted file]
includes/htmlform/HTMLComboboxField.php [deleted file]
includes/htmlform/HTMLEditTools.php [deleted file]
includes/htmlform/HTMLFloatField.php [deleted file]
includes/htmlform/HTMLFormFieldCloner.php [deleted file]
includes/htmlform/HTMLFormFieldWithButton.php [deleted file]
includes/htmlform/HTMLHiddenField.php [deleted file]
includes/htmlform/HTMLInfoField.php [deleted file]
includes/htmlform/HTMLIntField.php [deleted file]
includes/htmlform/HTMLMultiSelectField.php [deleted file]
includes/htmlform/HTMLRadioField.php [deleted file]
includes/htmlform/HTMLSelectAndOtherField.php [deleted file]
includes/htmlform/HTMLSelectField.php [deleted file]
includes/htmlform/HTMLSelectLimitField.php [deleted file]
includes/htmlform/HTMLSelectNamespace.php [deleted file]
includes/htmlform/HTMLSelectNamespaceWithButton.php [deleted file]
includes/htmlform/HTMLSelectOrOtherField.php [deleted file]
includes/htmlform/HTMLSubmitField.php [deleted file]
includes/htmlform/HTMLTagFilter.php [deleted file]
includes/htmlform/HTMLTextAreaField.php [deleted file]
includes/htmlform/HTMLTextField.php [deleted file]
includes/htmlform/HTMLTextFieldWithButton.php [deleted file]
includes/htmlform/HTMLTitleTextField.php [deleted file]
includes/htmlform/HTMLUserTextField.php [deleted file]
includes/htmlform/fields/HTMLApiField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLAutoCompleteSelectField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLButtonField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLCheckField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLCheckMatrix.php [new file with mode: 0644]
includes/htmlform/fields/HTMLComboboxField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLEditTools.php [new file with mode: 0644]
includes/htmlform/fields/HTMLFloatField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLFormFieldCloner.php [new file with mode: 0644]
includes/htmlform/fields/HTMLFormFieldWithButton.php [new file with mode: 0644]
includes/htmlform/fields/HTMLHiddenField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLInfoField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLIntField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLMultiSelectField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLRadioField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectAndOtherField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectLimitField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectNamespace.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectNamespaceWithButton.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSelectOrOtherField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLSubmitField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLTagFilter.php [new file with mode: 0644]
includes/htmlform/fields/HTMLTextAreaField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLTextField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLTextFieldWithButton.php [new file with mode: 0644]
includes/htmlform/fields/HTMLTitleTextField.php [new file with mode: 0644]
includes/htmlform/fields/HTMLUserTextField.php [new file with mode: 0644]

index 5808040..50d544c 100644 (file)
@@ -514,41 +514,41 @@ $wgAutoloadLocalClasses = [
        'GitInfo' => __DIR__ . '/includes/GitInfo.php',
        'GlobalDependency' => __DIR__ . '/includes/cache/CacheDependency.php',
        'GlobalVarConfig' => __DIR__ . '/includes/config/GlobalVarConfig.php',
-       'HTMLApiField' => __DIR__ . '/includes/htmlform/HTMLApiField.php',
-       'HTMLAutoCompleteSelectField' => __DIR__ . '/includes/htmlform/HTMLAutoCompleteSelectField.php',
-       'HTMLButtonField' => __DIR__ . '/includes/htmlform/HTMLButtonField.php',
+       'HTMLApiField' => __DIR__ . '/includes/htmlform/fields/HTMLApiField.php',
+       'HTMLAutoCompleteSelectField' => __DIR__ . '/includes/htmlform/fields/HTMLAutoCompleteSelectField.php',
+       'HTMLButtonField' => __DIR__ . '/includes/htmlform/fields/HTMLButtonField.php',
        'HTMLCacheUpdate' => __DIR__ . '/includes/deferred/HTMLCacheUpdate.php',
        'HTMLCacheUpdateJob' => __DIR__ . '/includes/jobqueue/jobs/HTMLCacheUpdateJob.php',
-       'HTMLCheckField' => __DIR__ . '/includes/htmlform/HTMLCheckField.php',
-       'HTMLCheckMatrix' => __DIR__ . '/includes/htmlform/HTMLCheckMatrix.php',
-       'HTMLComboboxField' => __DIR__ . '/includes/htmlform/HTMLComboboxField.php',
-       'HTMLEditTools' => __DIR__ . '/includes/htmlform/HTMLEditTools.php',
+       'HTMLCheckField' => __DIR__ . '/includes/htmlform/fields/HTMLCheckField.php',
+       'HTMLCheckMatrix' => __DIR__ . '/includes/htmlform/fields/HTMLCheckMatrix.php',
+       'HTMLComboboxField' => __DIR__ . '/includes/htmlform/fields/HTMLComboboxField.php',
+       'HTMLEditTools' => __DIR__ . '/includes/htmlform/fields/HTMLEditTools.php',
        'HTMLFileCache' => __DIR__ . '/includes/cache/HTMLFileCache.php',
-       'HTMLFloatField' => __DIR__ . '/includes/htmlform/HTMLFloatField.php',
+       'HTMLFloatField' => __DIR__ . '/includes/htmlform/fields/HTMLFloatField.php',
        'HTMLForm' => __DIR__ . '/includes/htmlform/HTMLForm.php',
        'HTMLFormField' => __DIR__ . '/includes/htmlform/HTMLFormField.php',
-       'HTMLFormFieldCloner' => __DIR__ . '/includes/htmlform/HTMLFormFieldCloner.php',
+       'HTMLFormFieldCloner' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldCloner.php',
        'HTMLFormFieldRequiredOptionsException' => __DIR__ . '/includes/htmlform/HTMLFormFieldRequiredOptionsException.php',
-       'HTMLFormFieldWithButton' => __DIR__ . '/includes/htmlform/HTMLFormFieldWithButton.php',
-       'HTMLHiddenField' => __DIR__ . '/includes/htmlform/HTMLHiddenField.php',
-       'HTMLInfoField' => __DIR__ . '/includes/htmlform/HTMLInfoField.php',
-       'HTMLIntField' => __DIR__ . '/includes/htmlform/HTMLIntField.php',
-       'HTMLMultiSelectField' => __DIR__ . '/includes/htmlform/HTMLMultiSelectField.php',
+       'HTMLFormFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLFormFieldWithButton.php',
+       'HTMLHiddenField' => __DIR__ . '/includes/htmlform/fields/HTMLHiddenField.php',
+       'HTMLInfoField' => __DIR__ . '/includes/htmlform/fields/HTMLInfoField.php',
+       'HTMLIntField' => __DIR__ . '/includes/htmlform/fields/HTMLIntField.php',
+       'HTMLMultiSelectField' => __DIR__ . '/includes/htmlform/fields/HTMLMultiSelectField.php',
        'HTMLNestedFilterable' => __DIR__ . '/includes/htmlform/HTMLNestedFilterable.php',
-       'HTMLRadioField' => __DIR__ . '/includes/htmlform/HTMLRadioField.php',
-       'HTMLSelectAndOtherField' => __DIR__ . '/includes/htmlform/HTMLSelectAndOtherField.php',
-       'HTMLSelectField' => __DIR__ . '/includes/htmlform/HTMLSelectField.php',
-       'HTMLSelectLimitField' => __DIR__ . '/includes/htmlform/HTMLSelectLimitField.php',
-       'HTMLSelectNamespace' => __DIR__ . '/includes/htmlform/HTMLSelectNamespace.php',
-       'HTMLSelectNamespaceWithButton' => __DIR__ . '/includes/htmlform/HTMLSelectNamespaceWithButton.php',
-       'HTMLSelectOrOtherField' => __DIR__ . '/includes/htmlform/HTMLSelectOrOtherField.php',
-       'HTMLSubmitField' => __DIR__ . '/includes/htmlform/HTMLSubmitField.php',
-       'HTMLTagFilter' => __DIR__ . '/includes/htmlform/HTMLTagFilter.php',
-       'HTMLTextAreaField' => __DIR__ . '/includes/htmlform/HTMLTextAreaField.php',
-       'HTMLTextField' => __DIR__ . '/includes/htmlform/HTMLTextField.php',
-       'HTMLTextFieldWithButton' => __DIR__ . '/includes/htmlform/HTMLTextFieldWithButton.php',
-       'HTMLTitleTextField' => __DIR__ . '/includes/htmlform/HTMLTitleTextField.php',
-       'HTMLUserTextField' => __DIR__ . '/includes/htmlform/HTMLUserTextField.php',
+       'HTMLRadioField' => __DIR__ . '/includes/htmlform/fields/HTMLRadioField.php',
+       'HTMLSelectAndOtherField' => __DIR__ . '/includes/htmlform/fields/HTMLSelectAndOtherField.php',
+       'HTMLSelectField' => __DIR__ . '/includes/htmlform/fields/HTMLSelectField.php',
+       'HTMLSelectLimitField' => __DIR__ . '/includes/htmlform/fields/HTMLSelectLimitField.php',
+       'HTMLSelectNamespace' => __DIR__ . '/includes/htmlform/fields/HTMLSelectNamespace.php',
+       'HTMLSelectNamespaceWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLSelectNamespaceWithButton.php',
+       'HTMLSelectOrOtherField' => __DIR__ . '/includes/htmlform/fields/HTMLSelectOrOtherField.php',
+       'HTMLSubmitField' => __DIR__ . '/includes/htmlform/fields/HTMLSubmitField.php',
+       'HTMLTagFilter' => __DIR__ . '/includes/htmlform/fields/HTMLTagFilter.php',
+       'HTMLTextAreaField' => __DIR__ . '/includes/htmlform/fields/HTMLTextAreaField.php',
+       'HTMLTextField' => __DIR__ . '/includes/htmlform/fields/HTMLTextField.php',
+       'HTMLTextFieldWithButton' => __DIR__ . '/includes/htmlform/fields/HTMLTextFieldWithButton.php',
+       'HTMLTitleTextField' => __DIR__ . '/includes/htmlform/fields/HTMLTitleTextField.php',
+       'HTMLUserTextField' => __DIR__ . '/includes/htmlform/fields/HTMLUserTextField.php',
        'HWLDFWordAccumulator' => __DIR__ . '/includes/diff/DairikiDiff.php',
        'HashBagOStuff' => __DIR__ . '/includes/libs/objectcache/HashBagOStuff.php',
        'HashConfig' => __DIR__ . '/includes/config/HashConfig.php',
diff --git a/includes/htmlform/HTMLApiField.php b/includes/htmlform/HTMLApiField.php
deleted file mode 100644 (file)
index 24a253e..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-
-class HTMLApiField extends HTMLFormField {
-       public function getTableRow( $value ) {
-               return '';
-       }
-
-       public function getDiv( $value ) {
-               return $this->getTableRow( $value );
-       }
-
-       public function getRaw( $value ) {
-               return $this->getTableRow( $value );
-       }
-
-       public function getInputHTML( $value ) {
-               return '';
-       }
-
-       public function hasVisibleOutput() {
-               return false;
-       }
-}
diff --git a/includes/htmlform/HTMLAutoCompleteSelectField.php b/includes/htmlform/HTMLAutoCompleteSelectField.php
deleted file mode 100644 (file)
index 76a88d5..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-<?php
-
-/**
- * Text field for selecting a value from a large list of possible values, with
- * auto-completion and optionally with a select dropdown for selecting common
- * options.
- *
- * HTMLComboboxField implements most of the same functionality and should be
- * used instead, if possible.
- *
- * If one of 'options-messages', 'options', or 'options-message' is provided
- * and non-empty, the select dropdown will be shown. An 'other' key will be
- * appended using message 'htmlform-selectorother-other' if not already
- * present.
- *
- * Besides the parameters recognized by HTMLTextField, the following are
- * recognized:
- *   options-messages - As for HTMLSelectField
- *   options - As for HTMLSelectField
- *   options-message - As for HTMLSelectField
- *   autocomplete - Associative array mapping display text to values.
- *   autocomplete-messages - Like autocomplete, but keys are message names.
- *   require-match - Boolean, if true the value must be in the options or the
- *     autocomplete.
- *   other-message - Message to use instead of htmlform-selectorother-other for
- *      the 'other' message.
- *   other - Raw text to use for the 'other' message
- */
-class HTMLAutoCompleteSelectField extends HTMLTextField {
-       protected $autocomplete = [];
-
-       function __construct( $params ) {
-               $params += [
-                       'require-match' => false,
-               ];
-
-               parent::__construct( $params );
-
-               if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) {
-                       foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) {
-                               $key = $this->msg( $key )->plain();
-                               $this->autocomplete[$key] = strval( $value );
-                       }
-               } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) {
-                       foreach ( $this->mParams['autocomplete'] as $key => $value ) {
-                               $this->autocomplete[$key] = strval( $value );
-                       }
-               }
-               if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) {
-                       throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' );
-               }
-
-               $this->getOptions();
-               if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) {
-                       if ( isset( $params['other-message'] ) ) {
-                               $msg = $this->getMessage( $params['other-message'] )->text();
-                       } elseif ( isset( $params['other'] ) ) {
-                               $msg = $params['other'];
-                       } else {
-                               $msg = wfMessage( 'htmlform-selectorother-other' )->text();
-                       }
-                       $this->mOptions[$msg] = 'other';
-               }
-       }
-
-       function loadDataFromRequest( $request ) {
-               if ( $request->getCheck( $this->mName ) ) {
-                       $val = $request->getText( $this->mName . '-select', 'other' );
-
-                       if ( $val === 'other' ) {
-                               $val = $request->getText( $this->mName );
-                               if ( isset( $this->autocomplete[$val] ) ) {
-                                       $val = $this->autocomplete[$val];
-                               }
-                       }
-
-                       return $val;
-               } else {
-                       return $this->getDefault();
-               }
-       }
-
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
-
-               if ( in_array( strval( $value ), $validOptions, true ) ) {
-                       return true;
-               } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) {
-                       return true;
-               } elseif ( $this->mParams['require-match'] ) {
-                       return $this->msg( 'htmlform-select-badoption' )->parse();
-               }
-
-               return true;
-       }
-
-       // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
-       public function getAttributes( array $list ) {
-               $attribs = [
-                       'type' => 'text',
-                       'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ),
-               ] + parent::getAttributes( $list );
-
-               if ( $this->getOptions() ) {
-                       $attribs['data-hide-if'] = FormatJson::encode(
-                               [ '!==', $this->mName . '-select', 'other' ]
-                       );
-               }
-
-               return $attribs;
-       }
-
-       function getInputHTML( $value ) {
-               $oldClass = $this->mClass;
-               $this->mClass = (array)$this->mClass;
-
-               $valInSelect = false;
-               $ret = '';
-
-               if ( $this->getOptions() ) {
-                       if ( $value !== false ) {
-                               $value = strval( $value );
-                               $valInSelect = in_array(
-                                       $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
-                               );
-                       }
-
-                       $selected = $valInSelect ? $value : 'other';
-                       $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected );
-                       $select->addOptions( $this->getOptions() );
-                       $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
-
-                       if ( !empty( $this->mParams['disabled'] ) ) {
-                               $select->setAttribute( 'disabled', 'disabled' );
-                       }
-
-                       if ( isset( $this->mParams['tabindex'] ) ) {
-                               $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
-                       }
-
-                       $ret = $select->getHTML() . "<br />\n";
-
-                       $this->mClass[] = 'mw-htmlform-hide-if';
-               }
-
-               if ( $valInSelect ) {
-                       $value = '';
-               } else {
-                       $key = array_search( strval( $value ), $this->autocomplete, true );
-                       if ( $key !== false ) {
-                               $value = $key;
-                       }
-               }
-
-               $this->mClass[] = 'mw-htmlform-autocomplete';
-               $ret .= parent::getInputHTML( $valInSelect ? '' : $value );
-               $this->mClass = $oldClass;
-
-               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
deleted file mode 100644 (file)
index 64fe7ed..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-<?php
-
-/**
- * Adds a generic button inline to the form. Does not do anything, you must add
- * click handling code in JavaScript. Use a HTMLSubmitField if you merely
- * wish to add a submit button to a form.
- *
- * Additional recognized configuration parameters include:
- * - flags: OOUI flags for the button, see OOUI\FlaggedElement
- * - buttonlabel-message: Message to use for the button display text, instead
- *   of the value from 'default'. Overrides 'buttonlabel' and 'buttonlabel-raw'.
- * - buttonlabel: Text to display for the button display text, instead
- *   of the value from 'default'. Overrides 'buttonlabel-raw'.
- * - buttonlabel-raw: HTMLto display for the button display text, instead
- *   of the value from 'default'.
- *
- * Note that the buttonlabel parameters are not supported on IE6 and IE7 due to
- * bugs in those browsers. If detected, they will be served buttons using the
- * value of 'default' as the button label.
- *
- * @since 1.22
- */
-class HTMLButtonField extends HTMLFormField {
-       protected $buttonType = 'button';
-       protected $buttonLabel = null;
-
-       /** @var array $mFlags Flags to add to OOUI Button widget */
-       protected $mFlags = [];
-
-       public function __construct( $info ) {
-               $info['nodata'] = true;
-               if ( isset( $info['flags'] ) ) {
-                       $this->mFlags = $info['flags'];
-               }
-
-               # Generate the label from a message, if possible
-               if ( isset( $info['buttonlabel-message'] ) ) {
-                       $this->buttonLabel = $this->getMessage( $info['buttonlabel-message'] )->parse();
-               } elseif ( isset( $info['buttonlabel'] ) ) {
-                       if ( $info['buttonlabel'] === '&#160;' ) {
-                               // Apparently some things set &nbsp directly and in an odd format
-                               $this->buttonLabel = '&#160;';
-                       } else {
-                               $this->buttonLabel = htmlspecialchars( $info['buttonlabel'] );
-                       }
-               } elseif ( isset( $info['buttonlabel-raw'] ) ) {
-                       $this->buttonLabel = $info['buttonlabel-raw'];
-               }
-
-               $this->setShowEmptyLabel( false );
-
-               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 = [
-                       'class' => 'mw-htmlform-submit ' . $this->mClass . $flags,
-                       'id' => $this->mID,
-                       'type' => $this->buttonType,
-                       'name' => $this->mName,
-                       'value' => $this->getDefault(),
-               ] + $this->getAttributes( [ 'disabled', 'tabindex' ] );
-
-               if ( $this->isBadIE() ) {
-                       return Html::element( 'input', $attr );
-               } else {
-                       return Html::rawElement( 'button', $attr,
-                               $this->buttonLabel ?: htmlspecialchars( $this->getDefault() ) );
-               }
-       }
-
-       /**
-        * Get the OOUI widget for this field.
-        * @param string $value
-        * @return OOUI\ButtonInputWidget
-        */
-       public function getInputOOUI( $value ) {
-               return new OOUI\ButtonInputWidget( [
-                       'name' => $this->mName,
-                       'value' => $this->getDefault(),
-                       'label' => !$this->isBadIE() && $this->buttonLabel
-                               ? new OOUI\HtmlSnippet( $this->buttonLabel )
-                               : $this->getDefault(),
-                       'type' => $this->buttonType,
-                       'classes' => [ 'mw-htmlform-submit', $this->mClass ],
-                       'id' => $this->mID,
-                       'flags' => $this->mFlags,
-                       'useInputTag' => $this->isBadIE(),
-               ] + OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
-               ) );
-       }
-
-       protected function needsLabel() {
-               return false;
-       }
-
-       /**
-        * Button cannot be invalid
-        *
-        * @param string $value
-        * @param array $alldata
-        *
-        * @return bool
-        */
-       public function validate( $value, $alldata ) {
-               return true;
-       }
-
-       /**
-        * IE<8 has bugs with <button>, so we'll need to avoid them.
-        * @return bool Whether the request is from a bad version of IE
-        */
-       private function isBadIE() {
-               $request = $this->mParent
-                       ? $this->mParent->getRequest()
-                       : RequestContext::getMain()->getRequest();
-               return preg_match( '/MSIE [1-7]\./i', $request->getHeader( 'User-Agent' ) );
-       }
-}
diff --git a/includes/htmlform/HTMLCheckField.php b/includes/htmlform/HTMLCheckField.php
deleted file mode 100644 (file)
index a553839..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-
-/**
- * A checkbox field
- */
-class HTMLCheckField extends HTMLFormField {
-       function getInputHTML( $value ) {
-               global $wgUseMediaWikiUIEverywhere;
-
-               if ( !empty( $this->mParams['invert'] ) ) {
-                       $value = !$value;
-               }
-
-               $attr = $this->getTooltipAndAccessKey();
-               $attr['id'] = $this->mID;
-
-               $attr += $this->getAttributes( [ 'disabled', 'tabindex' ] );
-
-               if ( $this->mClass !== '' ) {
-                       $attr['class'] = $this->mClass;
-               }
-
-               $attrLabel = [ 'for' => $this->mID ];
-               if ( isset( $attr['title'] ) ) {
-                       // propagate tooltip to label
-                       $attrLabel['title'] = $attr['title'];
-               }
-
-               $chkLabel = Xml::check( $this->mName, $value, $attr ) .
-                       '&#160;' .
-                       Html::rawElement( 'label', $attrLabel, $this->mLabel );
-
-               if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
-                       $chkLabel = Html::rawElement(
-                               'div',
-                               [ 'class' => 'mw-ui-checkbox' ],
-                               $chkLabel
-                       );
-               }
-
-               return $chkLabel;
-       }
-
-       /**
-        * 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 += OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
-               );
-
-               if ( $this->mClass !== '' ) {
-                       $attr['classes'] = [ $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() {
-               if ( $this->mParent instanceof OOUIHTMLForm ) {
-                       return $this->mLabel;
-               } elseif (
-                       $this->mParent instanceof HTMLForm &&
-                       $this->mParent->getDisplayFormat() === 'div'
-               ) {
-                       return '';
-               } else {
-                       return '&#160;';
-               }
-       }
-
-       /**
-        * Get label alignment when generating field for OOUI.
-        * @return string 'left', 'right', 'top' or 'inline'
-        */
-       protected function getLabelAlignOOUI() {
-               return 'inline';
-       }
-
-       /**
-        * checkboxes don't need a label.
-        * @return bool
-        */
-       protected function needsLabel() {
-               return false;
-       }
-
-       /**
-        * @param WebRequest $request
-        *
-        * @return bool
-        */
-       function loadDataFromRequest( $request ) {
-               $invert = isset( $this->mParams['invert'] ) && $this->mParams['invert'];
-
-               // GetCheck won't work like we want for checks.
-               // Fetch the value in either one of the two following case:
-               // - we have a valid submit attempt (form was just submitted, or a GET URL forged by the user)
-               // - checkbox name has a value (false or true), ie is not null
-               if ( $this->isSubmitAttempt( $request ) || $request->getVal( $this->mName ) !== null ) {
-                       return $invert
-                               ? !$request->getBool( $this->mName )
-                               : $request->getBool( $this->mName );
-               } else {
-                       return (bool)$this->getDefault();
-               }
-       }
-}
diff --git a/includes/htmlform/HTMLCheckMatrix.php b/includes/htmlform/HTMLCheckMatrix.php
deleted file mode 100644 (file)
index b324fb6..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-<?php
-
-/**
- * A checkbox matrix
- * Operates similarly to HTMLMultiSelectField, but instead of using an array of
- * options, uses an array of rows and an array of columns to dynamically
- * construct a matrix of options. The tags used to identify a particular cell
- * are of the form "columnName-rowName"
- *
- * Options:
- *   - columns
- *     - Required list of columns in the matrix.
- *   - rows
- *     - Required list of rows in the matrix.
- *   - force-options-on
- *     - Accepts array of column-row tags to be displayed as enabled but unavailable to change
- *   - force-options-off
- *     - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
- *   - tooltips
- *     - Optional array mapping row label to tooltip content
- *   - tooltip-class
- *     - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
- */
-class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
-       static private $requiredParams = [
-               // Required by underlying HTMLFormField
-               'fieldname',
-               // Required by HTMLCheckMatrix
-               'rows',
-               'columns'
-       ];
-
-       public function __construct( $params ) {
-               $missing = array_diff( self::$requiredParams, array_keys( $params ) );
-               if ( $missing ) {
-                       throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
-               }
-               parent::__construct( $params );
-       }
-
-       function validate( $value, $alldata ) {
-               $rows = $this->mParams['rows'];
-               $columns = $this->mParams['columns'];
-
-               // Make sure user-defined validation callback is run
-               $p = parent::validate( $value, $alldata );
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               // Make sure submitted value is an array
-               if ( !is_array( $value ) ) {
-                       return false;
-               }
-
-               // If all options are valid, array_intersect of the valid options
-               // and the provided options will return the provided options.
-               $validOptions = [];
-               foreach ( $rows as $rowTag ) {
-                       foreach ( $columns as $columnTag ) {
-                               $validOptions[] = $columnTag . '-' . $rowTag;
-                       }
-               }
-               $validValues = array_intersect( $value, $validOptions );
-               if ( count( $validValues ) == count( $value ) ) {
-                       return true;
-               } else {
-                       return $this->msg( 'htmlform-select-badoption' )->parse();
-               }
-       }
-
-       /**
-        * Build a table containing a matrix of checkbox options.
-        * The value of each option is a combination of the row tag and column tag.
-        * mParams['rows'] is an array with row labels as keys and row tags as values.
-        * mParams['columns'] is an array with column labels as keys and column tags as values.
-        *
-        * @param array $value Array of the options that should be checked
-        *
-        * @return string
-        */
-       function getInputHTML( $value ) {
-               $html = '';
-               $tableContents = '';
-               $rows = $this->mParams['rows'];
-               $columns = $this->mParams['columns'];
-
-               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
-
-               // Build the column headers
-               $headerContents = Html::rawElement( 'td', [], '&#160;' );
-               foreach ( $columns as $columnLabel => $columnTag ) {
-                       $headerContents .= Html::rawElement( 'td', [], $columnLabel );
-               }
-               $tableContents .= Html::rawElement( 'tr', [], "\n$headerContents\n" );
-
-               $tooltipClass = 'mw-icon-question';
-               if ( isset( $this->mParams['tooltip-class'] ) ) {
-                       $tooltipClass = $this->mParams['tooltip-class'];
-               }
-
-               // Build the options matrix
-               foreach ( $rows as $rowLabel => $rowTag ) {
-                       // Append tooltip if configured
-                       if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
-                               $tooltipAttribs = [
-                                       'class' => "mw-htmlform-tooltip $tooltipClass",
-                                       'title' => $this->mParams['tooltips'][$rowLabel],
-                               ];
-                               $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
-                       }
-                       $rowContents = Html::rawElement( 'td', [], $rowLabel );
-                       foreach ( $columns as $columnTag ) {
-                               $thisTag = "$columnTag-$rowTag";
-                               // Construct the checkbox
-                               $thisAttribs = [
-                                       'id' => "{$this->mID}-$thisTag",
-                                       'value' => $thisTag,
-                               ];
-                               $checked = in_array( $thisTag, (array)$value, true );
-                               if ( $this->isTagForcedOff( $thisTag ) ) {
-                                       $checked = false;
-                                       $thisAttribs['disabled'] = 1;
-                               } elseif ( $this->isTagForcedOn( $thisTag ) ) {
-                                       $checked = true;
-                                       $thisAttribs['disabled'] = 1;
-                               }
-
-                               $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs );
-
-                               $rowContents .= Html::rawElement(
-                                       'td',
-                                       [],
-                                       $checkbox
-                               );
-                       }
-                       $tableContents .= Html::rawElement( 'tr', [], "\n$rowContents\n" );
-               }
-
-               // Put it all in a table
-               $html .= Html::rawElement( 'table',
-                               [ 'class' => 'mw-htmlform-matrix' ],
-                               Html::rawElement( 'tbody', [], "\n$tableContents\n" ) ) . "\n";
-
-               return $html;
-       }
-
-       protected function getOneCheckbox( $checked, $attribs ) {
-               if ( $this->mParent instanceof OOUIHTMLForm ) {
-                       return new OOUI\CheckboxInputWidget( [
-                               'name' => "{$this->mName}[]",
-                               'selected' => $checked,
-                       ] + OOUI\Element::configFromHtmlAttributes(
-                               $attribs
-                       ) );
-               } else {
-                       $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
-                       if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
-                               $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
-                                       $checkbox .
-                                       Html::element( 'label', [ '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'] );
-       }
-
-       protected function isTagForcedOn( $tag ) {
-               return isset( $this->mParams['force-options-on'] )
-                       && in_array( $tag, $this->mParams['force-options-on'] );
-       }
-
-       /**
-        * Get the complete table row for the input, including help text,
-        * labels, and whatever.
-        * We override this function since the label should always be on a separate
-        * line above the options in the case of a checkbox matrix, i.e. it's always
-        * a "vertical-label".
-        *
-        * @param string $value The value to set the input to
-        *
-        * @return string Complete HTML table row
-        */
-       function getTableRow( $value ) {
-               list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
-               $inputHtml = $this->getInputHTML( $value );
-               $fieldType = get_class( $this );
-               $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
-               $cellAttributes = [ 'colspan' => 2 ];
-
-               $hideClass = '';
-               $hideAttributes = [];
-               if ( $this->mHideIf ) {
-                       $hideAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
-                       $hideClass = 'mw-htmlform-hide-if';
-               }
-
-               $label = $this->getLabelHtml( $cellAttributes );
-
-               $field = Html::rawElement(
-                       'td',
-                       [ 'class' => 'mw-input' ] + $cellAttributes,
-                       $inputHtml . "\n$errors"
-               );
-
-               $html = Html::rawElement( 'tr',
-                       [ 'class' => "mw-htmlform-vertical-label $hideClass" ] + $hideAttributes,
-                       $label );
-               $html .= Html::rawElement( 'tr',
-                       [ 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $hideClass" ] +
-                               $hideAttributes,
-                       $field );
-
-               return $html . $helptext;
-       }
-
-       /**
-        * @param WebRequest $request
-        *
-        * @return array
-        */
-       function loadDataFromRequest( $request ) {
-               if ( $this->isSubmitAttempt( $request ) ) {
-                       // Checkboxes are just not added to the request arrays if they're not checked,
-                       // so it's perfectly possible for there not to be an entry at all
-                       return $request->getArray( $this->mName, [] );
-               } else {
-                       // That's ok, the user has not yet submitted the form, so show the defaults
-                       return $this->getDefault();
-               }
-       }
-
-       function getDefault() {
-               if ( isset( $this->mDefault ) ) {
-                       return $this->mDefault;
-               } else {
-                       return [];
-               }
-       }
-
-       function filterDataForSubmit( $data ) {
-               $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
-               $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
-               $res = [];
-               foreach ( $columns as $column ) {
-                       foreach ( $rows as $row ) {
-                               // Make sure option hasn't been forced
-                               $thisTag = "$column-$row";
-                               if ( $this->isTagForcedOff( $thisTag ) ) {
-                                       $res[$thisTag] = false;
-                               } elseif ( $this->isTagForcedOn( $thisTag ) ) {
-                                       $res[$thisTag] = true;
-                               } else {
-                                       $res[$thisTag] = in_array( $thisTag, $data );
-                               }
-                       }
-               }
-
-               return $res;
-       }
-}
diff --git a/includes/htmlform/HTMLComboboxField.php b/includes/htmlform/HTMLComboboxField.php
deleted file mode 100644 (file)
index 778aedb..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<?php
-
-/**
- * A combo box field.
- *
- * You can think of it as a dropdown select with the ability to add custom options,
- * or as a text field with input suggestions (autocompletion).
- *
- * When JavaScript is not supported or enabled, it uses HTML5 `<datalist>` element.
- *
- * Besides the parameters recognized by HTMLTextField, the following are
- * recognized:
- *   options-messages - As for HTMLSelectField
- *   options - As for HTMLSelectField
- *   options-message - As for HTMLSelectField
- */
-class HTMLComboboxField extends HTMLTextField {
-       // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
-       public function getAttributes( array $list ) {
-               $attribs = [
-                       'type' => 'text',
-                       'list' => $this->mName . '-datalist',
-               ] + parent::getAttributes( $list );
-
-               return $attribs;
-       }
-
-       function getInputHTML( $value ) {
-               $datalist = new XmlSelect( false, $this->mName . '-datalist' );
-               $datalist->setTagName( 'datalist' );
-               $datalist->addOptions( $this->getOptions() );
-
-               return parent::getInputHTML( $value ) . $datalist->getHTML();
-       }
-
-       function getInputOOUI( $value ) {
-               $disabled = false;
-               $allowedParams = [ 'tabindex' ];
-               $attribs = OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( $allowedParams )
-               );
-
-               if ( $this->mClass !== '' ) {
-                       $attribs['classes'] = [ $this->mClass ];
-               }
-
-               if ( !empty( $this->mParams['disabled'] ) ) {
-                       $disabled = true;
-               }
-
-               return new OOUI\ComboBoxInputWidget( [
-                       'name' => $this->mName,
-                       'id' => $this->mID,
-                       'options' => $this->getOptionsOOUI(),
-                       'value' => strval( $value ),
-                       'disabled' => $disabled,
-               ] + $attribs );
-       }
-}
diff --git a/includes/htmlform/HTMLEditTools.php b/includes/htmlform/HTMLEditTools.php
deleted file mode 100644 (file)
index 1b5d1fb..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-class HTMLEditTools extends HTMLFormField {
-       public function getInputHTML( $value ) {
-               return '';
-       }
-
-       public function getTableRow( $value ) {
-               $msg = $this->formatMsg();
-
-               return
-                       '<tr><td></td><td class="mw-input">' .
-                       '<div class="mw-editTools">' .
-                       $msg->parseAsBlock() .
-                       "</div></td></tr>\n";
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getDiv( $value ) {
-               $msg = $this->formatMsg();
-
-               return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getRaw( $value ) {
-               return $this->getDiv( $value );
-       }
-
-       protected function formatMsg() {
-               if ( empty( $this->mParams['message'] ) ) {
-                       $msg = $this->msg( 'edittools' );
-               } else {
-                       $msg = $this->getMessage( $this->mParams['message'] );
-                       if ( $msg->isDisabled() ) {
-                               $msg = $this->msg( 'edittools' );
-                       }
-               }
-               $msg->inContentLanguage();
-
-               return $msg;
-       }
-}
diff --git a/includes/htmlform/HTMLFloatField.php b/includes/htmlform/HTMLFloatField.php
deleted file mode 100644 (file)
index 2ef4978..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-/**
- * A field that will contain a numeric value
- */
-class HTMLFloatField extends HTMLTextField {
-       function getSize() {
-               return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 20;
-       }
-
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               $value = trim( $value );
-
-               # http://www.w3.org/TR/html5/infrastructure.html#floating-point-numbers
-               # with the addition that a leading '+' sign is ok.
-               if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
-                       return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
-               }
-
-               # The "int" part of these message names is rather confusing.
-               # They make equal sense for all numbers.
-               if ( isset( $this->mParams['min'] ) ) {
-                       $min = $this->mParams['min'];
-
-                       if ( $min > $value ) {
-                               return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
-                       }
-               }
-
-               if ( isset( $this->mParams['max'] ) ) {
-                       $max = $this->mParams['max'];
-
-                       if ( $max < $value ) {
-                               return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
-                       }
-               }
-
-               return true;
-       }
-}
diff --git a/includes/htmlform/HTMLFormFieldCloner.php b/includes/htmlform/HTMLFormFieldCloner.php
deleted file mode 100644 (file)
index ec1bd84..0000000
+++ /dev/null
@@ -1,380 +0,0 @@
-<?php
-
-/**
- * A container for HTMLFormFields that allows for multiple copies of the set of
- * fields to be displayed to and entered by the user.
- *
- * Recognized parameters, besides the general ones, include:
- *   fields - HTMLFormField descriptors for the subfields this cloner manages.
- *     The format is just like for the HTMLForm. A field with key 'delete' is
- *     special: it must have type = submit and will serve to delete the group
- *     of fields.
- *   required - If specified, at least one group of fields must be submitted.
- *   format - HTMLForm display format to use when displaying the subfields:
- *     'table', 'div', or 'raw'.
- *   row-legend - If non-empty, each group of subfields will be enclosed in a
- *     fieldset. The value is the name of a message key to use as the legend.
- *   create-button-message - Message to use as the text of the button to
- *     add an additional group of fields.
- *   delete-button-message - Message to use as the text of automatically-
- *     generated 'delete' button. Ignored if 'delete' is included in 'fields'.
- *
- * In the generated HTML, the subfields will be named along the lines of
- * "clonerName[index][fieldname]", with ids "clonerId--index--fieldid". 'index'
- * may be a number or an arbitrary string, and may likely change when the page
- * is resubmitted. Cloners may be nested, resulting in field names along the
- * lines of "cloner1Name[index1][cloner2Name][index2][fieldname]" and
- * corresponding ids.
- *
- * Use of cloner may result in submissions of the page that are not submissions
- * of the HTMLForm, when non-JavaScript clients use the create or remove buttons.
- *
- * The result is an array, with values being arrays mapping subfield names to
- * their values. On non-HTMLForm-submission page loads, there may also be
- * additional (string) keys present with other types of values.
- *
- * @since 1.23
- */
-class HTMLFormFieldCloner extends HTMLFormField {
-       private static $counter = 0;
-
-       /**
-        * @var string String uniquely identifying this cloner instance and
-        * unlikely to exist otherwise in the generated HTML, while still being
-        * valid as part of an HTML id.
-        */
-       protected $uniqueId;
-
-       public function __construct( $params ) {
-               $this->uniqueId = get_class( $this ) . ++self::$counter . 'x';
-               parent::__construct( $params );
-
-               if ( empty( $this->mParams['fields'] ) || !is_array( $this->mParams['fields'] ) ) {
-                       throw new MWException( 'HTMLFormFieldCloner called without any fields' );
-               }
-
-               // Make sure the delete button, if explicitly specified, is sane
-               if ( isset( $this->mParams['fields']['delete'] ) ) {
-                       $class = 'mw-htmlform-cloner-delete-button';
-                       $info = $this->mParams['fields']['delete'] + [
-                               'cssclass' => $class
-                       ];
-                       unset( $info['name'], $info['class'] );
-
-                       if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) {
-                               throw new MWException(
-                                       'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
-                               );
-                       }
-
-                       if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) {
-                               $info['cssclass'] .= " $class";
-                       }
-
-                       $this->mParams['fields']['delete'] = $info;
-               }
-       }
-
-       /**
-        * Create the HTMLFormFields that go inside this element, using the
-        * specified key.
-        *
-        * @param string $key Array key under which these fields should be named
-        * @return HTMLFormField[]
-        */
-       protected function createFieldsForKey( $key ) {
-               $fields = [];
-               foreach ( $this->mParams['fields'] as $fieldname => $info ) {
-                       $name = "{$this->mName}[$key][$fieldname]";
-                       if ( isset( $info['name'] ) ) {
-                               $info['name'] = "{$this->mName}[$key][{$info['name']}]";
-                       } else {
-                               $info['name'] = $name;
-                       }
-                       if ( isset( $info['id'] ) ) {
-                               $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--{$info['id']}" );
-                       } else {
-                               $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" );
-                       }
-                       $field = HTMLForm::loadInputFromParameters( $name, $info, $this->mParent );
-                       $fields[$fieldname] = $field;
-               }
-               return $fields;
-       }
-
-       /**
-        * Re-key the specified values array to match the names applied by
-        * createFieldsForKey().
-        *
-        * @param string $key Array key under which these fields should be named
-        * @param array $values Values array from the request
-        * @return array
-        */
-       protected function rekeyValuesArray( $key, $values ) {
-               $data = [];
-               foreach ( $values as $fieldname => $value ) {
-                       $name = "{$this->mName}[$key][$fieldname]";
-                       $data[$name] = $value;
-               }
-               return $data;
-       }
-
-       protected function needsLabel() {
-               return false;
-       }
-
-       public function loadDataFromRequest( $request ) {
-               // It's possible that this might be posted with no fields. Detect that
-               // by looking for an edit token.
-               if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
-                       return $this->getDefault();
-               }
-
-               $values = $request->getArray( $this->mName );
-               if ( $values === null ) {
-                       $values = [];
-               }
-
-               $ret = [];
-               foreach ( $values as $key => $value ) {
-                       if ( $key === 'create' || isset( $value['delete'] ) ) {
-                               $ret['nonjs'] = 1;
-                               continue;
-                       }
-
-                       // Add back in $request->getValues() so things that look for e.g.
-                       // wpEditToken don't fail.
-                       $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
-
-                       $fields = $this->createFieldsForKey( $key );
-                       $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() );
-                       $row = [];
-                       foreach ( $fields as $fieldname => $field ) {
-                               if ( $field->skipLoadData( $subrequest ) ) {
-                                       continue;
-                               } elseif ( !empty( $field->mParams['disabled'] ) ) {
-                                       $row[$fieldname] = $field->getDefault();
-                               } else {
-                                       $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
-                               }
-                       }
-                       $ret[] = $row;
-               }
-
-               if ( isset( $values['create'] ) ) {
-                       // Non-JS client clicked the "create" button.
-                       $fields = $this->createFieldsForKey( $this->uniqueId );
-                       $row = [];
-                       foreach ( $fields as $fieldname => $field ) {
-                               if ( !empty( $field->mParams['nodata'] ) ) {
-                                       continue;
-                               } else {
-                                       $row[$fieldname] = $field->getDefault();
-                               }
-                       }
-                       $ret[] = $row;
-               }
-
-               return $ret;
-       }
-
-       public function getDefault() {
-               $ret = parent::getDefault();
-
-               // The default default is one entry with all subfields at their
-               // defaults.
-               if ( $ret === null ) {
-                       $fields = $this->createFieldsForKey( $this->uniqueId );
-                       $row = [];
-                       foreach ( $fields as $fieldname => $field ) {
-                               if ( !empty( $field->mParams['nodata'] ) ) {
-                                       continue;
-                               } else {
-                                       $row[$fieldname] = $field->getDefault();
-                               }
-                       }
-                       $ret = [ $row ];
-               }
-
-               return $ret;
-       }
-
-       public function cancelSubmit( $values, $alldata ) {
-               if ( isset( $values['nonjs'] ) ) {
-                       return true;
-               }
-
-               foreach ( $values as $key => $value ) {
-                       $fields = $this->createFieldsForKey( $key );
-                       foreach ( $fields as $fieldname => $field ) {
-                               if ( !array_key_exists( $fieldname, $value ) ) {
-                                       continue;
-                               }
-                               if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
-                                       return true;
-                               }
-                       }
-               }
-
-               return parent::cancelSubmit( $values, $alldata );
-       }
-
-       public function validate( $values, $alldata ) {
-               if ( isset( $this->mParams['required'] )
-                       && $this->mParams['required'] !== false
-                       && !$values
-               ) {
-                       return $this->msg( 'htmlform-cloner-required' )->parseAsBlock();
-               }
-
-               if ( isset( $values['nonjs'] ) ) {
-                       // The submission was a non-JS create/delete click, so fail
-                       // validation in case cancelSubmit() somehow didn't already handle
-                       // it.
-                       return false;
-               }
-
-               foreach ( $values as $key => $value ) {
-                       $fields = $this->createFieldsForKey( $key );
-                       foreach ( $fields as $fieldname => $field ) {
-                               if ( !array_key_exists( $fieldname, $value ) ) {
-                                       continue;
-                               }
-                               $ok = $field->validate( $value[$fieldname], $alldata );
-                               if ( $ok !== true ) {
-                                       return false;
-                               }
-                       }
-               }
-
-               return parent::validate( $values, $alldata );
-       }
-
-       /**
-        * Get the input HTML for the specified key.
-        *
-        * @param string $key Array key under which the fields should be named
-        * @param array $values
-        * @return string
-        */
-       protected function getInputHTMLForKey( $key, $values ) {
-               $displayFormat = isset( $this->mParams['format'] )
-                       ? $this->mParams['format']
-                       : $this->mParent->getDisplayFormat();
-
-               // Conveniently, PHP method names are case-insensitive.
-               $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
-
-               $html = '';
-               $hidden = '';
-               $hasLabel = false;
-
-               $fields = $this->createFieldsForKey( $key );
-               foreach ( $fields as $fieldname => $field ) {
-                       $v = array_key_exists( $fieldname, $values )
-                               ? $values[$fieldname]
-                               : $field->getDefault();
-
-                       if ( $field instanceof HTMLHiddenField ) {
-                               // HTMLHiddenField doesn't generate its own HTML
-                               list( $name, $value, $params ) = $field->getHiddenFieldData( $v );
-                               $hidden .= Html::hidden( $name, $value, $params ) . "\n";
-                       } else {
-                               $html .= $field->$getFieldHtmlMethod( $v );
-
-                               $labelValue = trim( $field->getLabel() );
-                               if ( $labelValue != '&#160;' && $labelValue !== '' ) {
-                                       $hasLabel = true;
-                               }
-                       }
-               }
-
-               if ( !isset( $fields['delete'] ) ) {
-                       $name = "{$this->mName}[$key][delete]";
-                       $label = isset( $this->mParams['delete-button-message'] )
-                               ? $this->mParams['delete-button-message']
-                               : 'htmlform-cloner-delete';
-                       $field = HTMLForm::loadInputFromParameters( $name, [
-                               'type' => 'submit',
-                               'name' => $name,
-                               'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ),
-                               'cssclass' => 'mw-htmlform-cloner-delete-button',
-                               'default' => $this->getMessage( $label )->text(),
-                       ], $this->mParent );
-                       $v = $field->getDefault();
-
-                       if ( $displayFormat === 'table' ) {
-                               $html .= $field->$getFieldHtmlMethod( $v );
-                       } else {
-                               $html .= $field->getInputHTML( $v );
-                       }
-               }
-
-               if ( $displayFormat !== 'raw' ) {
-                       $classes = [
-                               'mw-htmlform-cloner-row',
-                       ];
-
-                       if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
-                               $classes[] = 'mw-htmlform-nolabel';
-                       }
-
-                       $attribs = [
-                               'class' => implode( ' ', $classes ),
-                       ];
-
-                       if ( $displayFormat === 'table' ) {
-                               $html = Html::rawElement( 'table',
-                                       $attribs,
-                                       Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n";
-                       } else {
-                               $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
-                       }
-               }
-
-               $html .= $hidden;
-
-               if ( !empty( $this->mParams['row-legend'] ) ) {
-                       $legend = $this->msg( $this->mParams['row-legend'] )->text();
-                       $html = Xml::fieldset( $legend, $html );
-               }
-
-               return $html;
-       }
-
-       public function getInputHTML( $values ) {
-               $html = '';
-
-               foreach ( (array)$values as $key => $value ) {
-                       if ( $key === 'nonjs' ) {
-                               continue;
-                       }
-                       $html .= Html::rawElement( 'li', [ 'class' => 'mw-htmlform-cloner-li' ],
-                               $this->getInputHTMLForKey( $key, $value )
-                       );
-               }
-
-               $template = $this->getInputHTMLForKey( $this->uniqueId, null );
-               $html = Html::rawElement( 'ul', [
-                       'id' => "mw-htmlform-cloner-list-{$this->mID}",
-                       'class' => 'mw-htmlform-cloner-ul',
-                       'data-template' => $template,
-                       'data-unique-id' => $this->uniqueId,
-               ], $html );
-
-               $name = "{$this->mName}[create]";
-               $label = isset( $this->mParams['create-button-message'] )
-                       ? $this->mParams['create-button-message']
-                       : 'htmlform-cloner-create';
-               $field = HTMLForm::loadInputFromParameters( $name, [
-                       'type' => 'submit',
-                       'name' => $name,
-                       'id' => Sanitizer::escapeId( "{$this->mID}--create" ),
-                       'cssclass' => 'mw-htmlform-cloner-create-button',
-                       'default' => $this->getMessage( $label )->text(),
-               ], $this->mParent );
-               $html .= $field->getInputHTML( $field->getDefault() );
-
-               return $html;
-       }
-}
diff --git a/includes/htmlform/HTMLFormFieldWithButton.php b/includes/htmlform/HTMLFormFieldWithButton.php
deleted file mode 100644 (file)
index bcb07bd..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-<?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 = [ '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 = [
-                       'class' => 'mw-htmlform-submit ' . $this->mButtonClass,
-                       'id' => $this->mButtonId,
-               ] + $this->getAttributes( [ 'disabled', 'tabindex' ] );
-
-               return Html::input( $this->mButtonName, $this->mButtonValue, $this->mButtonType, $attr );
-       }
-
-       public function getInputOOUI( $value ) {
-               return new OOUI\ButtonInputWidget( [
-                       'name' => $this->mButtonName,
-                       'value' => $this->mButtonValue,
-                       'type' => $this->mButtonType,
-                       'label' => $this->mButtonValue,
-                       'flags' => $this->mButtonFlags,
-               ] + OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
-               ) );
-       }
-
-       /**
-        * 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/HTMLHiddenField.php b/includes/htmlform/HTMLHiddenField.php
deleted file mode 100644 (file)
index c0fce2b..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<?php
-
-class HTMLHiddenField extends HTMLFormField {
-       protected $outputAsDefault = true;
-
-       public function __construct( $params ) {
-               parent::__construct( $params );
-
-               if ( isset( $this->mParams['output-as-default'] ) ) {
-                       $this->outputAsDefault = (bool)$this->mParams['output-as-default'];
-               }
-
-               # Per HTML5 spec, hidden fields cannot be 'required'
-               # http://www.w3.org/TR/html5/forms.html#hidden-state-%28type=hidden%29
-               unset( $this->mParams['required'] );
-       }
-
-       public function getHiddenFieldData( $value ) {
-               $params = [];
-               if ( $this->mID ) {
-                       $params['id'] = $this->mID;
-               }
-
-               if ( $this->outputAsDefault ) {
-                       $value = $this->mDefault;
-               }
-
-               return [ $this->mName, $value, $params ];
-       }
-
-       public function getTableRow( $value ) {
-               list( $name, $value, $params ) = $this->getHiddenFieldData( $value );
-               $this->mParent->addHiddenField( $name, $value, $params );
-               return '';
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getDiv( $value ) {
-               return $this->getTableRow( $value );
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getRaw( $value ) {
-               return $this->getTableRow( $value );
-       }
-
-       public function getInputHTML( $value ) {
-               return '';
-       }
-
-       public function canDisplayErrors() {
-               return false;
-       }
-
-       public function hasVisibleOutput() {
-               return false;
-       }
-}
diff --git a/includes/htmlform/HTMLInfoField.php b/includes/htmlform/HTMLInfoField.php
deleted file mode 100644 (file)
index ada4fb6..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-/**
- * An information field (text blob), not a proper input.
- */
-class HTMLInfoField extends HTMLFormField {
-       public function __construct( $info ) {
-               $info['nodata'] = true;
-
-               parent::__construct( $info );
-       }
-
-       public function getInputHTML( $value ) {
-               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( [
-                       'label' => $value,
-               ] );
-       }
-
-       public function getTableRow( $value ) {
-               if ( !empty( $this->mParams['rawrow'] ) ) {
-                       return $value;
-               }
-
-               return parent::getTableRow( $value );
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getDiv( $value ) {
-               if ( !empty( $this->mParams['rawrow'] ) ) {
-                       return $value;
-               }
-
-               return parent::getDiv( $value );
-       }
-
-       /**
-        * @param string $value
-        * @return string
-        * @since 1.20
-        */
-       public function getRaw( $value ) {
-               if ( !empty( $this->mParams['rawrow'] ) ) {
-                       return $value;
-               }
-
-               return parent::getRaw( $value );
-       }
-
-       protected function needsLabel() {
-               return false;
-       }
-}
diff --git a/includes/htmlform/HTMLIntField.php b/includes/htmlform/HTMLIntField.php
deleted file mode 100644 (file)
index b0148d9..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?php
-
-/**
- * A field that must contain a number
- */
-class HTMLIntField extends HTMLFloatField {
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               # http://www.w3.org/TR/html5/infrastructure.html#signed-integers
-               # with the addition that a leading '+' sign is ok. Note that leading zeros
-               # are fine, and will be left in the input, which is useful for things like
-               # phone numbers when you know that they are integers (the HTML5 type=tel
-               # input does not require its value to be numeric).  If you want a tidier
-               # value to, eg, save in the DB, clean it up with intval().
-               if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) ) ) {
-                       return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
-               }
-
-               return true;
-       }
-}
diff --git a/includes/htmlform/HTMLMultiSelectField.php b/includes/htmlform/HTMLMultiSelectField.php
deleted file mode 100644 (file)
index a231b2f..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-<?php
-
-/**
- * Multi-select field
- */
-class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               if ( !is_array( $value ) ) {
-                       return false;
-               }
-
-               # If all options are valid, array_intersect of the valid options
-               # and the provided options will return the provided options.
-               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
-
-               $validValues = array_intersect( $value, $validOptions );
-               if ( count( $validValues ) == count( $value ) ) {
-                       return true;
-               } else {
-                       return $this->msg( 'htmlform-select-badoption' )->parse();
-               }
-       }
-
-       function getInputHTML( $value ) {
-               $value = HTMLFormField::forceToStringRecursive( $value );
-               $html = $this->formatOptions( $this->getOptions(), $value );
-
-               return $html;
-       }
-
-       function formatOptions( $options, $value ) {
-               $html = '';
-
-               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
-
-               foreach ( $options as $label => $info ) {
-                       if ( is_array( $info ) ) {
-                               $html .= Html::rawElement( 'h1', [], $label ) . "\n";
-                               $html .= $this->formatOptions( $info, $value );
-                       } else {
-                               $thisAttribs = [
-                                       'id' => "{$this->mID}-$info",
-                                       'value' => $info,
-                               ];
-                               $checked = in_array( $info, $value, true );
-
-                               $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs, $label );
-
-                               $html .= ' ' . Html::rawElement(
-                                       'div',
-                                       [ 'class' => 'mw-htmlform-flatlist-item' ],
-                                       $checkbox
-                               );
-                       }
-               }
-
-               return $html;
-       }
-
-       protected function getOneCheckbox( $checked, $attribs, $label ) {
-               if ( $this->mParent instanceof OOUIHTMLForm ) {
-                       throw new MWException( 'HTMLMultiSelectField#getOneCheckbox() is not supported' );
-               } else {
-                       $elementFunc = [ 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ];
-                       $checkbox =
-                               Xml::check( "{$this->mName}[]", $checked, $attribs ) .
-                               '&#160;' .
-                               call_user_func( $elementFunc,
-                                       'label',
-                                       [ 'for' => $attribs['id'] ],
-                                       $label
-                               );
-                       if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
-                               $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
-                                       $checkbox .
-                                       Html::closeElement( 'div' );
-                       }
-                       return $checkbox;
-               }
-       }
-
-       /**
-        * Get the OOUI version of this field.
-        *
-        * @since 1.28
-        * @param string[] $value
-        * @return OOUI\CheckboxMultiselectInputWidget
-        */
-       public function getInputOOUI( $value ) {
-               $attr = $this->getTooltipAndAccessKey();
-               $attr['id'] = $this->mID;
-               $attr['name'] = "{$this->mName}[]";
-
-               $attr['value'] = $value;
-               $attr['options'] = $this->getOptionsOOUI();
-
-               if ( $this->mOptionsLabelsNotFromMessage ) {
-                       foreach ( $attr['options'] as &$option ) {
-                               $option['label'] = new OOUI\HtmlSnippet( $option['label'] );
-                       }
-               }
-
-               $attr += OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
-               );
-
-               if ( $this->mClass !== '' ) {
-                       $attr['classes'] = [ $this->mClass ];
-               }
-
-               return new OOUI\CheckboxMultiselectInputWidget( $attr );
-       }
-
-       /**
-        * @param WebRequest $request
-        *
-        * @return string
-        */
-       function loadDataFromRequest( $request ) {
-               if ( $this->isSubmitAttempt( $request ) ) {
-                       // Checkboxes are just not added to the request arrays if they're not checked,
-                       // so it's perfectly possible for there not to be an entry at all
-                       return $request->getArray( $this->mName, [] );
-               } else {
-                       // That's ok, the user has not yet submitted the form, so show the defaults
-                       return $this->getDefault();
-               }
-       }
-
-       function getDefault() {
-               if ( isset( $this->mDefault ) ) {
-                       return $this->mDefault;
-               } else {
-                       return [];
-               }
-       }
-
-       function filterDataForSubmit( $data ) {
-               $data = HTMLFormField::forceToStringRecursive( $data );
-               $options = HTMLFormField::flattenOptions( $this->getOptions() );
-
-               $res = [];
-               foreach ( $options as $opt ) {
-                       $res["$opt"] = in_array( $opt, $data, true );
-               }
-
-               return $res;
-       }
-
-       protected function needsLabel() {
-               return false;
-       }
-}
diff --git a/includes/htmlform/HTMLRadioField.php b/includes/htmlform/HTMLRadioField.php
deleted file mode 100644 (file)
index e5b5e68..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-/**
- * Radio checkbox fields.
- */
-class HTMLRadioField extends HTMLFormField {
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               if ( !is_string( $value ) && !is_int( $value ) ) {
-                       return false;
-               }
-
-               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
-
-               if ( in_array( strval( $value ), $validOptions, true ) ) {
-                       return true;
-               } else {
-                       return $this->msg( 'htmlform-select-badoption' )->parse();
-               }
-       }
-
-       /**
-        * This returns a block of all the radio options, in one cell.
-        * @see includes/HTMLFormField#getInputHTML()
-        *
-        * @param string $value
-        *
-        * @return string
-        */
-       function getInputHTML( $value ) {
-               $html = $this->formatOptions( $this->getOptions(), strval( $value ) );
-
-               return $html;
-       }
-
-       function getInputOOUI( $value ) {
-               $options = [];
-               foreach ( $this->getOptions() as $label => $data ) {
-                       $options[] = [
-                               'data' => $data,
-                               'label' => $this->mOptionsLabelsNotFromMessage ? new OOUI\HtmlSnippet( $label ) : $label,
-                       ];
-               }
-
-               return new OOUI\RadioSelectInputWidget( [
-                       'name' => $this->mName,
-                       'id' => $this->mID,
-                       'value' => $value,
-                       'options' => $options,
-               ] + OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
-               ) );
-       }
-
-       function formatOptions( $options, $value ) {
-               global $wgUseMediaWikiUIEverywhere;
-
-               $html = '';
-
-               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
-               $elementFunc = [ 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ];
-
-               # @todo Should this produce an unordered list perhaps?
-               foreach ( $options as $label => $info ) {
-                       if ( is_array( $info ) ) {
-                               $html .= Html::rawElement( 'h1', [], $label ) . "\n";
-                               $html .= $this->formatOptions( $info, $value );
-                       } else {
-                               $id = Sanitizer::escapeId( $this->mID . "-$info" );
-                               $classes = [ 'mw-htmlform-flatlist-item' ];
-                               if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
-                                       $classes[] = 'mw-ui-radio';
-                               }
-                               $radio = Xml::radio( $this->mName, $info, $info === $value, $attribs + [ 'id' => $id ] );
-                               $radio .= '&#160;' . call_user_func( $elementFunc, 'label', [ 'for' => $id ], $label );
-
-                               $html .= ' ' . Html::rawElement(
-                                       'div',
-                                       [ 'class' => $classes ],
-                                       $radio
-                               );
-                       }
-               }
-
-               return $html;
-       }
-
-       protected function needsLabel() {
-               return false;
-       }
-}
diff --git a/includes/htmlform/HTMLSelectAndOtherField.php b/includes/htmlform/HTMLSelectAndOtherField.php
deleted file mode 100644 (file)
index e75c2b2..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-
-/**
- * Double field with a dropdown list constructed from a system message in the format
- *     * Optgroup header
- *     ** <option value>
- *     * New Optgroup header
- * Plus a text field underneath for an additional reason.  The 'value' of the field is
- * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
- * select dropdown.
- * @todo FIXME: If made 'required', only the text field should be compulsory.
- */
-class HTMLSelectAndOtherField extends HTMLSelectField {
-       function __construct( $params ) {
-               if ( array_key_exists( 'other', $params ) ) {
-                       // Do nothing
-               } elseif ( array_key_exists( 'other-message', $params ) ) {
-                       $params['other'] = $this->getMessage( $params['other-message'] )->plain();
-               } else {
-                       $params['other'] = $this->msg( 'htmlform-selectorother-other' )->plain();
-               }
-
-               parent::__construct( $params );
-
-               if ( $this->getOptions() === null ) {
-                       // Sulk
-                       throw new MWException( 'HTMLSelectAndOtherField called without any options' );
-               }
-               if ( !in_array( 'other', $this->mOptions, true ) ) {
-                       // Have 'other' always as first element
-                       $this->mOptions = [ $params['other'] => 'other' ] + $this->mOptions;
-               }
-               $this->mFlatOptions = self::flattenOptions( $this->getOptions() );
-
-       }
-
-       function getInputHTML( $value ) {
-               $select = parent::getInputHTML( $value[1] );
-
-               $textAttribs = [
-                       'id' => $this->mID . '-other',
-                       'size' => $this->getSize(),
-                       'class' => [ 'mw-htmlform-select-and-other-field' ],
-                       'data-id-select' => $this->mID,
-               ];
-
-               if ( $this->mClass !== '' ) {
-                       $textAttribs['class'][] = $this->mClass;
-               }
-
-               $allowedParams = [
-                       'required',
-                       'autofocus',
-                       'multiple',
-                       'disabled',
-                       'tabindex',
-                       'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js
-               ];
-
-               $textAttribs += $this->getAttributes( $allowedParams );
-
-               $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs );
-
-               return "$select<br />\n$textbox";
-       }
-
-       function getInputOOUI( $value ) {
-               return false;
-       }
-
-       /**
-        * @param WebRequest $request
-        *
-        * @return array("<overall message>","<select value>","<text field value>")
-        */
-       function loadDataFromRequest( $request ) {
-               if ( $request->getCheck( $this->mName ) ) {
-                       $list = $request->getText( $this->mName );
-                       $text = $request->getText( $this->mName . '-other' );
-
-                       // Should be built the same as in mediawiki.htmlform.js
-                       if ( $list == 'other' ) {
-                               $final = $text;
-                       } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
-                               # User has spoofed the select form to give an option which wasn't
-                               # in the original offer.  Sulk...
-                               $final = $text;
-                       } elseif ( $text == '' ) {
-                               $final = $list;
-                       } else {
-                               $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
-                       }
-               } else {
-                       $final = $this->getDefault();
-
-                       $list = 'other';
-                       $text = $final;
-                       foreach ( $this->mFlatOptions as $option ) {
-                               $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
-                               if ( strpos( $text, $match ) === 0 ) {
-                                       $list = $option;
-                                       $text = substr( $text, strlen( $match ) );
-                                       break;
-                               }
-                       }
-               }
-
-               return [ $final, $list, $text ];
-       }
-
-       function getSize() {
-               return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
-       }
-
-       function validate( $value, $alldata ) {
-               # HTMLSelectField forces $value to be one of the options in the select
-               # field, which is not useful here.  But we do want the validation further up
-               # the chain
-               $p = parent::validate( $value[1], $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               if ( isset( $this->mParams['required'] )
-                       && $this->mParams['required'] !== false
-                       && $value[1] === ''
-               ) {
-                       return $this->msg( 'htmlform-required' )->parse();
-               }
-
-               return true;
-       }
-}
diff --git a/includes/htmlform/HTMLSelectField.php b/includes/htmlform/HTMLSelectField.php
deleted file mode 100644 (file)
index b6ad46c..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-
-/**
- * A select dropdown field.  Basically a wrapper for Xmlselect class
- */
-class HTMLSelectField extends HTMLFormField {
-       function validate( $value, $alldata ) {
-               $p = parent::validate( $value, $alldata );
-
-               if ( $p !== true ) {
-                       return $p;
-               }
-
-               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
-
-               if ( in_array( strval( $value ), $validOptions, true ) ) {
-                       return true;
-               } else {
-                       return $this->msg( 'htmlform-select-badoption' )->parse();
-               }
-       }
-
-       function getInputHTML( $value ) {
-               $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
-
-               if ( !empty( $this->mParams['disabled'] ) ) {
-                       $select->setAttribute( 'disabled', 'disabled' );
-               }
-
-               $allowedParams = [ 'tabindex', 'size' ];
-               $customParams = $this->getAttributes( $allowedParams );
-               foreach ( $customParams as $name => $value ) {
-                       $select->setAttribute( $name, $value );
-               }
-
-               if ( $this->mClass !== '' ) {
-                       $select->setAttribute( 'class', $this->mClass );
-               }
-
-               $select->addOptions( $this->getOptions() );
-
-               return $select->getHTML();
-       }
-
-       function getInputOOUI( $value ) {
-               $disabled = false;
-               $allowedParams = [ 'tabindex' ];
-               $attribs = OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( $allowedParams )
-               );
-
-               if ( $this->mClass !== '' ) {
-                       $attribs['classes'] = [ $this->mClass ];
-               }
-
-               if ( !empty( $this->mParams['disabled'] ) ) {
-                       $disabled = true;
-               }
-
-               return new OOUI\DropdownInputWidget( [
-                       'name' => $this->mName,
-                       'id' => $this->mID,
-                       'options' => $this->getOptionsOOUI(),
-                       'value' => strval( $value ),
-                       'disabled' => $disabled,
-               ] + $attribs );
-       }
-}
diff --git a/includes/htmlform/HTMLSelectLimitField.php b/includes/htmlform/HTMLSelectLimitField.php
deleted file mode 100644 (file)
index e7f1c04..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * A limit dropdown, which accepts any valid number
- */
-class HTMLSelectLimitField extends HTMLSelectField {
-       /**
-        * Basically don't do any validation. If it's a number that's fine. Also,
-        * add it to the list if it's not there already
-        *
-        * @param string $value
-        * @param array $alldata
-        * @return bool
-        */
-       function validate( $value, $alldata ) {
-               if ( $value == '' ) {
-                       return true;
-               }
-
-               // Let folks pick an explicit limit not from our list, as long as it's a real numbr.
-               if ( !in_array( $value, $this->mParams['options'] )
-                       && $value == intval( $value )
-                       && $value > 0
-               ) {
-                       // This adds the explicitly requested limit value to the drop-down,
-                       // then makes sure it's sorted correctly so when we output the list
-                       // later, the custom option doesn't just show up last.
-                       $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] =
-                               intval( $value );
-                       asort( $this->mParams['options'] );
-               }
-
-               return true;
-       }
-}
diff --git a/includes/htmlform/HTMLSelectNamespace.php b/includes/htmlform/HTMLSelectNamespace.php
deleted file mode 100644 (file)
index ef21969..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-/**
- * Wrapper for Html::namespaceSelector to use in HTMLForm
- */
-class HTMLSelectNamespace extends HTMLFormField {
-       public function __construct( $params ) {
-               parent::__construct( $params );
-
-               $this->mAllValue = array_key_exists( 'all', $params )
-                       ? $params['all']
-                       : 'all';
-
-       }
-
-       function getInputHTML( $value ) {
-               return Html::namespaceSelector(
-                       [
-                               'selected' => $value,
-                               'all' => $this->mAllValue
-                       ], [
-                               'name' => $this->mName,
-                               'id' => $this->mID,
-                               'class' => 'namespaceselector',
-                       ]
-               );
-       }
-
-       public function getInputOOUI( $value ) {
-               return new MediaWiki\Widget\NamespaceInputWidget( [
-                       'value' => $value,
-                       'name' => $this->mName,
-                       'id' => $this->mID,
-                       'includeAllValue' => $this->mAllValue,
-               ] );
-       }
-}
diff --git a/includes/htmlform/HTMLSelectNamespaceWithButton.php b/includes/htmlform/HTMLSelectNamespaceWithButton.php
deleted file mode 100644 (file)
index 5225983..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-/**
- * Creates a Html::namespaceSelector input field with a button assigned to the input field.
- */
-class HTMLSelectNamespaceWithButton extends HTMLSelectNamespace {
-       /** @var HTMLFormFieldWithButton $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
deleted file mode 100644 (file)
index 8f7750c..0000000
+++ /dev/null
@@ -1,90 +0,0 @@
-<?php
-
-/**
- * Select dropdown field, with an additional "other" textbox.
- *
- * HTMLComboboxField implements the same functionality using a single form field
- * and should be used instead.
- */
-class HTMLSelectOrOtherField extends HTMLTextField {
-       function __construct( $params ) {
-               parent::__construct( $params );
-               $this->getOptions();
-               if ( !in_array( 'other', $this->mOptions, true ) ) {
-                       $msg =
-                               isset( $params['other'] )
-                                       ? $params['other']
-                                       : wfMessage( 'htmlform-selectorother-other' )->text();
-                       // Have 'other' always as first element
-                       $this->mOptions = [ $msg => 'other' ] + $this->mOptions;
-               }
-
-       }
-
-       function getInputHTML( $value ) {
-               $valInSelect = false;
-
-               if ( $value !== false ) {
-                       $value = strval( $value );
-                       $valInSelect = in_array(
-                               $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
-                       );
-               }
-
-               $selected = $valInSelect ? $value : 'other';
-
-               $select = new XmlSelect( $this->mName, $this->mID, $selected );
-               $select->addOptions( $this->getOptions() );
-
-               $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
-
-               $tbAttribs = [ 'id' => $this->mID . '-other', 'size' => $this->getSize() ];
-
-               if ( !empty( $this->mParams['disabled'] ) ) {
-                       $select->setAttribute( 'disabled', 'disabled' );
-                       $tbAttribs['disabled'] = 'disabled';
-               }
-
-               if ( isset( $this->mParams['tabindex'] ) ) {
-                       $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
-                       $tbAttribs['tabindex'] = $this->mParams['tabindex'];
-               }
-
-               $select = $select->getHTML();
-
-               if ( isset( $this->mParams['maxlength'] ) ) {
-                       $tbAttribs['maxlength'] = $this->mParams['maxlength'];
-               }
-
-               if ( $this->mClass !== '' ) {
-                       $tbAttribs['class'] = $this->mClass;
-               }
-
-               $textbox = Html::input( $this->mName . '-other', $valInSelect ? '' : $value, 'text', $tbAttribs );
-
-               return "$select<br />\n$textbox";
-       }
-
-       function getInputOOUI( $value ) {
-               return false;
-       }
-
-       /**
-        * @param WebRequest $request
-        *
-        * @return string
-        */
-       function loadDataFromRequest( $request ) {
-               if ( $request->getCheck( $this->mName ) ) {
-                       $val = $request->getText( $this->mName );
-
-                       if ( $val === 'other' ) {
-                               $val = $request->getText( $this->mName . '-other' );
-                       }
-
-                       return $val;
-               } else {
-                       return $this->getDefault();
-               }
-       }
-}
diff --git a/includes/htmlform/HTMLSubmitField.php b/includes/htmlform/HTMLSubmitField.php
deleted file mode 100644 (file)
index cb98549..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-<?php
-
-/**
- * Add a submit button inline in the form (as opposed to
- * HTMLForm::addButton(), which will add it at the end).
- */
-class HTMLSubmitField extends HTMLButtonField {
-       protected $buttonType = 'submit';
-
-       protected $mFlags = [ 'primary', 'constructive' ];
-
-       public function skipLoadData( $request ) {
-               return !$request->getCheck( $this->mName );
-       }
-
-       public function loadDataFromRequest( $request ) {
-               return $request->getCheck( $this->mName );
-       }
-}
diff --git a/includes/htmlform/HTMLTagFilter.php b/includes/htmlform/HTMLTagFilter.php
deleted file mode 100644 (file)
index 8075de5..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-<?php
-/**
- * Wrapper for ChangeTags::buildTagFilterSelector to use in HTMLForm
- */
-class HTMLTagFilter extends HTMLFormField {
-       protected $tagFilter;
-
-       function getTableRow( $value ) {
-               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
-               if ( $this->tagFilter ) {
-                       return parent::getTableRow( $value );
-               }
-               return '';
-       }
-
-       function getDiv( $value ) {
-               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
-               if ( $this->tagFilter ) {
-                       return parent::getDiv( $value );
-               }
-               return '';
-       }
-
-       function getInputHTML( $value ) {
-               if ( $this->tagFilter ) {
-                       // we only need the select field, HTMLForm should handle the label
-                       return $this->tagFilter[1];
-               }
-               return '';
-       }
-}
diff --git a/includes/htmlform/HTMLTextAreaField.php b/includes/htmlform/HTMLTextAreaField.php
deleted file mode 100644 (file)
index 8ffff43..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-
-class HTMLTextAreaField extends HTMLFormField {
-       const DEFAULT_COLS = 80;
-       const DEFAULT_ROWS = 25;
-
-       protected $mPlaceholder = '';
-
-       /**
-        * @param array $params
-        *   - cols, rows: textarea size
-        *   - placeholder/placeholder-message: set HTML placeholder attribute
-        *   - spellcheck: set HTML spellcheck attribute
-        */
-       public function __construct( $params ) {
-               parent::__construct( $params );
-
-               if ( isset( $params['placeholder-message'] ) ) {
-                       $this->mPlaceholder = $this->getMessage( $params['placeholder-message'] )->parse();
-               } elseif ( isset( $params['placeholder'] ) ) {
-                       $this->mPlaceholder = $params['placeholder'];
-               }
-       }
-
-       function getCols() {
-               return isset( $this->mParams['cols'] ) ? $this->mParams['cols'] : static::DEFAULT_COLS;
-       }
-
-       function getRows() {
-               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 = [
-                               'id' => $this->mID,
-                               'cols' => $this->getCols(),
-                               'rows' => $this->getRows(),
-                               'spellcheck' => $this->getSpellCheck(),
-                       ] + $this->getTooltipAndAccessKey();
-
-               if ( $this->mClass !== '' ) {
-                       $attribs['class'] = $this->mClass;
-               }
-               if ( $this->mPlaceholder !== '' ) {
-                       $attribs['placeholder'] = $this->mPlaceholder;
-               }
-
-               $allowedParams = [
-                       'tabindex',
-                       'disabled',
-                       'readonly',
-                       'required',
-                       'autofocus'
-               ];
-
-               $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'] = [ $this->mClass ];
-               }
-               if ( $this->mPlaceholder !== '' ) {
-                       $attribs['placeholder'] = $this->mPlaceholder;
-               }
-
-               $allowedParams = [
-                       'tabindex',
-                       'disabled',
-                       'readonly',
-                       'required',
-                       'autofocus',
-               ];
-
-               $attribs += OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( $allowedParams )
-               );
-
-               return new OOUI\TextInputWidget( [
-                       '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
deleted file mode 100644 (file)
index 3ab7176..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-<?php
-
-class HTMLTextField extends HTMLFormField {
-       protected $mPlaceholder = '';
-
-       /**
-        * @param array $params
-        *   - type: HTML textfield type
-        *   - size: field size in characters (defaults to 45)
-        *   - placeholder/placeholder-message: set HTML placeholder attribute
-        *   - spellcheck: set HTML spellcheck attribute
-        *   - persistent: upon unsuccessful requests, retain the value (defaults to true, except
-        *     for password fields)
-        */
-       public function __construct( $params ) {
-               parent::__construct( $params );
-
-               if ( isset( $params['placeholder-message'] ) ) {
-                       $this->mPlaceholder = $this->getMessage( $params['placeholder-message'] )->parse();
-               } elseif ( isset( $params['placeholder'] ) ) {
-                       $this->mPlaceholder = $params['placeholder'];
-               }
-       }
-
-       function getSize() {
-               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;
-       }
-
-       public function isPersistent() {
-               if ( isset( $this->mParams['persistent'] ) ) {
-                       return $this->mParams['persistent'];
-               }
-               // don't put passwords into the HTML body, they could get cached or otherwise leaked
-               return !( isset( $this->mParams['type'] ) && $this->mParams['type'] === 'password' );
-       }
-
-       function getInputHTML( $value ) {
-               if ( !$this->isPersistent() ) {
-                       $value = '';
-               }
-
-               $attribs = [
-                               'id' => $this->mID,
-                               'name' => $this->mName,
-                               'size' => $this->getSize(),
-                               'value' => $value,
-                               'dir' => $this->mDir,
-                               'spellcheck' => $this->getSpellCheck(),
-                       ] + $this->getTooltipAndAccessKey() + $this->getDataAttribs();
-
-               if ( $this->mClass !== '' ) {
-                       $attribs['class'] = $this->mClass;
-               }
-               if ( $this->mPlaceholder !== '' ) {
-                       $attribs['placeholder'] = $this->mPlaceholder;
-               }
-
-               # @todo Enforce pattern, step, required, readonly on the server side as
-               # well
-               $allowedParams = [
-                       'type',
-                       'min',
-                       'max',
-                       'pattern',
-                       'title',
-                       'step',
-                       'list',
-                       'maxlength',
-                       'tabindex',
-                       'disabled',
-                       'required',
-                       'autofocus',
-                       'multiple',
-                       'readonly'
-               ];
-
-               $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'] );
-
-               # Implement tiny differences between some field variants
-               # here, rather than creating a new class for each one which
-               # is essentially just a clone of this one.
-               if ( isset( $this->mParams['type'] ) ) {
-                       switch ( $this->mParams['type'] ) {
-                               case 'int':
-                                       $type = 'number';
-                                       break;
-                               case 'float':
-                                       $type = 'number';
-                                       $attribs['step'] = 'any';
-                                       break;
-                               # Pass through
-                               case 'email':
-                               case 'password':
-                               case 'file':
-                               case 'url':
-                                       $type = $this->mParams['type'];
-                                       break;
-                       }
-               }
-
-               return $type;
-       }
-
-       function getInputOOUI( $value ) {
-               if ( !$this->isPersistent() ) {
-                       $value = '';
-               }
-
-               $attribs = $this->getTooltipAndAccessKey();
-
-               if ( $this->mClass !== '' ) {
-                       $attribs['classes'] = [ $this->mClass ];
-               }
-               if ( $this->mPlaceholder !== '' ) {
-                       $attribs['placeholder'] = $this->mPlaceholder;
-               }
-
-               # @todo Enforce pattern, step, required, readonly on the server side as
-               # well
-               $allowedParams = [
-                       'autofocus',
-                       'autosize',
-                       'disabled',
-                       'flags',
-                       'indicator',
-                       'maxlength',
-                       'readonly',
-                       'required',
-                       'tabindex',
-                       'type',
-               ];
-
-               $attribs += OOUI\Element::configFromHtmlAttributes(
-                       $this->getAttributes( $allowedParams )
-               );
-
-               $type = $this->getType( $attribs );
-
-               return $this->getInputWidget( [
-                       'id' => $this->mID,
-                       'name' => $this->mName,
-                       'value' => $value,
-                       'type' => $type,
-               ] + $attribs );
-       }
-
-       protected function getInputWidget( $params ) {
-               return new OOUI\TextInputWidget( $params );
-       }
-
-       /**
-        * Returns an array of data-* attributes to add to the field.
-        *
-        * @return array
-        */
-       protected function getDataAttribs() {
-               return [];
-       }
-}
diff --git a/includes/htmlform/HTMLTextFieldWithButton.php b/includes/htmlform/HTMLTextFieldWithButton.php
deleted file mode 100644 (file)
index 7c1c673..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-<?php
-/**
- * Creates a text input field with a button assigned to the input field.
- */
-class HTMLTextFieldWithButton extends HTMLTextField {
-       /** @var HTMLFormFieldWithButton $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
deleted file mode 100644 (file)
index fcf721a..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?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 += [
-                       '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 );
-       }
-
-       public function getInputHtml( $value ) {
-               // add mw-searchInput class to enable search suggestions for non-OOUI, too
-               $this->mClass .= 'mw-searchInput';
-
-               // return the HTMLTextField html
-               return parent::getInputHTML( $value );
-       }
-
-       protected function getDataAttribs() {
-               return [
-                       'data-mw-searchsuggest' => FormatJson::encode( [
-                               'wrapAsLink' => false,
-                       ] ),
-               ];
-       }
-}
diff --git a/includes/htmlform/HTMLUserTextField.php b/includes/htmlform/HTMLUserTextField.php
deleted file mode 100644 (file)
index 5a7e0b9..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-<?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 += [
-                       '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 );
-       }
-
-       public function getInputHtml( $value ) {
-               // add the required module and css class for user suggestions in non-OOUI mode
-               $this->mParent->getOutput()->addModules( 'mediawiki.userSuggest' );
-               $this->mClass .= ' mw-autocomplete-user';
-
-               // return parent html
-               return parent::getInputHTML( $value );
-       }
-}
diff --git a/includes/htmlform/fields/HTMLApiField.php b/includes/htmlform/fields/HTMLApiField.php
new file mode 100644 (file)
index 0000000..24a253e
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+
+class HTMLApiField extends HTMLFormField {
+       public function getTableRow( $value ) {
+               return '';
+       }
+
+       public function getDiv( $value ) {
+               return $this->getTableRow( $value );
+       }
+
+       public function getRaw( $value ) {
+               return $this->getTableRow( $value );
+       }
+
+       public function getInputHTML( $value ) {
+               return '';
+       }
+
+       public function hasVisibleOutput() {
+               return false;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLAutoCompleteSelectField.php b/includes/htmlform/fields/HTMLAutoCompleteSelectField.php
new file mode 100644 (file)
index 0000000..76a88d5
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+
+/**
+ * Text field for selecting a value from a large list of possible values, with
+ * auto-completion and optionally with a select dropdown for selecting common
+ * options.
+ *
+ * HTMLComboboxField implements most of the same functionality and should be
+ * used instead, if possible.
+ *
+ * If one of 'options-messages', 'options', or 'options-message' is provided
+ * and non-empty, the select dropdown will be shown. An 'other' key will be
+ * appended using message 'htmlform-selectorother-other' if not already
+ * present.
+ *
+ * Besides the parameters recognized by HTMLTextField, the following are
+ * recognized:
+ *   options-messages - As for HTMLSelectField
+ *   options - As for HTMLSelectField
+ *   options-message - As for HTMLSelectField
+ *   autocomplete - Associative array mapping display text to values.
+ *   autocomplete-messages - Like autocomplete, but keys are message names.
+ *   require-match - Boolean, if true the value must be in the options or the
+ *     autocomplete.
+ *   other-message - Message to use instead of htmlform-selectorother-other for
+ *      the 'other' message.
+ *   other - Raw text to use for the 'other' message
+ */
+class HTMLAutoCompleteSelectField extends HTMLTextField {
+       protected $autocomplete = [];
+
+       function __construct( $params ) {
+               $params += [
+                       'require-match' => false,
+               ];
+
+               parent::__construct( $params );
+
+               if ( array_key_exists( 'autocomplete-messages', $this->mParams ) ) {
+                       foreach ( $this->mParams['autocomplete-messages'] as $key => $value ) {
+                               $key = $this->msg( $key )->plain();
+                               $this->autocomplete[$key] = strval( $value );
+                       }
+               } elseif ( array_key_exists( 'autocomplete', $this->mParams ) ) {
+                       foreach ( $this->mParams['autocomplete'] as $key => $value ) {
+                               $this->autocomplete[$key] = strval( $value );
+                       }
+               }
+               if ( !is_array( $this->autocomplete ) || !$this->autocomplete ) {
+                       throw new MWException( 'HTMLAutoCompleteSelectField called without any autocompletions' );
+               }
+
+               $this->getOptions();
+               if ( $this->mOptions && !in_array( 'other', $this->mOptions, true ) ) {
+                       if ( isset( $params['other-message'] ) ) {
+                               $msg = $this->getMessage( $params['other-message'] )->text();
+                       } elseif ( isset( $params['other'] ) ) {
+                               $msg = $params['other'];
+                       } else {
+                               $msg = wfMessage( 'htmlform-selectorother-other' )->text();
+                       }
+                       $this->mOptions[$msg] = 'other';
+               }
+       }
+
+       function loadDataFromRequest( $request ) {
+               if ( $request->getCheck( $this->mName ) ) {
+                       $val = $request->getText( $this->mName . '-select', 'other' );
+
+                       if ( $val === 'other' ) {
+                               $val = $request->getText( $this->mName );
+                               if ( isset( $this->autocomplete[$val] ) ) {
+                                       $val = $this->autocomplete[$val];
+                               }
+                       }
+
+                       return $val;
+               } else {
+                       return $this->getDefault();
+               }
+       }
+
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+               if ( in_array( strval( $value ), $validOptions, true ) ) {
+                       return true;
+               } elseif ( in_array( strval( $value ), $this->autocomplete, true ) ) {
+                       return true;
+               } elseif ( $this->mParams['require-match'] ) {
+                       return $this->msg( 'htmlform-select-badoption' )->parse();
+               }
+
+               return true;
+       }
+
+       // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
+       public function getAttributes( array $list ) {
+               $attribs = [
+                       'type' => 'text',
+                       'data-autocomplete' => FormatJson::encode( array_keys( $this->autocomplete ) ),
+               ] + parent::getAttributes( $list );
+
+               if ( $this->getOptions() ) {
+                       $attribs['data-hide-if'] = FormatJson::encode(
+                               [ '!==', $this->mName . '-select', 'other' ]
+                       );
+               }
+
+               return $attribs;
+       }
+
+       function getInputHTML( $value ) {
+               $oldClass = $this->mClass;
+               $this->mClass = (array)$this->mClass;
+
+               $valInSelect = false;
+               $ret = '';
+
+               if ( $this->getOptions() ) {
+                       if ( $value !== false ) {
+                               $value = strval( $value );
+                               $valInSelect = in_array(
+                                       $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
+                               );
+                       }
+
+                       $selected = $valInSelect ? $value : 'other';
+                       $select = new XmlSelect( $this->mName . '-select', $this->mID . '-select', $selected );
+                       $select->addOptions( $this->getOptions() );
+                       $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
+
+                       if ( !empty( $this->mParams['disabled'] ) ) {
+                               $select->setAttribute( 'disabled', 'disabled' );
+                       }
+
+                       if ( isset( $this->mParams['tabindex'] ) ) {
+                               $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
+                       }
+
+                       $ret = $select->getHTML() . "<br />\n";
+
+                       $this->mClass[] = 'mw-htmlform-hide-if';
+               }
+
+               if ( $valInSelect ) {
+                       $value = '';
+               } else {
+                       $key = array_search( strval( $value ), $this->autocomplete, true );
+                       if ( $key !== false ) {
+                               $value = $key;
+                       }
+               }
+
+               $this->mClass[] = 'mw-htmlform-autocomplete';
+               $ret .= parent::getInputHTML( $valInSelect ? '' : $value );
+               $this->mClass = $oldClass;
+
+               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/fields/HTMLButtonField.php b/includes/htmlform/fields/HTMLButtonField.php
new file mode 100644 (file)
index 0000000..64fe7ed
--- /dev/null
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * Adds a generic button inline to the form. Does not do anything, you must add
+ * click handling code in JavaScript. Use a HTMLSubmitField if you merely
+ * wish to add a submit button to a form.
+ *
+ * Additional recognized configuration parameters include:
+ * - flags: OOUI flags for the button, see OOUI\FlaggedElement
+ * - buttonlabel-message: Message to use for the button display text, instead
+ *   of the value from 'default'. Overrides 'buttonlabel' and 'buttonlabel-raw'.
+ * - buttonlabel: Text to display for the button display text, instead
+ *   of the value from 'default'. Overrides 'buttonlabel-raw'.
+ * - buttonlabel-raw: HTMLto display for the button display text, instead
+ *   of the value from 'default'.
+ *
+ * Note that the buttonlabel parameters are not supported on IE6 and IE7 due to
+ * bugs in those browsers. If detected, they will be served buttons using the
+ * value of 'default' as the button label.
+ *
+ * @since 1.22
+ */
+class HTMLButtonField extends HTMLFormField {
+       protected $buttonType = 'button';
+       protected $buttonLabel = null;
+
+       /** @var array $mFlags Flags to add to OOUI Button widget */
+       protected $mFlags = [];
+
+       public function __construct( $info ) {
+               $info['nodata'] = true;
+               if ( isset( $info['flags'] ) ) {
+                       $this->mFlags = $info['flags'];
+               }
+
+               # Generate the label from a message, if possible
+               if ( isset( $info['buttonlabel-message'] ) ) {
+                       $this->buttonLabel = $this->getMessage( $info['buttonlabel-message'] )->parse();
+               } elseif ( isset( $info['buttonlabel'] ) ) {
+                       if ( $info['buttonlabel'] === '&#160;' ) {
+                               // Apparently some things set &nbsp directly and in an odd format
+                               $this->buttonLabel = '&#160;';
+                       } else {
+                               $this->buttonLabel = htmlspecialchars( $info['buttonlabel'] );
+                       }
+               } elseif ( isset( $info['buttonlabel-raw'] ) ) {
+                       $this->buttonLabel = $info['buttonlabel-raw'];
+               }
+
+               $this->setShowEmptyLabel( false );
+
+               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 = [
+                       'class' => 'mw-htmlform-submit ' . $this->mClass . $flags,
+                       'id' => $this->mID,
+                       'type' => $this->buttonType,
+                       'name' => $this->mName,
+                       'value' => $this->getDefault(),
+               ] + $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               if ( $this->isBadIE() ) {
+                       return Html::element( 'input', $attr );
+               } else {
+                       return Html::rawElement( 'button', $attr,
+                               $this->buttonLabel ?: htmlspecialchars( $this->getDefault() ) );
+               }
+       }
+
+       /**
+        * Get the OOUI widget for this field.
+        * @param string $value
+        * @return OOUI\ButtonInputWidget
+        */
+       public function getInputOOUI( $value ) {
+               return new OOUI\ButtonInputWidget( [
+                       'name' => $this->mName,
+                       'value' => $this->getDefault(),
+                       'label' => !$this->isBadIE() && $this->buttonLabel
+                               ? new OOUI\HtmlSnippet( $this->buttonLabel )
+                               : $this->getDefault(),
+                       'type' => $this->buttonType,
+                       'classes' => [ 'mw-htmlform-submit', $this->mClass ],
+                       'id' => $this->mID,
+                       'flags' => $this->mFlags,
+                       'useInputTag' => $this->isBadIE(),
+               ] + OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
+               ) );
+       }
+
+       protected function needsLabel() {
+               return false;
+       }
+
+       /**
+        * Button cannot be invalid
+        *
+        * @param string $value
+        * @param array $alldata
+        *
+        * @return bool
+        */
+       public function validate( $value, $alldata ) {
+               return true;
+       }
+
+       /**
+        * IE<8 has bugs with <button>, so we'll need to avoid them.
+        * @return bool Whether the request is from a bad version of IE
+        */
+       private function isBadIE() {
+               $request = $this->mParent
+                       ? $this->mParent->getRequest()
+                       : RequestContext::getMain()->getRequest();
+               return preg_match( '/MSIE [1-7]\./i', $request->getHeader( 'User-Agent' ) );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLCheckField.php b/includes/htmlform/fields/HTMLCheckField.php
new file mode 100644 (file)
index 0000000..a553839
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * A checkbox field
+ */
+class HTMLCheckField extends HTMLFormField {
+       function getInputHTML( $value ) {
+               global $wgUseMediaWikiUIEverywhere;
+
+               if ( !empty( $this->mParams['invert'] ) ) {
+                       $value = !$value;
+               }
+
+               $attr = $this->getTooltipAndAccessKey();
+               $attr['id'] = $this->mID;
+
+               $attr += $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               if ( $this->mClass !== '' ) {
+                       $attr['class'] = $this->mClass;
+               }
+
+               $attrLabel = [ 'for' => $this->mID ];
+               if ( isset( $attr['title'] ) ) {
+                       // propagate tooltip to label
+                       $attrLabel['title'] = $attr['title'];
+               }
+
+               $chkLabel = Xml::check( $this->mName, $value, $attr ) .
+                       '&#160;' .
+                       Html::rawElement( 'label', $attrLabel, $this->mLabel );
+
+               if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
+                       $chkLabel = Html::rawElement(
+                               'div',
+                               [ 'class' => 'mw-ui-checkbox' ],
+                               $chkLabel
+                       );
+               }
+
+               return $chkLabel;
+       }
+
+       /**
+        * 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 += OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
+               );
+
+               if ( $this->mClass !== '' ) {
+                       $attr['classes'] = [ $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() {
+               if ( $this->mParent instanceof OOUIHTMLForm ) {
+                       return $this->mLabel;
+               } elseif (
+                       $this->mParent instanceof HTMLForm &&
+                       $this->mParent->getDisplayFormat() === 'div'
+               ) {
+                       return '';
+               } else {
+                       return '&#160;';
+               }
+       }
+
+       /**
+        * Get label alignment when generating field for OOUI.
+        * @return string 'left', 'right', 'top' or 'inline'
+        */
+       protected function getLabelAlignOOUI() {
+               return 'inline';
+       }
+
+       /**
+        * checkboxes don't need a label.
+        * @return bool
+        */
+       protected function needsLabel() {
+               return false;
+       }
+
+       /**
+        * @param WebRequest $request
+        *
+        * @return bool
+        */
+       function loadDataFromRequest( $request ) {
+               $invert = isset( $this->mParams['invert'] ) && $this->mParams['invert'];
+
+               // GetCheck won't work like we want for checks.
+               // Fetch the value in either one of the two following case:
+               // - we have a valid submit attempt (form was just submitted, or a GET URL forged by the user)
+               // - checkbox name has a value (false or true), ie is not null
+               if ( $this->isSubmitAttempt( $request ) || $request->getVal( $this->mName ) !== null ) {
+                       return $invert
+                               ? !$request->getBool( $this->mName )
+                               : $request->getBool( $this->mName );
+               } else {
+                       return (bool)$this->getDefault();
+               }
+       }
+}
diff --git a/includes/htmlform/fields/HTMLCheckMatrix.php b/includes/htmlform/fields/HTMLCheckMatrix.php
new file mode 100644 (file)
index 0000000..b324fb6
--- /dev/null
@@ -0,0 +1,266 @@
+<?php
+
+/**
+ * A checkbox matrix
+ * Operates similarly to HTMLMultiSelectField, but instead of using an array of
+ * options, uses an array of rows and an array of columns to dynamically
+ * construct a matrix of options. The tags used to identify a particular cell
+ * are of the form "columnName-rowName"
+ *
+ * Options:
+ *   - columns
+ *     - Required list of columns in the matrix.
+ *   - rows
+ *     - Required list of rows in the matrix.
+ *   - force-options-on
+ *     - Accepts array of column-row tags to be displayed as enabled but unavailable to change
+ *   - force-options-off
+ *     - Accepts array of column-row tags to be displayed as disabled but unavailable to change.
+ *   - tooltips
+ *     - Optional array mapping row label to tooltip content
+ *   - tooltip-class
+ *     - Optional CSS class used on tooltip container span. Defaults to mw-icon-question.
+ */
+class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
+       static private $requiredParams = [
+               // Required by underlying HTMLFormField
+               'fieldname',
+               // Required by HTMLCheckMatrix
+               'rows',
+               'columns'
+       ];
+
+       public function __construct( $params ) {
+               $missing = array_diff( self::$requiredParams, array_keys( $params ) );
+               if ( $missing ) {
+                       throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
+               }
+               parent::__construct( $params );
+       }
+
+       function validate( $value, $alldata ) {
+               $rows = $this->mParams['rows'];
+               $columns = $this->mParams['columns'];
+
+               // Make sure user-defined validation callback is run
+               $p = parent::validate( $value, $alldata );
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               // Make sure submitted value is an array
+               if ( !is_array( $value ) ) {
+                       return false;
+               }
+
+               // If all options are valid, array_intersect of the valid options
+               // and the provided options will return the provided options.
+               $validOptions = [];
+               foreach ( $rows as $rowTag ) {
+                       foreach ( $columns as $columnTag ) {
+                               $validOptions[] = $columnTag . '-' . $rowTag;
+                       }
+               }
+               $validValues = array_intersect( $value, $validOptions );
+               if ( count( $validValues ) == count( $value ) ) {
+                       return true;
+               } else {
+                       return $this->msg( 'htmlform-select-badoption' )->parse();
+               }
+       }
+
+       /**
+        * Build a table containing a matrix of checkbox options.
+        * The value of each option is a combination of the row tag and column tag.
+        * mParams['rows'] is an array with row labels as keys and row tags as values.
+        * mParams['columns'] is an array with column labels as keys and column tags as values.
+        *
+        * @param array $value Array of the options that should be checked
+        *
+        * @return string
+        */
+       function getInputHTML( $value ) {
+               $html = '';
+               $tableContents = '';
+               $rows = $this->mParams['rows'];
+               $columns = $this->mParams['columns'];
+
+               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               // Build the column headers
+               $headerContents = Html::rawElement( 'td', [], '&#160;' );
+               foreach ( $columns as $columnLabel => $columnTag ) {
+                       $headerContents .= Html::rawElement( 'td', [], $columnLabel );
+               }
+               $tableContents .= Html::rawElement( 'tr', [], "\n$headerContents\n" );
+
+               $tooltipClass = 'mw-icon-question';
+               if ( isset( $this->mParams['tooltip-class'] ) ) {
+                       $tooltipClass = $this->mParams['tooltip-class'];
+               }
+
+               // Build the options matrix
+               foreach ( $rows as $rowLabel => $rowTag ) {
+                       // Append tooltip if configured
+                       if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
+                               $tooltipAttribs = [
+                                       'class' => "mw-htmlform-tooltip $tooltipClass",
+                                       'title' => $this->mParams['tooltips'][$rowLabel],
+                               ];
+                               $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
+                       }
+                       $rowContents = Html::rawElement( 'td', [], $rowLabel );
+                       foreach ( $columns as $columnTag ) {
+                               $thisTag = "$columnTag-$rowTag";
+                               // Construct the checkbox
+                               $thisAttribs = [
+                                       'id' => "{$this->mID}-$thisTag",
+                                       'value' => $thisTag,
+                               ];
+                               $checked = in_array( $thisTag, (array)$value, true );
+                               if ( $this->isTagForcedOff( $thisTag ) ) {
+                                       $checked = false;
+                                       $thisAttribs['disabled'] = 1;
+                               } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+                                       $checked = true;
+                                       $thisAttribs['disabled'] = 1;
+                               }
+
+                               $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs );
+
+                               $rowContents .= Html::rawElement(
+                                       'td',
+                                       [],
+                                       $checkbox
+                               );
+                       }
+                       $tableContents .= Html::rawElement( 'tr', [], "\n$rowContents\n" );
+               }
+
+               // Put it all in a table
+               $html .= Html::rawElement( 'table',
+                               [ 'class' => 'mw-htmlform-matrix' ],
+                               Html::rawElement( 'tbody', [], "\n$tableContents\n" ) ) . "\n";
+
+               return $html;
+       }
+
+       protected function getOneCheckbox( $checked, $attribs ) {
+               if ( $this->mParent instanceof OOUIHTMLForm ) {
+                       return new OOUI\CheckboxInputWidget( [
+                               'name' => "{$this->mName}[]",
+                               'selected' => $checked,
+                       ] + OOUI\Element::configFromHtmlAttributes(
+                               $attribs
+                       ) );
+               } else {
+                       $checkbox = Xml::check( "{$this->mName}[]", $checked, $attribs );
+                       if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+                               $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
+                                       $checkbox .
+                                       Html::element( 'label', [ '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'] );
+       }
+
+       protected function isTagForcedOn( $tag ) {
+               return isset( $this->mParams['force-options-on'] )
+                       && in_array( $tag, $this->mParams['force-options-on'] );
+       }
+
+       /**
+        * Get the complete table row for the input, including help text,
+        * labels, and whatever.
+        * We override this function since the label should always be on a separate
+        * line above the options in the case of a checkbox matrix, i.e. it's always
+        * a "vertical-label".
+        *
+        * @param string $value The value to set the input to
+        *
+        * @return string Complete HTML table row
+        */
+       function getTableRow( $value ) {
+               list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
+               $inputHtml = $this->getInputHTML( $value );
+               $fieldType = get_class( $this );
+               $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
+               $cellAttributes = [ 'colspan' => 2 ];
+
+               $hideClass = '';
+               $hideAttributes = [];
+               if ( $this->mHideIf ) {
+                       $hideAttributes['data-hide-if'] = FormatJson::encode( $this->mHideIf );
+                       $hideClass = 'mw-htmlform-hide-if';
+               }
+
+               $label = $this->getLabelHtml( $cellAttributes );
+
+               $field = Html::rawElement(
+                       'td',
+                       [ 'class' => 'mw-input' ] + $cellAttributes,
+                       $inputHtml . "\n$errors"
+               );
+
+               $html = Html::rawElement( 'tr',
+                       [ 'class' => "mw-htmlform-vertical-label $hideClass" ] + $hideAttributes,
+                       $label );
+               $html .= Html::rawElement( 'tr',
+                       [ 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass $hideClass" ] +
+                               $hideAttributes,
+                       $field );
+
+               return $html . $helptext;
+       }
+
+       /**
+        * @param WebRequest $request
+        *
+        * @return array
+        */
+       function loadDataFromRequest( $request ) {
+               if ( $this->isSubmitAttempt( $request ) ) {
+                       // Checkboxes are just not added to the request arrays if they're not checked,
+                       // so it's perfectly possible for there not to be an entry at all
+                       return $request->getArray( $this->mName, [] );
+               } else {
+                       // That's ok, the user has not yet submitted the form, so show the defaults
+                       return $this->getDefault();
+               }
+       }
+
+       function getDefault() {
+               if ( isset( $this->mDefault ) ) {
+                       return $this->mDefault;
+               } else {
+                       return [];
+               }
+       }
+
+       function filterDataForSubmit( $data ) {
+               $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
+               $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
+               $res = [];
+               foreach ( $columns as $column ) {
+                       foreach ( $rows as $row ) {
+                               // Make sure option hasn't been forced
+                               $thisTag = "$column-$row";
+                               if ( $this->isTagForcedOff( $thisTag ) ) {
+                                       $res[$thisTag] = false;
+                               } elseif ( $this->isTagForcedOn( $thisTag ) ) {
+                                       $res[$thisTag] = true;
+                               } else {
+                                       $res[$thisTag] = in_array( $thisTag, $data );
+                               }
+                       }
+               }
+
+               return $res;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLComboboxField.php b/includes/htmlform/fields/HTMLComboboxField.php
new file mode 100644 (file)
index 0000000..778aedb
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * A combo box field.
+ *
+ * You can think of it as a dropdown select with the ability to add custom options,
+ * or as a text field with input suggestions (autocompletion).
+ *
+ * When JavaScript is not supported or enabled, it uses HTML5 `<datalist>` element.
+ *
+ * Besides the parameters recognized by HTMLTextField, the following are
+ * recognized:
+ *   options-messages - As for HTMLSelectField
+ *   options - As for HTMLSelectField
+ *   options-message - As for HTMLSelectField
+ */
+class HTMLComboboxField extends HTMLTextField {
+       // FIXME Ewww, this shouldn't be adding any attributes not requested in $list :(
+       public function getAttributes( array $list ) {
+               $attribs = [
+                       'type' => 'text',
+                       'list' => $this->mName . '-datalist',
+               ] + parent::getAttributes( $list );
+
+               return $attribs;
+       }
+
+       function getInputHTML( $value ) {
+               $datalist = new XmlSelect( false, $this->mName . '-datalist' );
+               $datalist->setTagName( 'datalist' );
+               $datalist->addOptions( $this->getOptions() );
+
+               return parent::getInputHTML( $value ) . $datalist->getHTML();
+       }
+
+       function getInputOOUI( $value ) {
+               $disabled = false;
+               $allowedParams = [ 'tabindex' ];
+               $attribs = OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( $allowedParams )
+               );
+
+               if ( $this->mClass !== '' ) {
+                       $attribs['classes'] = [ $this->mClass ];
+               }
+
+               if ( !empty( $this->mParams['disabled'] ) ) {
+                       $disabled = true;
+               }
+
+               return new OOUI\ComboBoxInputWidget( [
+                       'name' => $this->mName,
+                       'id' => $this->mID,
+                       'options' => $this->getOptionsOOUI(),
+                       'value' => strval( $value ),
+                       'disabled' => $disabled,
+               ] + $attribs );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLEditTools.php b/includes/htmlform/fields/HTMLEditTools.php
new file mode 100644 (file)
index 0000000..1b5d1fb
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+
+class HTMLEditTools extends HTMLFormField {
+       public function getInputHTML( $value ) {
+               return '';
+       }
+
+       public function getTableRow( $value ) {
+               $msg = $this->formatMsg();
+
+               return
+                       '<tr><td></td><td class="mw-input">' .
+                       '<div class="mw-editTools">' .
+                       $msg->parseAsBlock() .
+                       "</div></td></tr>\n";
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getDiv( $value ) {
+               $msg = $this->formatMsg();
+
+               return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getRaw( $value ) {
+               return $this->getDiv( $value );
+       }
+
+       protected function formatMsg() {
+               if ( empty( $this->mParams['message'] ) ) {
+                       $msg = $this->msg( 'edittools' );
+               } else {
+                       $msg = $this->getMessage( $this->mParams['message'] );
+                       if ( $msg->isDisabled() ) {
+                               $msg = $this->msg( 'edittools' );
+                       }
+               }
+               $msg->inContentLanguage();
+
+               return $msg;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLFloatField.php b/includes/htmlform/fields/HTMLFloatField.php
new file mode 100644 (file)
index 0000000..2ef4978
--- /dev/null
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * A field that will contain a numeric value
+ */
+class HTMLFloatField extends HTMLTextField {
+       function getSize() {
+               return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 20;
+       }
+
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               $value = trim( $value );
+
+               # http://www.w3.org/TR/html5/infrastructure.html#floating-point-numbers
+               # with the addition that a leading '+' sign is ok.
+               if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
+                       return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
+               }
+
+               # The "int" part of these message names is rather confusing.
+               # They make equal sense for all numbers.
+               if ( isset( $this->mParams['min'] ) ) {
+                       $min = $this->mParams['min'];
+
+                       if ( $min > $value ) {
+                               return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
+                       }
+               }
+
+               if ( isset( $this->mParams['max'] ) ) {
+                       $max = $this->mParams['max'];
+
+                       if ( $max < $value ) {
+                               return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
+                       }
+               }
+
+               return true;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLFormFieldCloner.php b/includes/htmlform/fields/HTMLFormFieldCloner.php
new file mode 100644 (file)
index 0000000..ec1bd84
--- /dev/null
@@ -0,0 +1,380 @@
+<?php
+
+/**
+ * A container for HTMLFormFields that allows for multiple copies of the set of
+ * fields to be displayed to and entered by the user.
+ *
+ * Recognized parameters, besides the general ones, include:
+ *   fields - HTMLFormField descriptors for the subfields this cloner manages.
+ *     The format is just like for the HTMLForm. A field with key 'delete' is
+ *     special: it must have type = submit and will serve to delete the group
+ *     of fields.
+ *   required - If specified, at least one group of fields must be submitted.
+ *   format - HTMLForm display format to use when displaying the subfields:
+ *     'table', 'div', or 'raw'.
+ *   row-legend - If non-empty, each group of subfields will be enclosed in a
+ *     fieldset. The value is the name of a message key to use as the legend.
+ *   create-button-message - Message to use as the text of the button to
+ *     add an additional group of fields.
+ *   delete-button-message - Message to use as the text of automatically-
+ *     generated 'delete' button. Ignored if 'delete' is included in 'fields'.
+ *
+ * In the generated HTML, the subfields will be named along the lines of
+ * "clonerName[index][fieldname]", with ids "clonerId--index--fieldid". 'index'
+ * may be a number or an arbitrary string, and may likely change when the page
+ * is resubmitted. Cloners may be nested, resulting in field names along the
+ * lines of "cloner1Name[index1][cloner2Name][index2][fieldname]" and
+ * corresponding ids.
+ *
+ * Use of cloner may result in submissions of the page that are not submissions
+ * of the HTMLForm, when non-JavaScript clients use the create or remove buttons.
+ *
+ * The result is an array, with values being arrays mapping subfield names to
+ * their values. On non-HTMLForm-submission page loads, there may also be
+ * additional (string) keys present with other types of values.
+ *
+ * @since 1.23
+ */
+class HTMLFormFieldCloner extends HTMLFormField {
+       private static $counter = 0;
+
+       /**
+        * @var string String uniquely identifying this cloner instance and
+        * unlikely to exist otherwise in the generated HTML, while still being
+        * valid as part of an HTML id.
+        */
+       protected $uniqueId;
+
+       public function __construct( $params ) {
+               $this->uniqueId = get_class( $this ) . ++self::$counter . 'x';
+               parent::__construct( $params );
+
+               if ( empty( $this->mParams['fields'] ) || !is_array( $this->mParams['fields'] ) ) {
+                       throw new MWException( 'HTMLFormFieldCloner called without any fields' );
+               }
+
+               // Make sure the delete button, if explicitly specified, is sane
+               if ( isset( $this->mParams['fields']['delete'] ) ) {
+                       $class = 'mw-htmlform-cloner-delete-button';
+                       $info = $this->mParams['fields']['delete'] + [
+                               'cssclass' => $class
+                       ];
+                       unset( $info['name'], $info['class'] );
+
+                       if ( !isset( $info['type'] ) || $info['type'] !== 'submit' ) {
+                               throw new MWException(
+                                       'HTMLFormFieldCloner delete field, if specified, must be of type "submit"'
+                               );
+                       }
+
+                       if ( !in_array( $class, explode( ' ', $info['cssclass'] ) ) ) {
+                               $info['cssclass'] .= " $class";
+                       }
+
+                       $this->mParams['fields']['delete'] = $info;
+               }
+       }
+
+       /**
+        * Create the HTMLFormFields that go inside this element, using the
+        * specified key.
+        *
+        * @param string $key Array key under which these fields should be named
+        * @return HTMLFormField[]
+        */
+       protected function createFieldsForKey( $key ) {
+               $fields = [];
+               foreach ( $this->mParams['fields'] as $fieldname => $info ) {
+                       $name = "{$this->mName}[$key][$fieldname]";
+                       if ( isset( $info['name'] ) ) {
+                               $info['name'] = "{$this->mName}[$key][{$info['name']}]";
+                       } else {
+                               $info['name'] = $name;
+                       }
+                       if ( isset( $info['id'] ) ) {
+                               $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--{$info['id']}" );
+                       } else {
+                               $info['id'] = Sanitizer::escapeId( "{$this->mID}--$key--$fieldname" );
+                       }
+                       $field = HTMLForm::loadInputFromParameters( $name, $info, $this->mParent );
+                       $fields[$fieldname] = $field;
+               }
+               return $fields;
+       }
+
+       /**
+        * Re-key the specified values array to match the names applied by
+        * createFieldsForKey().
+        *
+        * @param string $key Array key under which these fields should be named
+        * @param array $values Values array from the request
+        * @return array
+        */
+       protected function rekeyValuesArray( $key, $values ) {
+               $data = [];
+               foreach ( $values as $fieldname => $value ) {
+                       $name = "{$this->mName}[$key][$fieldname]";
+                       $data[$name] = $value;
+               }
+               return $data;
+       }
+
+       protected function needsLabel() {
+               return false;
+       }
+
+       public function loadDataFromRequest( $request ) {
+               // It's possible that this might be posted with no fields. Detect that
+               // by looking for an edit token.
+               if ( !$request->getCheck( 'wpEditToken' ) && $request->getArray( $this->mName ) === null ) {
+                       return $this->getDefault();
+               }
+
+               $values = $request->getArray( $this->mName );
+               if ( $values === null ) {
+                       $values = [];
+               }
+
+               $ret = [];
+               foreach ( $values as $key => $value ) {
+                       if ( $key === 'create' || isset( $value['delete'] ) ) {
+                               $ret['nonjs'] = 1;
+                               continue;
+                       }
+
+                       // Add back in $request->getValues() so things that look for e.g.
+                       // wpEditToken don't fail.
+                       $data = $this->rekeyValuesArray( $key, $value ) + $request->getValues();
+
+                       $fields = $this->createFieldsForKey( $key );
+                       $subrequest = new DerivativeRequest( $request, $data, $request->wasPosted() );
+                       $row = [];
+                       foreach ( $fields as $fieldname => $field ) {
+                               if ( $field->skipLoadData( $subrequest ) ) {
+                                       continue;
+                               } elseif ( !empty( $field->mParams['disabled'] ) ) {
+                                       $row[$fieldname] = $field->getDefault();
+                               } else {
+                                       $row[$fieldname] = $field->loadDataFromRequest( $subrequest );
+                               }
+                       }
+                       $ret[] = $row;
+               }
+
+               if ( isset( $values['create'] ) ) {
+                       // Non-JS client clicked the "create" button.
+                       $fields = $this->createFieldsForKey( $this->uniqueId );
+                       $row = [];
+                       foreach ( $fields as $fieldname => $field ) {
+                               if ( !empty( $field->mParams['nodata'] ) ) {
+                                       continue;
+                               } else {
+                                       $row[$fieldname] = $field->getDefault();
+                               }
+                       }
+                       $ret[] = $row;
+               }
+
+               return $ret;
+       }
+
+       public function getDefault() {
+               $ret = parent::getDefault();
+
+               // The default default is one entry with all subfields at their
+               // defaults.
+               if ( $ret === null ) {
+                       $fields = $this->createFieldsForKey( $this->uniqueId );
+                       $row = [];
+                       foreach ( $fields as $fieldname => $field ) {
+                               if ( !empty( $field->mParams['nodata'] ) ) {
+                                       continue;
+                               } else {
+                                       $row[$fieldname] = $field->getDefault();
+                               }
+                       }
+                       $ret = [ $row ];
+               }
+
+               return $ret;
+       }
+
+       public function cancelSubmit( $values, $alldata ) {
+               if ( isset( $values['nonjs'] ) ) {
+                       return true;
+               }
+
+               foreach ( $values as $key => $value ) {
+                       $fields = $this->createFieldsForKey( $key );
+                       foreach ( $fields as $fieldname => $field ) {
+                               if ( !array_key_exists( $fieldname, $value ) ) {
+                                       continue;
+                               }
+                               if ( $field->cancelSubmit( $value[$fieldname], $alldata ) ) {
+                                       return true;
+                               }
+                       }
+               }
+
+               return parent::cancelSubmit( $values, $alldata );
+       }
+
+       public function validate( $values, $alldata ) {
+               if ( isset( $this->mParams['required'] )
+                       && $this->mParams['required'] !== false
+                       && !$values
+               ) {
+                       return $this->msg( 'htmlform-cloner-required' )->parseAsBlock();
+               }
+
+               if ( isset( $values['nonjs'] ) ) {
+                       // The submission was a non-JS create/delete click, so fail
+                       // validation in case cancelSubmit() somehow didn't already handle
+                       // it.
+                       return false;
+               }
+
+               foreach ( $values as $key => $value ) {
+                       $fields = $this->createFieldsForKey( $key );
+                       foreach ( $fields as $fieldname => $field ) {
+                               if ( !array_key_exists( $fieldname, $value ) ) {
+                                       continue;
+                               }
+                               $ok = $field->validate( $value[$fieldname], $alldata );
+                               if ( $ok !== true ) {
+                                       return false;
+                               }
+                       }
+               }
+
+               return parent::validate( $values, $alldata );
+       }
+
+       /**
+        * Get the input HTML for the specified key.
+        *
+        * @param string $key Array key under which the fields should be named
+        * @param array $values
+        * @return string
+        */
+       protected function getInputHTMLForKey( $key, $values ) {
+               $displayFormat = isset( $this->mParams['format'] )
+                       ? $this->mParams['format']
+                       : $this->mParent->getDisplayFormat();
+
+               // Conveniently, PHP method names are case-insensitive.
+               $getFieldHtmlMethod = $displayFormat == 'table' ? 'getTableRow' : ( 'get' . $displayFormat );
+
+               $html = '';
+               $hidden = '';
+               $hasLabel = false;
+
+               $fields = $this->createFieldsForKey( $key );
+               foreach ( $fields as $fieldname => $field ) {
+                       $v = array_key_exists( $fieldname, $values )
+                               ? $values[$fieldname]
+                               : $field->getDefault();
+
+                       if ( $field instanceof HTMLHiddenField ) {
+                               // HTMLHiddenField doesn't generate its own HTML
+                               list( $name, $value, $params ) = $field->getHiddenFieldData( $v );
+                               $hidden .= Html::hidden( $name, $value, $params ) . "\n";
+                       } else {
+                               $html .= $field->$getFieldHtmlMethod( $v );
+
+                               $labelValue = trim( $field->getLabel() );
+                               if ( $labelValue != '&#160;' && $labelValue !== '' ) {
+                                       $hasLabel = true;
+                               }
+                       }
+               }
+
+               if ( !isset( $fields['delete'] ) ) {
+                       $name = "{$this->mName}[$key][delete]";
+                       $label = isset( $this->mParams['delete-button-message'] )
+                               ? $this->mParams['delete-button-message']
+                               : 'htmlform-cloner-delete';
+                       $field = HTMLForm::loadInputFromParameters( $name, [
+                               'type' => 'submit',
+                               'name' => $name,
+                               'id' => Sanitizer::escapeId( "{$this->mID}--$key--delete" ),
+                               'cssclass' => 'mw-htmlform-cloner-delete-button',
+                               'default' => $this->getMessage( $label )->text(),
+                       ], $this->mParent );
+                       $v = $field->getDefault();
+
+                       if ( $displayFormat === 'table' ) {
+                               $html .= $field->$getFieldHtmlMethod( $v );
+                       } else {
+                               $html .= $field->getInputHTML( $v );
+                       }
+               }
+
+               if ( $displayFormat !== 'raw' ) {
+                       $classes = [
+                               'mw-htmlform-cloner-row',
+                       ];
+
+                       if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
+                               $classes[] = 'mw-htmlform-nolabel';
+                       }
+
+                       $attribs = [
+                               'class' => implode( ' ', $classes ),
+                       ];
+
+                       if ( $displayFormat === 'table' ) {
+                               $html = Html::rawElement( 'table',
+                                       $attribs,
+                                       Html::rawElement( 'tbody', [], "\n$html\n" ) ) . "\n";
+                       } else {
+                               $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
+                       }
+               }
+
+               $html .= $hidden;
+
+               if ( !empty( $this->mParams['row-legend'] ) ) {
+                       $legend = $this->msg( $this->mParams['row-legend'] )->text();
+                       $html = Xml::fieldset( $legend, $html );
+               }
+
+               return $html;
+       }
+
+       public function getInputHTML( $values ) {
+               $html = '';
+
+               foreach ( (array)$values as $key => $value ) {
+                       if ( $key === 'nonjs' ) {
+                               continue;
+                       }
+                       $html .= Html::rawElement( 'li', [ 'class' => 'mw-htmlform-cloner-li' ],
+                               $this->getInputHTMLForKey( $key, $value )
+                       );
+               }
+
+               $template = $this->getInputHTMLForKey( $this->uniqueId, null );
+               $html = Html::rawElement( 'ul', [
+                       'id' => "mw-htmlform-cloner-list-{$this->mID}",
+                       'class' => 'mw-htmlform-cloner-ul',
+                       'data-template' => $template,
+                       'data-unique-id' => $this->uniqueId,
+               ], $html );
+
+               $name = "{$this->mName}[create]";
+               $label = isset( $this->mParams['create-button-message'] )
+                       ? $this->mParams['create-button-message']
+                       : 'htmlform-cloner-create';
+               $field = HTMLForm::loadInputFromParameters( $name, [
+                       'type' => 'submit',
+                       'name' => $name,
+                       'id' => Sanitizer::escapeId( "{$this->mID}--create" ),
+                       'cssclass' => 'mw-htmlform-cloner-create-button',
+                       'default' => $this->getMessage( $label )->text(),
+               ], $this->mParent );
+               $html .= $field->getInputHTML( $field->getDefault() );
+
+               return $html;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLFormFieldWithButton.php b/includes/htmlform/fields/HTMLFormFieldWithButton.php
new file mode 100644 (file)
index 0000000..bcb07bd
--- /dev/null
@@ -0,0 +1,75 @@
+<?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 = [ '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 = [
+                       'class' => 'mw-htmlform-submit ' . $this->mButtonClass,
+                       'id' => $this->mButtonId,
+               ] + $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               return Html::input( $this->mButtonName, $this->mButtonValue, $this->mButtonType, $attr );
+       }
+
+       public function getInputOOUI( $value ) {
+               return new OOUI\ButtonInputWidget( [
+                       'name' => $this->mButtonName,
+                       'value' => $this->mButtonValue,
+                       'type' => $this->mButtonType,
+                       'label' => $this->mButtonValue,
+                       'flags' => $this->mButtonFlags,
+               ] + OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
+               ) );
+       }
+
+       /**
+        * 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/fields/HTMLHiddenField.php b/includes/htmlform/fields/HTMLHiddenField.php
new file mode 100644 (file)
index 0000000..c0fce2b
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+
+class HTMLHiddenField extends HTMLFormField {
+       protected $outputAsDefault = true;
+
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               if ( isset( $this->mParams['output-as-default'] ) ) {
+                       $this->outputAsDefault = (bool)$this->mParams['output-as-default'];
+               }
+
+               # Per HTML5 spec, hidden fields cannot be 'required'
+               # http://www.w3.org/TR/html5/forms.html#hidden-state-%28type=hidden%29
+               unset( $this->mParams['required'] );
+       }
+
+       public function getHiddenFieldData( $value ) {
+               $params = [];
+               if ( $this->mID ) {
+                       $params['id'] = $this->mID;
+               }
+
+               if ( $this->outputAsDefault ) {
+                       $value = $this->mDefault;
+               }
+
+               return [ $this->mName, $value, $params ];
+       }
+
+       public function getTableRow( $value ) {
+               list( $name, $value, $params ) = $this->getHiddenFieldData( $value );
+               $this->mParent->addHiddenField( $name, $value, $params );
+               return '';
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getDiv( $value ) {
+               return $this->getTableRow( $value );
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getRaw( $value ) {
+               return $this->getTableRow( $value );
+       }
+
+       public function getInputHTML( $value ) {
+               return '';
+       }
+
+       public function canDisplayErrors() {
+               return false;
+       }
+
+       public function hasVisibleOutput() {
+               return false;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLInfoField.php b/includes/htmlform/fields/HTMLInfoField.php
new file mode 100644 (file)
index 0000000..ada4fb6
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * An information field (text blob), not a proper input.
+ */
+class HTMLInfoField extends HTMLFormField {
+       public function __construct( $info ) {
+               $info['nodata'] = true;
+
+               parent::__construct( $info );
+       }
+
+       public function getInputHTML( $value ) {
+               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( [
+                       'label' => $value,
+               ] );
+       }
+
+       public function getTableRow( $value ) {
+               if ( !empty( $this->mParams['rawrow'] ) ) {
+                       return $value;
+               }
+
+               return parent::getTableRow( $value );
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getDiv( $value ) {
+               if ( !empty( $this->mParams['rawrow'] ) ) {
+                       return $value;
+               }
+
+               return parent::getDiv( $value );
+       }
+
+       /**
+        * @param string $value
+        * @return string
+        * @since 1.20
+        */
+       public function getRaw( $value ) {
+               if ( !empty( $this->mParams['rawrow'] ) ) {
+                       return $value;
+               }
+
+               return parent::getRaw( $value );
+       }
+
+       protected function needsLabel() {
+               return false;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLIntField.php b/includes/htmlform/fields/HTMLIntField.php
new file mode 100644 (file)
index 0000000..b0148d9
--- /dev/null
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * A field that must contain a number
+ */
+class HTMLIntField extends HTMLFloatField {
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               # http://www.w3.org/TR/html5/infrastructure.html#signed-integers
+               # with the addition that a leading '+' sign is ok. Note that leading zeros
+               # are fine, and will be left in the input, which is useful for things like
+               # phone numbers when you know that they are integers (the HTML5 type=tel
+               # input does not require its value to be numeric).  If you want a tidier
+               # value to, eg, save in the DB, clean it up with intval().
+               if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) ) ) {
+                       return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
+               }
+
+               return true;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLMultiSelectField.php b/includes/htmlform/fields/HTMLMultiSelectField.php
new file mode 100644 (file)
index 0000000..a231b2f
--- /dev/null
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * Multi-select field
+ */
+class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               if ( !is_array( $value ) ) {
+                       return false;
+               }
+
+               # If all options are valid, array_intersect of the valid options
+               # and the provided options will return the provided options.
+               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+               $validValues = array_intersect( $value, $validOptions );
+               if ( count( $validValues ) == count( $value ) ) {
+                       return true;
+               } else {
+                       return $this->msg( 'htmlform-select-badoption' )->parse();
+               }
+       }
+
+       function getInputHTML( $value ) {
+               $value = HTMLFormField::forceToStringRecursive( $value );
+               $html = $this->formatOptions( $this->getOptions(), $value );
+
+               return $html;
+       }
+
+       function formatOptions( $options, $value ) {
+               $html = '';
+
+               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
+
+               foreach ( $options as $label => $info ) {
+                       if ( is_array( $info ) ) {
+                               $html .= Html::rawElement( 'h1', [], $label ) . "\n";
+                               $html .= $this->formatOptions( $info, $value );
+                       } else {
+                               $thisAttribs = [
+                                       'id' => "{$this->mID}-$info",
+                                       'value' => $info,
+                               ];
+                               $checked = in_array( $info, $value, true );
+
+                               $checkbox = $this->getOneCheckbox( $checked, $attribs + $thisAttribs, $label );
+
+                               $html .= ' ' . Html::rawElement(
+                                       'div',
+                                       [ 'class' => 'mw-htmlform-flatlist-item' ],
+                                       $checkbox
+                               );
+                       }
+               }
+
+               return $html;
+       }
+
+       protected function getOneCheckbox( $checked, $attribs, $label ) {
+               if ( $this->mParent instanceof OOUIHTMLForm ) {
+                       throw new MWException( 'HTMLMultiSelectField#getOneCheckbox() is not supported' );
+               } else {
+                       $elementFunc = [ 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ];
+                       $checkbox =
+                               Xml::check( "{$this->mName}[]", $checked, $attribs ) .
+                               '&#160;' .
+                               call_user_func( $elementFunc,
+                                       'label',
+                                       [ 'for' => $attribs['id'] ],
+                                       $label
+                               );
+                       if ( $this->mParent->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
+                               $checkbox = Html::openElement( 'div', [ 'class' => 'mw-ui-checkbox' ] ) .
+                                       $checkbox .
+                                       Html::closeElement( 'div' );
+                       }
+                       return $checkbox;
+               }
+       }
+
+       /**
+        * Get the OOUI version of this field.
+        *
+        * @since 1.28
+        * @param string[] $value
+        * @return OOUI\CheckboxMultiselectInputWidget
+        */
+       public function getInputOOUI( $value ) {
+               $attr = $this->getTooltipAndAccessKey();
+               $attr['id'] = $this->mID;
+               $attr['name'] = "{$this->mName}[]";
+
+               $attr['value'] = $value;
+               $attr['options'] = $this->getOptionsOOUI();
+
+               if ( $this->mOptionsLabelsNotFromMessage ) {
+                       foreach ( $attr['options'] as &$option ) {
+                               $option['label'] = new OOUI\HtmlSnippet( $option['label'] );
+                       }
+               }
+
+               $attr += OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
+               );
+
+               if ( $this->mClass !== '' ) {
+                       $attr['classes'] = [ $this->mClass ];
+               }
+
+               return new OOUI\CheckboxMultiselectInputWidget( $attr );
+       }
+
+       /**
+        * @param WebRequest $request
+        *
+        * @return string
+        */
+       function loadDataFromRequest( $request ) {
+               if ( $this->isSubmitAttempt( $request ) ) {
+                       // Checkboxes are just not added to the request arrays if they're not checked,
+                       // so it's perfectly possible for there not to be an entry at all
+                       return $request->getArray( $this->mName, [] );
+               } else {
+                       // That's ok, the user has not yet submitted the form, so show the defaults
+                       return $this->getDefault();
+               }
+       }
+
+       function getDefault() {
+               if ( isset( $this->mDefault ) ) {
+                       return $this->mDefault;
+               } else {
+                       return [];
+               }
+       }
+
+       function filterDataForSubmit( $data ) {
+               $data = HTMLFormField::forceToStringRecursive( $data );
+               $options = HTMLFormField::flattenOptions( $this->getOptions() );
+
+               $res = [];
+               foreach ( $options as $opt ) {
+                       $res["$opt"] = in_array( $opt, $data, true );
+               }
+
+               return $res;
+       }
+
+       protected function needsLabel() {
+               return false;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLRadioField.php b/includes/htmlform/fields/HTMLRadioField.php
new file mode 100644 (file)
index 0000000..e5b5e68
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * Radio checkbox fields.
+ */
+class HTMLRadioField extends HTMLFormField {
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               if ( !is_string( $value ) && !is_int( $value ) ) {
+                       return false;
+               }
+
+               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+               if ( in_array( strval( $value ), $validOptions, true ) ) {
+                       return true;
+               } else {
+                       return $this->msg( 'htmlform-select-badoption' )->parse();
+               }
+       }
+
+       /**
+        * This returns a block of all the radio options, in one cell.
+        * @see includes/HTMLFormField#getInputHTML()
+        *
+        * @param string $value
+        *
+        * @return string
+        */
+       function getInputHTML( $value ) {
+               $html = $this->formatOptions( $this->getOptions(), strval( $value ) );
+
+               return $html;
+       }
+
+       function getInputOOUI( $value ) {
+               $options = [];
+               foreach ( $this->getOptions() as $label => $data ) {
+                       $options[] = [
+                               'data' => $data,
+                               'label' => $this->mOptionsLabelsNotFromMessage ? new OOUI\HtmlSnippet( $label ) : $label,
+                       ];
+               }
+
+               return new OOUI\RadioSelectInputWidget( [
+                       'name' => $this->mName,
+                       'id' => $this->mID,
+                       'value' => $value,
+                       'options' => $options,
+               ] + OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( [ 'disabled', 'tabindex' ] )
+               ) );
+       }
+
+       function formatOptions( $options, $value ) {
+               global $wgUseMediaWikiUIEverywhere;
+
+               $html = '';
+
+               $attribs = $this->getAttributes( [ 'disabled', 'tabindex' ] );
+               $elementFunc = [ 'Html', $this->mOptionsLabelsNotFromMessage ? 'rawElement' : 'element' ];
+
+               # @todo Should this produce an unordered list perhaps?
+               foreach ( $options as $label => $info ) {
+                       if ( is_array( $info ) ) {
+                               $html .= Html::rawElement( 'h1', [], $label ) . "\n";
+                               $html .= $this->formatOptions( $info, $value );
+                       } else {
+                               $id = Sanitizer::escapeId( $this->mID . "-$info" );
+                               $classes = [ 'mw-htmlform-flatlist-item' ];
+                               if ( $wgUseMediaWikiUIEverywhere || $this->mParent instanceof VFormHTMLForm ) {
+                                       $classes[] = 'mw-ui-radio';
+                               }
+                               $radio = Xml::radio( $this->mName, $info, $info === $value, $attribs + [ 'id' => $id ] );
+                               $radio .= '&#160;' . call_user_func( $elementFunc, 'label', [ 'for' => $id ], $label );
+
+                               $html .= ' ' . Html::rawElement(
+                                       'div',
+                                       [ 'class' => $classes ],
+                                       $radio
+                               );
+                       }
+               }
+
+               return $html;
+       }
+
+       protected function needsLabel() {
+               return false;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSelectAndOtherField.php b/includes/htmlform/fields/HTMLSelectAndOtherField.php
new file mode 100644 (file)
index 0000000..e75c2b2
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+
+/**
+ * Double field with a dropdown list constructed from a system message in the format
+ *     * Optgroup header
+ *     ** <option value>
+ *     * New Optgroup header
+ * Plus a text field underneath for an additional reason.  The 'value' of the field is
+ * "<select>: <extra reason>", or "<extra reason>" if nothing has been selected in the
+ * select dropdown.
+ * @todo FIXME: If made 'required', only the text field should be compulsory.
+ */
+class HTMLSelectAndOtherField extends HTMLSelectField {
+       function __construct( $params ) {
+               if ( array_key_exists( 'other', $params ) ) {
+                       // Do nothing
+               } elseif ( array_key_exists( 'other-message', $params ) ) {
+                       $params['other'] = $this->getMessage( $params['other-message'] )->plain();
+               } else {
+                       $params['other'] = $this->msg( 'htmlform-selectorother-other' )->plain();
+               }
+
+               parent::__construct( $params );
+
+               if ( $this->getOptions() === null ) {
+                       // Sulk
+                       throw new MWException( 'HTMLSelectAndOtherField called without any options' );
+               }
+               if ( !in_array( 'other', $this->mOptions, true ) ) {
+                       // Have 'other' always as first element
+                       $this->mOptions = [ $params['other'] => 'other' ] + $this->mOptions;
+               }
+               $this->mFlatOptions = self::flattenOptions( $this->getOptions() );
+
+       }
+
+       function getInputHTML( $value ) {
+               $select = parent::getInputHTML( $value[1] );
+
+               $textAttribs = [
+                       'id' => $this->mID . '-other',
+                       'size' => $this->getSize(),
+                       'class' => [ 'mw-htmlform-select-and-other-field' ],
+                       'data-id-select' => $this->mID,
+               ];
+
+               if ( $this->mClass !== '' ) {
+                       $textAttribs['class'][] = $this->mClass;
+               }
+
+               $allowedParams = [
+                       'required',
+                       'autofocus',
+                       'multiple',
+                       'disabled',
+                       'tabindex',
+                       'maxlength', // gets dynamic with javascript, see mediawiki.htmlform.js
+               ];
+
+               $textAttribs += $this->getAttributes( $allowedParams );
+
+               $textbox = Html::input( $this->mName . '-other', $value[2], 'text', $textAttribs );
+
+               return "$select<br />\n$textbox";
+       }
+
+       function getInputOOUI( $value ) {
+               return false;
+       }
+
+       /**
+        * @param WebRequest $request
+        *
+        * @return array("<overall message>","<select value>","<text field value>")
+        */
+       function loadDataFromRequest( $request ) {
+               if ( $request->getCheck( $this->mName ) ) {
+                       $list = $request->getText( $this->mName );
+                       $text = $request->getText( $this->mName . '-other' );
+
+                       // Should be built the same as in mediawiki.htmlform.js
+                       if ( $list == 'other' ) {
+                               $final = $text;
+                       } elseif ( !in_array( $list, $this->mFlatOptions, true ) ) {
+                               # User has spoofed the select form to give an option which wasn't
+                               # in the original offer.  Sulk...
+                               $final = $text;
+                       } elseif ( $text == '' ) {
+                               $final = $list;
+                       } else {
+                               $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
+                       }
+               } else {
+                       $final = $this->getDefault();
+
+                       $list = 'other';
+                       $text = $final;
+                       foreach ( $this->mFlatOptions as $option ) {
+                               $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
+                               if ( strpos( $text, $match ) === 0 ) {
+                                       $list = $option;
+                                       $text = substr( $text, strlen( $match ) );
+                                       break;
+                               }
+                       }
+               }
+
+               return [ $final, $list, $text ];
+       }
+
+       function getSize() {
+               return isset( $this->mParams['size'] ) ? $this->mParams['size'] : 45;
+       }
+
+       function validate( $value, $alldata ) {
+               # HTMLSelectField forces $value to be one of the options in the select
+               # field, which is not useful here.  But we do want the validation further up
+               # the chain
+               $p = parent::validate( $value[1], $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               if ( isset( $this->mParams['required'] )
+                       && $this->mParams['required'] !== false
+                       && $value[1] === ''
+               ) {
+                       return $this->msg( 'htmlform-required' )->parse();
+               }
+
+               return true;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSelectField.php b/includes/htmlform/fields/HTMLSelectField.php
new file mode 100644 (file)
index 0000000..b6ad46c
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * A select dropdown field.  Basically a wrapper for Xmlselect class
+ */
+class HTMLSelectField extends HTMLFormField {
+       function validate( $value, $alldata ) {
+               $p = parent::validate( $value, $alldata );
+
+               if ( $p !== true ) {
+                       return $p;
+               }
+
+               $validOptions = HTMLFormField::flattenOptions( $this->getOptions() );
+
+               if ( in_array( strval( $value ), $validOptions, true ) ) {
+                       return true;
+               } else {
+                       return $this->msg( 'htmlform-select-badoption' )->parse();
+               }
+       }
+
+       function getInputHTML( $value ) {
+               $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
+
+               if ( !empty( $this->mParams['disabled'] ) ) {
+                       $select->setAttribute( 'disabled', 'disabled' );
+               }
+
+               $allowedParams = [ 'tabindex', 'size' ];
+               $customParams = $this->getAttributes( $allowedParams );
+               foreach ( $customParams as $name => $value ) {
+                       $select->setAttribute( $name, $value );
+               }
+
+               if ( $this->mClass !== '' ) {
+                       $select->setAttribute( 'class', $this->mClass );
+               }
+
+               $select->addOptions( $this->getOptions() );
+
+               return $select->getHTML();
+       }
+
+       function getInputOOUI( $value ) {
+               $disabled = false;
+               $allowedParams = [ 'tabindex' ];
+               $attribs = OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( $allowedParams )
+               );
+
+               if ( $this->mClass !== '' ) {
+                       $attribs['classes'] = [ $this->mClass ];
+               }
+
+               if ( !empty( $this->mParams['disabled'] ) ) {
+                       $disabled = true;
+               }
+
+               return new OOUI\DropdownInputWidget( [
+                       'name' => $this->mName,
+                       'id' => $this->mID,
+                       'options' => $this->getOptionsOOUI(),
+                       'value' => strval( $value ),
+                       'disabled' => $disabled,
+               ] + $attribs );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSelectLimitField.php b/includes/htmlform/fields/HTMLSelectLimitField.php
new file mode 100644 (file)
index 0000000..e7f1c04
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * A limit dropdown, which accepts any valid number
+ */
+class HTMLSelectLimitField extends HTMLSelectField {
+       /**
+        * Basically don't do any validation. If it's a number that's fine. Also,
+        * add it to the list if it's not there already
+        *
+        * @param string $value
+        * @param array $alldata
+        * @return bool
+        */
+       function validate( $value, $alldata ) {
+               if ( $value == '' ) {
+                       return true;
+               }
+
+               // Let folks pick an explicit limit not from our list, as long as it's a real numbr.
+               if ( !in_array( $value, $this->mParams['options'] )
+                       && $value == intval( $value )
+                       && $value > 0
+               ) {
+                       // This adds the explicitly requested limit value to the drop-down,
+                       // then makes sure it's sorted correctly so when we output the list
+                       // later, the custom option doesn't just show up last.
+                       $this->mParams['options'][$this->mParent->getLanguage()->formatNum( $value )] =
+                               intval( $value );
+                       asort( $this->mParams['options'] );
+               }
+
+               return true;
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSelectNamespace.php b/includes/htmlform/fields/HTMLSelectNamespace.php
new file mode 100644 (file)
index 0000000..ef21969
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Wrapper for Html::namespaceSelector to use in HTMLForm
+ */
+class HTMLSelectNamespace extends HTMLFormField {
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               $this->mAllValue = array_key_exists( 'all', $params )
+                       ? $params['all']
+                       : 'all';
+
+       }
+
+       function getInputHTML( $value ) {
+               return Html::namespaceSelector(
+                       [
+                               'selected' => $value,
+                               'all' => $this->mAllValue
+                       ], [
+                               'name' => $this->mName,
+                               'id' => $this->mID,
+                               'class' => 'namespaceselector',
+                       ]
+               );
+       }
+
+       public function getInputOOUI( $value ) {
+               return new MediaWiki\Widget\NamespaceInputWidget( [
+                       'value' => $value,
+                       'name' => $this->mName,
+                       'id' => $this->mID,
+                       'includeAllValue' => $this->mAllValue,
+               ] );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSelectNamespaceWithButton.php b/includes/htmlform/fields/HTMLSelectNamespaceWithButton.php
new file mode 100644 (file)
index 0000000..5225983
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Creates a Html::namespaceSelector input field with a button assigned to the input field.
+ */
+class HTMLSelectNamespaceWithButton extends HTMLSelectNamespace {
+       /** @var HTMLFormFieldWithButton $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/fields/HTMLSelectOrOtherField.php b/includes/htmlform/fields/HTMLSelectOrOtherField.php
new file mode 100644 (file)
index 0000000..8f7750c
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * Select dropdown field, with an additional "other" textbox.
+ *
+ * HTMLComboboxField implements the same functionality using a single form field
+ * and should be used instead.
+ */
+class HTMLSelectOrOtherField extends HTMLTextField {
+       function __construct( $params ) {
+               parent::__construct( $params );
+               $this->getOptions();
+               if ( !in_array( 'other', $this->mOptions, true ) ) {
+                       $msg =
+                               isset( $params['other'] )
+                                       ? $params['other']
+                                       : wfMessage( 'htmlform-selectorother-other' )->text();
+                       // Have 'other' always as first element
+                       $this->mOptions = [ $msg => 'other' ] + $this->mOptions;
+               }
+
+       }
+
+       function getInputHTML( $value ) {
+               $valInSelect = false;
+
+               if ( $value !== false ) {
+                       $value = strval( $value );
+                       $valInSelect = in_array(
+                               $value, HTMLFormField::flattenOptions( $this->getOptions() ), true
+                       );
+               }
+
+               $selected = $valInSelect ? $value : 'other';
+
+               $select = new XmlSelect( $this->mName, $this->mID, $selected );
+               $select->addOptions( $this->getOptions() );
+
+               $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
+
+               $tbAttribs = [ 'id' => $this->mID . '-other', 'size' => $this->getSize() ];
+
+               if ( !empty( $this->mParams['disabled'] ) ) {
+                       $select->setAttribute( 'disabled', 'disabled' );
+                       $tbAttribs['disabled'] = 'disabled';
+               }
+
+               if ( isset( $this->mParams['tabindex'] ) ) {
+                       $select->setAttribute( 'tabindex', $this->mParams['tabindex'] );
+                       $tbAttribs['tabindex'] = $this->mParams['tabindex'];
+               }
+
+               $select = $select->getHTML();
+
+               if ( isset( $this->mParams['maxlength'] ) ) {
+                       $tbAttribs['maxlength'] = $this->mParams['maxlength'];
+               }
+
+               if ( $this->mClass !== '' ) {
+                       $tbAttribs['class'] = $this->mClass;
+               }
+
+               $textbox = Html::input( $this->mName . '-other', $valInSelect ? '' : $value, 'text', $tbAttribs );
+
+               return "$select<br />\n$textbox";
+       }
+
+       function getInputOOUI( $value ) {
+               return false;
+       }
+
+       /**
+        * @param WebRequest $request
+        *
+        * @return string
+        */
+       function loadDataFromRequest( $request ) {
+               if ( $request->getCheck( $this->mName ) ) {
+                       $val = $request->getText( $this->mName );
+
+                       if ( $val === 'other' ) {
+                               $val = $request->getText( $this->mName . '-other' );
+                       }
+
+                       return $val;
+               } else {
+                       return $this->getDefault();
+               }
+       }
+}
diff --git a/includes/htmlform/fields/HTMLSubmitField.php b/includes/htmlform/fields/HTMLSubmitField.php
new file mode 100644 (file)
index 0000000..cb98549
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * Add a submit button inline in the form (as opposed to
+ * HTMLForm::addButton(), which will add it at the end).
+ */
+class HTMLSubmitField extends HTMLButtonField {
+       protected $buttonType = 'submit';
+
+       protected $mFlags = [ 'primary', 'constructive' ];
+
+       public function skipLoadData( $request ) {
+               return !$request->getCheck( $this->mName );
+       }
+
+       public function loadDataFromRequest( $request ) {
+               return $request->getCheck( $this->mName );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLTagFilter.php b/includes/htmlform/fields/HTMLTagFilter.php
new file mode 100644 (file)
index 0000000..8075de5
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * Wrapper for ChangeTags::buildTagFilterSelector to use in HTMLForm
+ */
+class HTMLTagFilter extends HTMLFormField {
+       protected $tagFilter;
+
+       function getTableRow( $value ) {
+               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+               if ( $this->tagFilter ) {
+                       return parent::getTableRow( $value );
+               }
+               return '';
+       }
+
+       function getDiv( $value ) {
+               $this->tagFilter = ChangeTags::buildTagFilterSelector( $value );
+               if ( $this->tagFilter ) {
+                       return parent::getDiv( $value );
+               }
+               return '';
+       }
+
+       function getInputHTML( $value ) {
+               if ( $this->tagFilter ) {
+                       // we only need the select field, HTMLForm should handle the label
+                       return $this->tagFilter[1];
+               }
+               return '';
+       }
+}
diff --git a/includes/htmlform/fields/HTMLTextAreaField.php b/includes/htmlform/fields/HTMLTextAreaField.php
new file mode 100644 (file)
index 0000000..8ffff43
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+
+class HTMLTextAreaField extends HTMLFormField {
+       const DEFAULT_COLS = 80;
+       const DEFAULT_ROWS = 25;
+
+       protected $mPlaceholder = '';
+
+       /**
+        * @param array $params
+        *   - cols, rows: textarea size
+        *   - placeholder/placeholder-message: set HTML placeholder attribute
+        *   - spellcheck: set HTML spellcheck attribute
+        */
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               if ( isset( $params['placeholder-message'] ) ) {
+                       $this->mPlaceholder = $this->getMessage( $params['placeholder-message'] )->parse();
+               } elseif ( isset( $params['placeholder'] ) ) {
+                       $this->mPlaceholder = $params['placeholder'];
+               }
+       }
+
+       function getCols() {
+               return isset( $this->mParams['cols'] ) ? $this->mParams['cols'] : static::DEFAULT_COLS;
+       }
+
+       function getRows() {
+               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 = [
+                               'id' => $this->mID,
+                               'cols' => $this->getCols(),
+                               'rows' => $this->getRows(),
+                               'spellcheck' => $this->getSpellCheck(),
+                       ] + $this->getTooltipAndAccessKey();
+
+               if ( $this->mClass !== '' ) {
+                       $attribs['class'] = $this->mClass;
+               }
+               if ( $this->mPlaceholder !== '' ) {
+                       $attribs['placeholder'] = $this->mPlaceholder;
+               }
+
+               $allowedParams = [
+                       'tabindex',
+                       'disabled',
+                       'readonly',
+                       'required',
+                       'autofocus'
+               ];
+
+               $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'] = [ $this->mClass ];
+               }
+               if ( $this->mPlaceholder !== '' ) {
+                       $attribs['placeholder'] = $this->mPlaceholder;
+               }
+
+               $allowedParams = [
+                       'tabindex',
+                       'disabled',
+                       'readonly',
+                       'required',
+                       'autofocus',
+               ];
+
+               $attribs += OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( $allowedParams )
+               );
+
+               return new OOUI\TextInputWidget( [
+                       'id' => $this->mID,
+                       'name' => $this->mName,
+                       'multiline' => true,
+                       'value' => $value,
+                       'rows' => $this->getRows(),
+               ] + $attribs );
+       }
+}
diff --git a/includes/htmlform/fields/HTMLTextField.php b/includes/htmlform/fields/HTMLTextField.php
new file mode 100644 (file)
index 0000000..3ab7176
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+
+class HTMLTextField extends HTMLFormField {
+       protected $mPlaceholder = '';
+
+       /**
+        * @param array $params
+        *   - type: HTML textfield type
+        *   - size: field size in characters (defaults to 45)
+        *   - placeholder/placeholder-message: set HTML placeholder attribute
+        *   - spellcheck: set HTML spellcheck attribute
+        *   - persistent: upon unsuccessful requests, retain the value (defaults to true, except
+        *     for password fields)
+        */
+       public function __construct( $params ) {
+               parent::__construct( $params );
+
+               if ( isset( $params['placeholder-message'] ) ) {
+                       $this->mPlaceholder = $this->getMessage( $params['placeholder-message'] )->parse();
+               } elseif ( isset( $params['placeholder'] ) ) {
+                       $this->mPlaceholder = $params['placeholder'];
+               }
+       }
+
+       function getSize() {
+               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;
+       }
+
+       public function isPersistent() {
+               if ( isset( $this->mParams['persistent'] ) ) {
+                       return $this->mParams['persistent'];
+               }
+               // don't put passwords into the HTML body, they could get cached or otherwise leaked
+               return !( isset( $this->mParams['type'] ) && $this->mParams['type'] === 'password' );
+       }
+
+       function getInputHTML( $value ) {
+               if ( !$this->isPersistent() ) {
+                       $value = '';
+               }
+
+               $attribs = [
+                               'id' => $this->mID,
+                               'name' => $this->mName,
+                               'size' => $this->getSize(),
+                               'value' => $value,
+                               'dir' => $this->mDir,
+                               'spellcheck' => $this->getSpellCheck(),
+                       ] + $this->getTooltipAndAccessKey() + $this->getDataAttribs();
+
+               if ( $this->mClass !== '' ) {
+                       $attribs['class'] = $this->mClass;
+               }
+               if ( $this->mPlaceholder !== '' ) {
+                       $attribs['placeholder'] = $this->mPlaceholder;
+               }
+
+               # @todo Enforce pattern, step, required, readonly on the server side as
+               # well
+               $allowedParams = [
+                       'type',
+                       'min',
+                       'max',
+                       'pattern',
+                       'title',
+                       'step',
+                       'list',
+                       'maxlength',
+                       'tabindex',
+                       'disabled',
+                       'required',
+                       'autofocus',
+                       'multiple',
+                       'readonly'
+               ];
+
+               $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'] );
+
+               # Implement tiny differences between some field variants
+               # here, rather than creating a new class for each one which
+               # is essentially just a clone of this one.
+               if ( isset( $this->mParams['type'] ) ) {
+                       switch ( $this->mParams['type'] ) {
+                               case 'int':
+                                       $type = 'number';
+                                       break;
+                               case 'float':
+                                       $type = 'number';
+                                       $attribs['step'] = 'any';
+                                       break;
+                               # Pass through
+                               case 'email':
+                               case 'password':
+                               case 'file':
+                               case 'url':
+                                       $type = $this->mParams['type'];
+                                       break;
+                       }
+               }
+
+               return $type;
+       }
+
+       function getInputOOUI( $value ) {
+               if ( !$this->isPersistent() ) {
+                       $value = '';
+               }
+
+               $attribs = $this->getTooltipAndAccessKey();
+
+               if ( $this->mClass !== '' ) {
+                       $attribs['classes'] = [ $this->mClass ];
+               }
+               if ( $this->mPlaceholder !== '' ) {
+                       $attribs['placeholder'] = $this->mPlaceholder;
+               }
+
+               # @todo Enforce pattern, step, required, readonly on the server side as
+               # well
+               $allowedParams = [
+                       'autofocus',
+                       'autosize',
+                       'disabled',
+                       'flags',
+                       'indicator',
+                       'maxlength',
+                       'readonly',
+                       'required',
+                       'tabindex',
+                       'type',
+               ];
+
+               $attribs += OOUI\Element::configFromHtmlAttributes(
+                       $this->getAttributes( $allowedParams )
+               );
+
+               $type = $this->getType( $attribs );
+
+               return $this->getInputWidget( [
+                       'id' => $this->mID,
+                       'name' => $this->mName,
+                       'value' => $value,
+                       'type' => $type,
+               ] + $attribs );
+       }
+
+       protected function getInputWidget( $params ) {
+               return new OOUI\TextInputWidget( $params );
+       }
+
+       /**
+        * Returns an array of data-* attributes to add to the field.
+        *
+        * @return array
+        */
+       protected function getDataAttribs() {
+               return [];
+       }
+}
diff --git a/includes/htmlform/fields/HTMLTextFieldWithButton.php b/includes/htmlform/fields/HTMLTextFieldWithButton.php
new file mode 100644 (file)
index 0000000..7c1c673
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+/**
+ * Creates a text input field with a button assigned to the input field.
+ */
+class HTMLTextFieldWithButton extends HTMLTextField {
+       /** @var HTMLFormFieldWithButton $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/fields/HTMLTitleTextField.php b/includes/htmlform/fields/HTMLTitleTextField.php
new file mode 100644 (file)
index 0000000..fcf721a
--- /dev/null
@@ -0,0 +1,99 @@
+<?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 += [
+                       '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 );
+       }
+
+       public function getInputHtml( $value ) {
+               // add mw-searchInput class to enable search suggestions for non-OOUI, too
+               $this->mClass .= 'mw-searchInput';
+
+               // return the HTMLTextField html
+               return parent::getInputHTML( $value );
+       }
+
+       protected function getDataAttribs() {
+               return [
+                       'data-mw-searchsuggest' => FormatJson::encode( [
+                               'wrapAsLink' => false,
+                       ] ),
+               ];
+       }
+}
diff --git a/includes/htmlform/fields/HTMLUserTextField.php b/includes/htmlform/fields/HTMLUserTextField.php
new file mode 100644 (file)
index 0000000..5a7e0b9
--- /dev/null
@@ -0,0 +1,56 @@
+<?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 += [
+                       '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 );
+       }
+
+       public function getInputHtml( $value ) {
+               // add the required module and css class for user suggestions in non-OOUI mode
+               $this->mParent->getOutput()->addModules( 'mediawiki.userSuggest' );
+               $this->mClass .= ' mw-autocomplete-user';
+
+               // return parent html
+               return parent::getInputHTML( $value );
+       }
+}