diff options
Diffstat (limited to 'includes/HTMLForm.php')
-rw-r--r-- | includes/HTMLForm.php | 289 |
1 files changed, 209 insertions, 80 deletions
diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 948de61f..7326bf5c 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -33,10 +33,10 @@ * 'help-message' -- message key for a message to use as a help text. * can be an array of msg key and then parameters to * the message. - * Overwrites 'help-messages'. - * 'help-messages' -- array of message key. As above, each item can - * be an array of msg key and then parameters. - * Overwrites 'help-message'. + * Overwrites 'help-messages'. + * 'help-messages' -- array of message key. As above, each item can + * be an array of msg key and then parameters. + * Overwrites 'help-message'. * 'required' -- passed through to the object, indicating that it * is a required field. * 'size' -- the length of text fields @@ -53,9 +53,9 @@ * * TODO: Document 'section' / 'subsection' stuff */ -class HTMLForm { +class HTMLForm extends ContextSource { - # A mapping of 'type' inputs onto standard HTMLFormField subclasses + // A mapping of 'type' inputs onto standard HTMLFormField subclasses static $typeMappings = array( 'text' => 'HTMLTextField', 'textarea' => 'HTMLTextAreaField', @@ -73,15 +73,18 @@ class HTMLForm { 'hidden' => 'HTMLHiddenField', 'edittools' => 'HTMLEditTools', - # HTMLTextField will output the correct type="" attribute automagically. - # There are about four zillion other HTML5 input types, like url, but - # we don't use those at the moment, so no point in adding all of them. + // HTMLTextField will output the correct type="" attribute automagically. + // There are about four zillion other HTML5 input types, like url, but + // we don't use those at the moment, so no point in adding all of them. 'email' => 'HTMLTextField', 'password' => 'HTMLTextField', ); protected $mMessagePrefix; + + /** @var HTMLFormField[] */ protected $mFlatFields; + protected $mFieldTree; protected $mShowReset = false; public $mFieldData; @@ -102,15 +105,30 @@ class HTMLForm { protected $mSubmitText; protected $mSubmitTooltip; - protected $mContext; // <! IContextSource protected $mTitle; protected $mMethod = 'post'; + /** + * Form action URL. false means we will use the URL to set Title + * @since 1.19 + * @var false|string + */ + protected $mAction = false; + protected $mUseMultipart = false; protected $mHiddenFields = array(); protected $mButtons = array(); protected $mWrapperLegend = false; + + /** + * If true, sections that contain both fields and subsections will + * render their subsections before their fields. + * + * Subclasses may set this to false to render subsections after fields + * instead. + */ + protected $mSubSectionBeforeFields = true; /** * Build a new HTMLForm from an array of field attributes @@ -121,7 +139,7 @@ class HTMLForm { */ public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) { if( $context instanceof IContextSource ){ - $this->mContext = $context; + $this->setContext( $context ); $this->mTitle = false; // We don't need them to set a title $this->mMessagePrefix = $messagePrefix; } else { @@ -175,11 +193,12 @@ class HTMLForm { * done already. * @deprecated since 1.18 load modules with ResourceLoader instead */ - static function addJS() { } + static function addJS() { wfDeprecated( __METHOD__, '1.18' ); } /** * Initialise a new Object for the field - * @param $descriptor input Descriptor, as described above + * @param $fieldname string + * @param $descriptor string input Descriptor, as described above * @return HTMLFormField subclass */ static function loadInputFromParameters( $fieldname, $descriptor ) { @@ -221,12 +240,27 @@ class HTMLForm { * @return Status|boolean */ function tryAuthorizedSubmit() { - $editToken = $this->getRequest()->getVal( 'wpEditToken' ); - $result = false; - if ( $this->getMethod() != 'post' || $this->getUser()->matchEditToken( $editToken ) ) { + + $submit = false; + if ( $this->getMethod() != 'post' ) { + $submit = 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 ); + } else { + $submit = true; + } + } + + if ( $submit ) { $result = $this->trySubmit(); } + return $result; } @@ -276,7 +310,7 @@ class HTMLForm { $data = $this->filterDataForSubmit( $this->mFieldData ); - $res = call_user_func( $callback, $data ); + $res = call_user_func( $callback, $data, $this ); return $res; } @@ -306,7 +340,16 @@ class HTMLForm { * Set the introductory message, overwriting any existing message. * @param $msg String complete text of message to display */ - function setIntro( $msg ) { $this->mPre = $msg; } + function setIntro( $msg ) { + $this->setPreText( $msg ); + } + + /** + * Set the introductory message, overwriting any existing message. + * @since 1.19 + * @param $msg String complete text of message to display + */ + function setPreText( $msg ) { $this->mPre = $msg; } /** * Add introductory text. @@ -317,7 +360,7 @@ class HTMLForm { /** * Add header text, inside the form. * @param $msg String complete text of message to display - * @param $section The section to add the header to + * @param $section string The section to add the header to */ function addHeaderText( $msg, $section = null ) { if ( is_null( $section ) ) { @@ -331,6 +374,20 @@ class HTMLForm { } /** + * Set header text, inside the form. + * @since 1.19 + * @param $msg String complete text of message to display + * @param $section The section to add the header to + */ + function setHeaderText( $msg, $section = null ) { + if ( is_null( $section ) ) { + $this->mHeader = $msg; + } else { + $this->mSectionHeaders[$section] = $msg; + } + } + + /** * Add footer text, inside the form. * @param $msg String complete text of message to display * @param $section string The section to add the footer text to @@ -347,12 +404,32 @@ class HTMLForm { } /** + * Set footer text, inside the form. + * @since 1.19 + * @param $msg String complete text of message to display + * @param $section string The section to add the footer text to + */ + function setFooterText( $msg, $section = null ) { + if ( is_null( $section ) ) { + $this->mFooter = $msg; + } else { + $this->mSectionFooters[$section] = $msg; + } + } + + /** * Add text to the end of the display. * @param $msg String complete text of message to display */ function addPostText( $msg ) { $this->mPost .= $msg; } /** + * Set text at the end of the display. + * @param $msg String complete text of message to display + */ + function setPostText( $msg ) { $this->mPost = $msg; } + + /** * Add a hidden field to the output * @param $name String field name. This will be used exactly as entered * @param $value String field value @@ -368,11 +445,20 @@ class HTMLForm { } /** - * Display the form (sending to wgOut), with an appropriate error + * Display the form (sending to $wgOut), with an appropriate error * message or stack of messages, and any validation errors, etc. * @param $submitResult Mixed output from HTMLForm::trySubmit() */ function displayForm( $submitResult ) { + $this->getOutput()->addHTML( $this->getHTML( $submitResult ) ); + } + + /** + * Returns the raw HTML generated by the form + * @param $submitResult Mixed output from HTMLForm::trySubmit() + * @return string + */ + function getHTML( $submitResult ) { # For good measure (it is the default) $this->getOutput()->preventClickjacking(); $this->getOutput()->addModules( 'mediawiki.htmlform' ); @@ -388,11 +474,7 @@ class HTMLForm { $html = $this->wrapForm( $html ); - $this->getOutput()->addHTML( '' - . $this->mPre - . $html - . $this->mPost - ); + return '' . $this->mPre . $html . $this->mPost; } /** @@ -412,7 +494,7 @@ class HTMLForm { : 'application/x-www-form-urlencoded'; # Attributes $attribs = array( - 'action' => $this->getTitle()->getFullURL(), + 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction, 'method' => $this->mMethod, 'class' => 'visualClear', 'enctype' => $encType, @@ -433,7 +515,7 @@ class HTMLForm { $html = ''; if( $this->getMethod() == 'post' ){ - $html .= Html::hidden( 'wpEditToken', $this->getUser()->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; + $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; } @@ -506,6 +588,7 @@ class HTMLForm { /** * Get the whole body of the form. + * @return String */ function getBody() { return $this->displaySection( $this->mFieldTree ); @@ -571,6 +654,15 @@ class HTMLForm { } /** + * Set the text for the submit button to a message + * @since 1.19 + * @param $msg String message key + */ + public function setSubmitTextMsg( $msg ) { + return $this->setSubmitText( $this->msg( $msg )->escaped() ); + } + + /** * Get the text for the submit button, either customised or a default. * @return unknown_type */ @@ -609,6 +701,16 @@ class HTMLForm { public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; } /** + * Prompt the whole form to be wrapped in a <fieldset>, with + * this message as its <legend> element. + * @since 1.19 + * @param $msg String message key + */ + public function setWrapperLegendMsg( $msg ) { + return $this->setWrapperLegend( $this->msg( $msg )->escaped() ); + } + + /** * Set the prefix for various default messages * TODO: currently only used for the <fieldset> legend on forms * with multiple sections; should be used elsewhre? @@ -637,36 +739,6 @@ class HTMLForm { } /** - * @return IContextSource - */ - public function getContext(){ - return $this->mContext instanceof IContextSource - ? $this->mContext - : RequestContext::getMain(); - } - - /** - * @return OutputPage - */ - public function getOutput(){ - return $this->getContext()->getOutput(); - } - - /** - * @return WebRequest - */ - public function getRequest(){ - return $this->getContext()->getRequest(); - } - - /** - * @return User - */ - public function getUser(){ - return $this->getContext()->getUser(); - } - - /** * Set the method used to submit the form * @param $method String */ @@ -680,9 +752,10 @@ class HTMLForm { /** * TODO: Document - * @param $fields array of fields (either arrays or objects) + * @param $fields array[]|HTMLFormField[] array of fields (either arrays or objects) * @param $sectionName string ID attribute of the <table> tag for this section, ignored if empty * @param $fieldsetIDPrefix string ID prefix for the <fieldset> tag of each subsection, ignored if empty + * @return String */ function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) { $tableHtml = ''; @@ -696,8 +769,9 @@ class HTMLForm { : $value->getDefault(); $tableHtml .= $value->getTableRow( $v ); - if ( $value->getLabel() != ' ' ) + if ( $value->getLabel() != ' ' ) { $hasLeftColumn = true; + } } elseif ( is_array( $value ) ) { $section = $this->displaySection( $value, $key ); $legend = $this->getLegend( $key ); @@ -732,7 +806,11 @@ class HTMLForm { $tableHtml = Html::rawElement( 'table', $attribs, Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n"; - return $subsectionHtml . "\n" . $tableHtml; + if ( $this->mSubSectionBeforeFields ) { + return $subsectionHtml . "\n" . $tableHtml; + } else { + return $tableHtml . "\n" . $subsectionHtml; + } } /** @@ -789,6 +867,19 @@ class HTMLForm { public function getLegend( $key ) { return wfMsg( "{$this->mMessagePrefix}-$key" ); } + + /** + * Set the value for the action attribute of the form. + * When set to false (which is the default state), the set title is used. + * + * @since 1.19 + * + * @param string|false $action + */ + public function setAction( $action ) { + $this->mAction = $action; + } + } /** @@ -830,20 +921,20 @@ abstract class HTMLFormField { * @return Mixed Bool true on success, or String error to display. */ function validate( $value, $alldata ) { - if ( isset( $this->mValidationCallback ) ) { - return call_user_func( $this->mValidationCallback, $value, $alldata ); - } - if ( isset( $this->mParams['required'] ) && $value === '' ) { return wfMsgExt( 'htmlform-required', 'parseinline' ); } + if ( isset( $this->mValidationCallback ) ) { + return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent ); + } + return true; } function filter( $value, $alldata ) { if ( isset( $this->mFilterCallback ) ) { - $value = call_user_func( $this->mFilterCallback, $value, $alldata ); + $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent ); } return $value; @@ -934,6 +1025,10 @@ abstract class HTMLFormField { if ( isset( $params['filter-callback'] ) ) { $this->mFilterCallback = $params['filter-callback']; } + + if ( isset( $params['flatlist'] ) ){ + $this->mClass .= ' mw-htmlform-flatlist'; + } } /** @@ -1055,7 +1150,7 @@ abstract class HTMLFormField { /** * flatten an array of options to a single array, for instance, * a set of <options> inside <optgroups>. - * @param $options Associative Array with values either Strings + * @param $options array Associative Array with values either Strings * or Arrays * @return Array flattened input */ @@ -1118,6 +1213,10 @@ class HTMLTextField extends HTMLFormField { 'value' => $value, ) + $this->getTooltipAndAccessKey(); + if ( $this->mClass !== '' ) { + $attribs['class'] = $this->mClass; + } + if ( isset( $this->mParams['maxlength'] ) ) { $attribs['maxlength'] = $this->mParams['maxlength']; } @@ -1188,7 +1287,10 @@ class HTMLTextAreaField extends HTMLFormField { 'rows' => $this->getRows(), ) + $this->getTooltipAndAccessKey(); - + if ( $this->mClass !== '' ) { + $attribs['class'] = $this->mClass; + } + if ( !empty( $this->mParams['disabled'] ) ) { $attribs['disabled'] = 'disabled'; } @@ -1295,6 +1397,10 @@ class HTMLCheckField extends HTMLFormField { if ( !empty( $this->mParams['disabled'] ) ) { $attr['disabled'] = 'disabled'; } + + if ( $this->mClass !== '' ) { + $attr['class'] = $this->mClass; + } return Xml::check( $this->mName, $value, $attr ) . ' ' . Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); @@ -1303,6 +1409,7 @@ class HTMLCheckField extends HTMLFormField { /** * For a checkbox, the label goes on the right hand side, and is * added in getInputHTML(), rather than HTMLFormField::getRow() + * @return String */ function getLabel() { return ' '; @@ -1371,6 +1478,10 @@ class HTMLSelectField extends HTMLFormField { if ( !empty( $this->mParams['disabled'] ) ) { $select->setAttribute( 'disabled', 'disabled' ); } + + if ( $this->mClass !== '' ) { + $select->setAttribute( 'class', $this->mClass ); + } $select->addOptions( $this->mParams['options'] ); @@ -1432,6 +1543,10 @@ class HTMLSelectOrOtherField extends HTMLTextField { if ( isset( $this->mParams['maxlength'] ) ) { $tbAttribs['maxlength'] = $this->mParams['maxlength']; } + + if ( $this->mClass !== '' ) { + $tbAttribs['class'] = $this->mClass; + } $textbox = Html::input( $this->mName . '-other', @@ -1467,13 +1582,6 @@ class HTMLSelectOrOtherField extends HTMLTextField { */ class HTMLMultiSelectField extends HTMLFormField { - public function __construct( $params ){ - parent::__construct( $params ); - if( isset( $params['flatlist'] ) ){ - $this->mClass .= ' mw-htmlform-multiselect-flatlist'; - } - } - function validate( $value, $alldata ) { $p = parent::validate( $value, $alldata ); @@ -1525,7 +1633,7 @@ class HTMLMultiSelectField extends HTMLFormField { $attribs + $thisAttribs ); $checkbox .= ' ' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label ); - $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-multiselect-item' ), $checkbox ); + $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox ); } } @@ -1655,6 +1763,10 @@ class HTMLSelectAndOtherField extends HTMLSelectField { 'id' => $this->mID . '-other', 'size' => $this->getSize(), ); + + if ( $this->mClass !== '' ) { + $textAttribs['class'] = $this->mClass; + } foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) { if ( isset( $this->mParams[$param] ) ) { @@ -1696,7 +1808,17 @@ class HTMLSelectAndOtherField extends HTMLSelectField { } else { $final = $this->getDefault(); - $list = $text = ''; + + $list = 'other'; + $text = $final; + foreach ( $this->mFlatOptions as $option ) { + $match = $option . wfMsgForContent( 'colon-separator' ); + if( strpos( $text, $match ) === 0 ) { + $list = $option; + $text = substr( $text, strlen( $match ) ); + break; + } + } } return array( $final, $list, $text ); } @@ -1729,6 +1851,8 @@ class HTMLSelectAndOtherField extends HTMLSelectField { * Radio checkbox fields. */ class HTMLRadioField extends HTMLFormField { + + function validate( $value, $alldata ) { $p = parent::validate( $value, $alldata ); @@ -1752,6 +1876,8 @@ class HTMLRadioField extends HTMLFormField { /** * This returns a block of all the radio options, in one cell. * @see includes/HTMLFormField#getInputHTML() + * @param $value String + * @return String */ function getInputHTML( $value ) { $html = $this->formatOptions( $this->mParams['options'], $value ); @@ -1774,16 +1900,16 @@ class HTMLRadioField extends HTMLFormField { $html .= $this->formatOptions( $info, $value ); } else { $id = Sanitizer::escapeId( $this->mID . "-$info" ); - $html .= Xml::radio( + $radio = Xml::radio( $this->mName, $info, $info == $value, $attribs + array( 'id' => $id ) ); - $html .= ' ' . + $radio .= ' ' . Html::rawElement( 'label', array( 'for' => $id ), $label ); - $html .= "<br />\n"; + $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio ); } } @@ -1864,7 +1990,7 @@ class HTMLSubmitField extends HTMLFormField { return Xml::submitButton( $value, array( - 'class' => 'mw-htmlform-submit', + 'class' => 'mw-htmlform-submit ' . $this->mClass, 'name' => $this->mName, 'id' => $this->mID, ) @@ -1877,6 +2003,9 @@ class HTMLSubmitField extends HTMLFormField { /** * Button cannot be invalid + * @param $value String + * @param $alldata Array + * @return Bool */ public function validate( $value, $alldata ){ return true; |