* 'help-messages' -- array of message keys/objects. As above, each item can
* be an array of msg key and then parameters.
* Overwrites 'help'.
+ * 'notice' -- message text for a message to use as a notice in the field.
+ * Currently used by OOUI form fields only.
+ * 'notice-messages' -- array of message keys/objects to use for notice.
+ * Overrides 'notice'.
+ * 'notice-message' -- message key or object to use as a notice.
* 'required' -- passed through to the object, indicating that it
* is a required field.
* 'size' -- the length of text fields
'checkmatrix' => 'HTMLCheckMatrix',
'cloner' => 'HTMLFormFieldCloner',
'autocompleteselect' => 'HTMLAutoCompleteSelectField',
+ 'date' => 'HTMLDateTimeField',
+ 'time' => 'HTMLDateTimeField',
+ 'datetime' => 'HTMLDateTimeField',
// HTMLTextField will output the correct type="" attribute automagically.
// There are about four zillion other HTML5 input types, like range, but
// we don't use those at the moment, so no point in adding all of them.
protected $mFieldTree;
protected $mShowReset = false;
protected $mShowSubmit = true;
- protected $mSubmitFlags = [ 'constructive', 'primary' ];
+ protected $mSubmitFlags = [ 'primary', 'progressive' ];
+ protected $mShowCancel = false;
+ protected $mCancelTarget;
protected $mSubmitCallback;
protected $mValidationErrorMessage;
protected $mSubmitText;
protected $mSubmitTooltip;
+ protected $mFormIdentifier;
protected $mTitle;
protected $mMethod = 'post';
protected $mWasSubmitted = false;
switch ( $displayFormat ) {
case 'vform':
- $reflector = new ReflectionClass( 'VFormHTMLForm' );
- return $reflector->newInstanceArgs( $arguments );
+ return ObjectFactory::constructClassInstance( VFormHTMLForm::class, $arguments );
case 'ooui':
- $reflector = new ReflectionClass( 'OOUIHTMLForm' );
- return $reflector->newInstanceArgs( $arguments );
+ return ObjectFactory::constructClassInstance( OOUIHTMLForm::class, $arguments );
default:
- $reflector = new ReflectionClass( 'HTMLForm' );
- $form = $reflector->newInstanceArgs( $arguments );
+ /** @var HTMLForm $form */
+ $form = ObjectFactory::constructClassInstance( HTMLForm::class, $arguments );
$form->setDisplayFormat( $displayFormat );
return $form;
}
$this->mFieldTree = $loadedDescriptor;
}
+ /**
+ * @param string $fieldname
+ * @return bool
+ */
+ public function hasField( $fieldname ) {
+ return isset( $this->mFlatFields[$fieldname] );
+ }
+
+ /**
+ * @param string $fieldname
+ * @return HTMLFormField
+ * @throws DomainException on invalid field name
+ */
+ public function getField( $fieldname ) {
+ if ( !$this->hasField( $fieldname ) ) {
+ throw new DomainException( __METHOD__ . ': no field named ' . $fieldname );
+ }
+ return $this->mFlatFields[$fieldname];
+ }
+
/**
* Set format in which to display the form
*
}
# Load data from the request.
- $this->loadData();
+ if (
+ $this->mFormIdentifier === null ||
+ $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier
+ ) {
+ $this->loadData();
+ } else {
+ $this->mFieldData = [];
+ }
return $this;
}
public function tryAuthorizedSubmit() {
$result = false;
- $submit = false;
+ $identOkay = false;
+ if ( $this->mFormIdentifier === null ) {
+ $identOkay = true;
+ } else {
+ $identOkay = $this->getRequest()->getVal( 'wpFormIdentifier' ) === $this->mFormIdentifier;
+ }
+
+ $tokenOkay = false;
if ( $this->getMethod() !== 'post' ) {
- $submit = true; // no session check needed
+ $tokenOkay = true; // no session check needed
} elseif ( $this->getRequest()->wasPosted() ) {
$editToken = $this->getRequest()->getVal( 'wpEditToken' );
if ( $this->getUser()->isLoggedIn() || $editToken !== null ) {
// Session tokens for logged-out users have no security value.
// However, if the user gave one, check it in order to give a nice
// "session expired" error instead of "permission denied" or such.
- $submit = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
+ $tokenOkay = $this->getUser()->matchEditToken( $editToken, $this->mTokenSalt );
} else {
- $submit = true;
+ $tokenOkay = true;
}
}
- if ( $submit ) {
+ if ( $tokenOkay && $identOkay ) {
$this->mWasSubmitted = true;
$result = $this->trySubmit();
}
$this->getOutput()->addModuleStyles( 'mediawiki.htmlform.styles' );
$html = ''
- . $this->getErrors( $submitResult )
+ . $this->getErrorsOrWarnings( $submitResult, 'error' )
+ . $this->getErrorsOrWarnings( $submitResult, 'warning' )
. $this->getHeaderText()
. $this->getBody()
. $this->getHiddenFields()
*/
public function getHiddenFields() {
$html = '';
+ if ( $this->mFormIdentifier !== null ) {
+ $html .= Html::hidden(
+ 'wpFormIdentifier',
+ $this->mFormIdentifier
+ ) . "\n";
+ }
if ( $this->getMethod() === 'post' ) {
$html .= Html::hidden(
'wpEditToken',
) . "\n";
}
+ if ( $this->mShowCancel ) {
+ $target = $this->mCancelTarget ?: Title::newMainPage();
+ if ( $target instanceof Title ) {
+ $target = $target->getLocalURL();
+ }
+ $buttons .= Html::element(
+ 'a',
+ [
+ 'class' => $useMediaWikiUIEverywhere ? 'mw-ui-button' : null,
+ 'href' => $target,
+ ],
+ $this->msg( 'cancel' )->text()
+ ) . "\n";
+ }
+
// IE<8 has bugs with <button>, so we'll need to avoid them.
$isBadIE = preg_match( '/MSIE [1-7]\./i', $this->getRequest()->getHeader( 'User-Agent' ) );
*
* @param string|array|Status $errors
*
+ * @deprecated since 1.28, use getErrorsOrWarnings() instead
+ *
* @return string
*/
public function getErrors( $errors ) {
- if ( $errors instanceof Status ) {
- if ( $errors->isOK() ) {
- $errorstr = '';
+ wfDeprecated( __METHOD__ );
+ return $this->getErrorsOrWarnings( $errors, 'error' );
+ }
+
+ /**
+ * Returns a formatted list of errors or warnings from the given elements.
+ *
+ * @param string|array|Status $elements The set of errors/warnings to process.
+ * @param string $elementsType Should warnings or errors be returned. This is meant
+ * for Status objects, all other valid types are always considered as errors.
+ * @return string
+ */
+ public function getErrorsOrWarnings( $elements, $elementsType ) {
+ if ( !in_array( $elementsType, [ 'error', 'warning' ], true ) ) {
+ throw new DomainException( $elementsType . ' is not a valid type.' );
+ }
+ $elementstr = false;
+ if ( $elements instanceof Status ) {
+ list( $errorStatus, $warningStatus ) = $elements->splitByErrorType();
+ $status = $elementsType === 'error' ? $errorStatus : $warningStatus;
+ if ( $status->isGood() ) {
+ $elementstr = '';
} else {
- $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
+ $elementstr = $this->getOutput()->parse(
+ $status->getWikiText()
+ );
}
- } elseif ( is_array( $errors ) ) {
- $errorstr = $this->formatErrors( $errors );
- } else {
- $errorstr = $errors;
+ } elseif ( is_array( $elements ) && $elementsType === 'error' ) {
+ $elementstr = $this->formatErrors( $elements );
+ } elseif ( $elementsType === 'error' ) {
+ $elementstr = $elements;
}
- return $errorstr
- ? Html::rawElement( 'div', [ 'class' => 'error' ], $errorstr )
+ return $elementstr
+ ? Html::rawElement( 'div', [ 'class' => $elementsType ], $elementstr )
: '';
}
return $this;
}
+ /**
+ * Set an internal identifier for this form. It will be submitted as a hidden form field, allowing
+ * HTMLForm to determine whether the form was submitted (or merely viewed). Setting this serves
+ * two purposes:
+ *
+ * - If you use two or more forms on one page, it allows HTMLForm to identify which of the forms
+ * was submitted, and not attempt to validate the other ones.
+ * - If you use checkbox or multiselect fields inside a form using the GET method, it allows
+ * HTMLForm to distinguish between the initial page view and a form submission with all
+ * checkboxes or select options unchecked.
+ *
+ * @since 1.28
+ * @param string $ident
+ * @return $this
+ */
+ public function setFormIdentifier( $ident ) {
+ $this->mFormIdentifier = $ident;
+
+ return $this;
+ }
+
/**
* Stop a default submit button being shown for this form. This implies that an
* alternate submit method must be provided manually.
return $this;
}
+ /**
+ * Show a cancel button (or prevent it). The button is not shown by default.
+ * @param bool $show
+ * @return HTMLForm $this for chaining calls
+ * @since 1.27
+ */
+ public function showCancel( $show = true ) {
+ $this->mShowCancel = $show;
+ return $this;
+ }
+
+ /**
+ * Sets the target where the user is redirected to after clicking cancel.
+ * @param Title|string $target Target as a Title object or an URL
+ * @return HTMLForm $this for chaining calls
+ * @since 1.27
+ */
+ public function setCancelTarget( $target ) {
+ $this->mCancelTarget = $target;
+ return $this;
+ }
+
/**
* Set the id of the \<table\> or outermost \<div\> element.
*