From 9db190c7e736ec8d063187d4241b59feaf7dc2d1 Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Wed, 22 Jun 2011 11:28:20 +0200 Subject: update to MediaWiki 1.17.0 --- includes/HTMLForm.php | 745 +++++++++++++++++++++++++++++++------------------- 1 file changed, 470 insertions(+), 275 deletions(-) (limited to 'includes/HTMLForm.php') diff --git a/includes/HTMLForm.php b/includes/HTMLForm.php index 12687dc4..be912daf 100644 --- a/includes/HTMLForm.php +++ b/includes/HTMLForm.php @@ -1,48 +1,52 @@ $info, * where $info is an Associative Array with any of the following: - * - * 'class' -- the subclass of HTMLFormField that will be used - * to create the object. *NOT* the CSS class! - * 'type' -- roughly translates into the type attribute. + * if 'class' is not specified, this is used as a map + * through HTMLForm::$typeMappings to get the class name. + * 'default' -- default value when the form is displayed + * 'id' -- HTML id attribute + * 'cssclass' -- CSS class + * 'options' -- varies according to the specific object. + * 'label-message' -- message key for a message to use as the label. + * can be an array of msg key and then parameters to + * the message. + * 'label' -- alternatively, a raw text message. Overridden by + * label-message + * '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. + * 'required' -- passed through to the object, indicating that it + * is a required field. + * 'size' -- the length of text fields + * 'filter-callback -- a function name to give you the chance to + * massage the inputted value before it's processed. + * @see HTMLForm::filter() + * 'validation-callback' -- a function name to give you the chance + * to impose extra validation on the field input. + * @see HTMLForm::validate() + * 'name' -- By default, the 'name' attribute of the input field + * is "wp{$fieldname}". If you want a different name + * (eg one without the "wp" prefix), specify it here and + * it will be used without modification. + * * TODO: Document 'section' / 'subsection' stuff */ class HTMLForm { @@ -64,38 +68,40 @@ class HTMLForm { 'submit' => 'HTMLSubmitField', '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. 'email' => 'HTMLTextField', 'password' => 'HTMLTextField', ); - + protected $mMessagePrefix; protected $mFlatFields; protected $mFieldTree; protected $mShowReset = false; public $mFieldData; - + protected $mSubmitCallback; protected $mValidationErrorMessage; - + protected $mPre = ''; protected $mHeader = ''; + protected $mFooter = ''; protected $mPost = ''; protected $mId; - + protected $mSubmitID; protected $mSubmitName; protected $mSubmitText; protected $mSubmitTooltip; protected $mTitle; - + protected $mMethod = 'post'; + protected $mUseMultipart = false; protected $mHiddenFields = array(); protected $mButtons = array(); - + protected $mWrapperLegend = false; /** @@ -103,31 +109,30 @@ class HTMLForm { * @param $descriptor Array of Field constructs, as described above * @param $messagePrefix String a prefix to go in front of default messages */ - public function __construct( $descriptor, $messagePrefix='' ) { + public function __construct( $descriptor, $messagePrefix = '' ) { $this->mMessagePrefix = $messagePrefix; // Expand out into a tree. $loadedDescriptor = array(); $this->mFlatFields = array(); - foreach( $descriptor as $fieldname => $info ) { - $section = ''; - if ( isset( $info['section'] ) ) - $section = $info['section']; + foreach ( $descriptor as $fieldname => $info ) { + $section = isset( $info['section'] ) + ? $info['section'] + : ''; - $info['name'] = $fieldname; - - if ( isset( $info['type'] ) && $info['type'] == 'file' ) + if ( isset( $info['type'] ) && $info['type'] == 'file' ) { $this->mUseMultipart = true; + } - $field = self::loadInputFromParameters( $info ); + $field = self::loadInputFromParameters( $fieldname, $info ); $field->mParent = $this; $setSection =& $loadedDescriptor; - if( $section ) { + if ( $section ) { $sectionParts = explode( '/', $section ); - while( count( $sectionParts ) ) { + while ( count( $sectionParts ) ) { $newName = array_shift( $sectionParts ); if ( !isset( $setSection[$newName] ) ) { @@ -146,15 +151,15 @@ class HTMLForm { } /** - * Add the HTMLForm-specific JavaScript, if it hasn't been + * Add the HTMLForm-specific JavaScript, if it hasn't been * done already. */ static function addJS() { - if( self::$jsAdded ) return; + if ( self::$jsAdded ) return; - global $wgOut, $wgStylePath; + global $wgOut; - $wgOut->addScriptFile( "$wgStylePath/common/htmlform.js" ); + $wgOut->addModules( 'mediawiki.legacy.htmlform' ); } /** @@ -162,7 +167,7 @@ class HTMLForm { * @param $descriptor input Descriptor, as described above * @return HTMLFormField subclass */ - static function loadInputFromParameters( $descriptor ) { + static function loadInputFromParameters( $fieldname, $descriptor ) { if ( isset( $descriptor['class'] ) ) { $class = $descriptor['class']; } elseif ( isset( $descriptor['type'] ) ) { @@ -170,9 +175,11 @@ class HTMLForm { $descriptor['class'] = $class; } - if( !$class ) { + if ( !$class ) { throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) ); } + + $descriptor['fieldname'] = $fieldname; $obj = new $class( $descriptor ); @@ -180,31 +187,50 @@ class HTMLForm { } /** - * The here's-one-I-made-earlier option: do the submission if - * posted, or display the form with or without funky valiation - * errors - * @return Bool whether submission was successful. + * Prepare form for submission */ - function show() { - $html = ''; + function prepareForm() { + # Check if we have the info we need + if ( ! $this->mTitle ) { + throw new MWException( "You must call setTitle() on an HTMLForm" ); + } + // FIXME shouldn't this be closer to displayForm() ? self::addJS(); # Load data from the request. $this->loadData(); + } - # Try a submission + /** + * Try submitting, with edit token check first + * @return Status|boolean + */ + function tryAuthorizedSubmit() { global $wgUser, $wgRequest; $editToken = $wgRequest->getVal( 'wpEditToken' ); $result = false; - if ( $wgUser->matchEditToken( $editToken ) ) + if ( $this->getMethod() != 'post' || $wgUser->matchEditToken( $editToken ) ) { $result = $this->trySubmit(); + } + return $result; + } + + /** + * The here's-one-I-made-earlier option: do the submission if + * posted, or display the form with or without funky valiation + * errors + * @return Bool or Status whether submission was successful. + */ + function show() { + $this->prepareForm(); - if( $result === true ) + $result = $this->tryAuthorizedSubmit(); + if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){ return $result; + } - # Display form. $this->displayForm( $result ); return false; } @@ -213,19 +239,20 @@ class HTMLForm { * Validate all the fields, and call the submision callback * function if everything is kosher. * @return Mixed Bool true == Successful submission, Bool false - * == No submission attempted, anything else == Error to + * == No submission attempted, anything else == Error to * display. */ function trySubmit() { # Check for validation - foreach( $this->mFlatFields as $fieldname => $field ) { - if ( !empty( $field->mParams['nodata'] ) ) + foreach ( $this->mFlatFields as $fieldname => $field ) { + if ( !empty( $field->mParams['nodata'] ) ) { continue; - if ( $field->validate( + } + if ( $field->validate( $this->mFieldData[$fieldname], - $this->mFieldData ) - !== true ) - { + $this->mFieldData ) + !== true + ) { return isset( $this->mValidationErrorMessage ) ? $this->mValidationErrorMessage : array( 'htmlform-invalid-input' ); @@ -254,14 +281,14 @@ class HTMLForm { } /** - * Set a message to display on a validation error. + * Set a message to display on a validation error. * @param $msg Mixed String or Array of valid inputs to wfMsgExt() * (so each entry can be either a String or Array) */ function setValidationErrorMessage( $msg ) { $this->mValidationErrorMessage = $msg; } - + /** * Set the introductory message, overwriting any existing message. * @param $msg String complete text of message to display @@ -273,52 +300,58 @@ class HTMLForm { * @param $msg String complete text of message to display */ function addPreText( $msg ) { $this->mPre .= $msg; } - + /** * Add header text, inside the form. * @param $msg String complete text of message to display */ function addHeaderText( $msg ) { $this->mHeader .= $msg; } - + + /** + * Add footer text, inside the form. + * @param $msg String complete text of message to display + */ + function addFooterText( $msg ) { $this->mFooter .= $msg; } + /** * Add text to the end of the display. * @param $msg String complete text of message to display */ function addPostText( $msg ) { $this->mPost .= $msg; } - + /** * Add a hidden field to the output - * @param $name String field name + * @param $name String field name. This will be used exactly as entered * @param $value String field value + * @param $attribs Array */ - public function addHiddenField( $name, $value ){ - $this->mHiddenFields[ $name ] = $value; + public function addHiddenField( $name, $value, $attribs = array() ) { + $attribs += array( 'name' => $name ); + $this->mHiddenFields[] = array( $value, $attribs ); } - - public function addButton( $name, $value, $id=null, $attribs=null ){ + + public function addButton( $name, $value, $id = null, $attribs = null ) { $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' ); } /** - * 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 ) { global $wgOut; - if ( $submitResult !== false ) { - $this->displayErrors( $submitResult ); - } - # For good measure (it is the default) $wgOut->preventClickjacking(); $html = '' + . $this->getErrors( $submitResult ) . $this->mHeader . $this->getBody() . $this->getHiddenFields() . $this->getButtons() + . $this->mFooter ; $html = $this->wrapForm( $html ); @@ -336,25 +369,26 @@ class HTMLForm { * @return String wrapped HTML. */ function wrapForm( $html ) { - + # Include a
wrapper for style, if requested. - if ( $this->mWrapperLegend !== false ){ + if ( $this->mWrapperLegend !== false ) { $html = Xml::fieldset( $this->mWrapperLegend, $html ); } # Use multipart/form-data - $encType = $this->mUseMultipart + $encType = $this->mUseMultipart ? 'multipart/form-data' : 'application/x-www-form-urlencoded'; # Attributes $attribs = array( 'action' => $this->getTitle()->getFullURL(), - 'method' => 'post', + 'method' => $this->mMethod, 'class' => 'visualClear', - 'enctype' => $encType, + 'enctype' => $encType, ); - if ( !empty( $this->mId ) ) + if ( !empty( $this->mId ) ) { $attribs['id'] = $this->mId; - + } + return Html::rawElement( 'form', $attribs, $html ); } @@ -364,13 +398,17 @@ class HTMLForm { */ function getHiddenFields() { global $wgUser; - $html = ''; - $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; - $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; + $html = ''; - foreach( $this->mHiddenFields as $name => $value ){ - $html .= Html::hidden( $name, $value ) . "\n"; + if( $this->getMethod() == 'post' ){ + $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; + $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; + } + + foreach ( $this->mHiddenFields as $data ) { + list( $value, $attribs ) = $data; + $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n"; } return $html; @@ -382,13 +420,16 @@ class HTMLForm { */ function getButtons() { $html = ''; - $attribs = array(); - if ( isset( $this->mSubmitID ) ) + if ( isset( $this->mSubmitID ) ) { $attribs['id'] = $this->mSubmitID; - if ( isset( $this->mSubmitName ) ) + } + + if ( isset( $this->mSubmitName ) ) { $attribs['name'] = $this->mSubmitName; + } + if ( isset( $this->mSubmitTooltip ) ) { global $wgUser; $attribs += $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mSubmitTooltip ); @@ -398,7 +439,7 @@ class HTMLForm { $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; - if( $this->mShowReset ) { + if ( $this->mShowReset ) { $html .= Html::element( 'input', array( @@ -407,20 +448,25 @@ class HTMLForm { ) ) . "\n"; } - - foreach( $this->mButtons as $button ){ + + foreach ( $this->mButtons as $button ) { $attrs = array( 'type' => 'submit', 'name' => $button['name'], 'value' => $button['value'] ); - if ( $button['attribs'] ) - $attrs += $button['attribs']; - if( isset( $button['id'] ) ) + + if ( $button['attribs'] ) { + $attrs += $button['attribs']; + } + + if ( isset( $button['id'] ) ) { $attrs['id'] = $button['id']; + } + $html .= Html::element( 'input', $attrs ); } - + return $html; } @@ -434,18 +480,25 @@ class HTMLForm { /** * Format and display an error message stack. * @param $errors Mixed String or Array of message keys + * @return String */ - function displayErrors( $errors ) { - if ( is_array( $errors ) ) { + function getErrors( $errors ) { + if ( $errors instanceof Status ) { + global $wgOut; + if ( $errors->isOK() ) { + $errorstr = ''; + } else { + $errorstr = $wgOut->parse( $errors->getWikiText() ); + } + } elseif ( is_array( $errors ) ) { $errorstr = $this->formatErrors( $errors ); } else { $errorstr = $errors; } - - $errorstr = Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr ); - global $wgOut; - $wgOut->addHTML( $errorstr ); + return $errorstr + ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr ) + : ''; } /** @@ -455,13 +508,15 @@ class HTMLForm { */ static function formatErrors( $errors ) { $errorstr = ''; + foreach ( $errors as $error ) { - if( is_array( $error ) ) { + if ( is_array( $error ) ) { $msg = array_shift( $error ); } else { $msg = $error; $error = array(); } + $errorstr .= Html::rawElement( 'li', null, @@ -488,27 +543,26 @@ class HTMLForm { */ function getSubmitText() { return $this->mSubmitText - ? $this->mSubmitText + ? $this->mSubmitText : wfMsg( 'htmlform-submit' ); } - + public function setSubmitName( $name ) { $this->mSubmitName = $name; } - + public function setSubmitTooltip( $name ) { $this->mSubmitTooltip = $name; } - /** - * Set the id for the submit button. + * Set the id for the submit button. * @param $t String. FIXME: Integrity is *not* validated */ function setSubmitID( $t ) { $this->mSubmitID = $t; } - + public function setId( $id ) { $this->mId = $id; } @@ -518,11 +572,11 @@ class HTMLForm { * @param $legend String HTML to go inside the element. * Will be escaped */ - public function setWrapperLegend( $legend ){ $this->mWrapperLegend = $legend; } + public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; } /** * Set the prefix for various default messages - * TODO: currently only used for the
legend on forms + * TODO: currently only used for the
legend on forms * with multiple sections; should be used elsewhre? * @param $p String */ @@ -545,6 +599,18 @@ class HTMLForm { function getTitle() { return $this->mTitle; } + + /** + * Set the method used to submit the form + * @param $method String + */ + public function setMethod( $method='post' ){ + $this->mMethod = $method; + } + + public function getMethod(){ + return $this->mMethod; + } /** * TODO: Document @@ -555,14 +621,14 @@ class HTMLForm { $subsectionHtml = ''; $hasLeftColumn = false; - foreach( $fields as $key => $value ) { + foreach ( $fields as $key => $value ) { if ( is_object( $value ) ) { $v = empty( $value->mParams['nodata'] ) ? $this->mFieldData[$key] : $value->getDefault(); $tableHtml .= $value->getTableRow( $v ); - if( $value->getLabel() != ' ' ) + if ( $value->getLabel() != ' ' ) $hasLeftColumn = true; } elseif ( is_array( $value ) ) { $section = $this->displaySection( $value, $key ); @@ -572,13 +638,18 @@ class HTMLForm { } $classes = array(); - if( !$hasLeftColumn ) // Avoid strange spacing when no labels exist + + if ( !$hasLeftColumn ) { // Avoid strange spacing when no labels exist $classes[] = 'mw-htmlform-nolabel'; + } + $attribs = array( - 'class' => implode( ' ', $classes ), + 'class' => implode( ' ', $classes ), ); - if ( $sectionName ) + + if ( $sectionName ) { $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" ); + } $tableHtml = Html::rawElement( 'table', $attribs, Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n"; @@ -594,9 +665,10 @@ class HTMLForm { $fieldData = array(); - foreach( $this->mFlatFields as $fieldname => $field ) { - if ( !empty( $field->mParams['nodata'] ) ) continue; - if ( !empty( $field->mParams['disabled'] ) ) { + foreach ( $this->mFlatFields as $fieldname => $field ) { + if ( !empty( $field->mParams['nodata'] ) ) { + continue; + } elseif ( !empty( $field->mParams['disabled'] ) ) { $fieldData[$fieldname] = $field->getDefault(); } else { $fieldData[$fieldname] = $field->loadDataFromRequest( $wgRequest ); @@ -604,7 +676,7 @@ class HTMLForm { } # Filter data. - foreach( $fieldData as $name => &$value ) { + foreach ( $fieldData as $name => &$value ) { $field = $this->mFlatFields[$name]; $value = $field->filter( $value, $this->mFlatFields ); } @@ -614,7 +686,7 @@ class HTMLForm { /** * Stop a reset button being shown for this form - * @param $suppressReset Bool set to false to re-enable the + * @param $suppressReset Bool set to false to re-enable the * button again */ function suppressReset( $suppressReset = true ) { @@ -623,9 +695,9 @@ class HTMLForm { /** * Overload this if you want to apply special filtration routines - * to the form as a whole, after it's submitted but before it's + * to the form as a whole, after it's submitted but before it's * processed. - * @param $data + * @param $data * @return unknown_type */ function filterDataForSubmit( $data ) { @@ -634,36 +706,37 @@ class HTMLForm { } /** - * The parent class to generate form fields. Any field type should + * The parent class to generate form fields. Any field type should * be a subclass of this. */ abstract class HTMLFormField { - + protected $mValidationCallback; protected $mFilterCallback; protected $mName; public $mParams; protected $mLabel; # String label. Set on construction protected $mID; + protected $mClass = ''; protected $mDefault; public $mParent; - + /** * This function must be implemented to return the HTML to generate * the input object itself. It should not implement the surrounding * table cells/rows, or labels/help messages. * @param $value String the value to set the input to; eg a default - * text for a text input. + * text for a text input. * @return String valid HTML. */ abstract function getInputHTML( $value ); /** - * Override this function to add specific validation checks on the + * Override this function to add specific validation checks on the * field input. Don't forget to call parent::validate() to ensure * that the user-defined callback mValidationCallback is still run * @param $value String the value the field was submitted with - * @param $alldata $all the data collected from the form + * @param $alldata Array the data collected from the form * @return Mixed Bool true on success, or String error to display. */ function validate( $value, $alldata ) { @@ -671,11 +744,15 @@ abstract class HTMLFormField { return call_user_func( $this->mValidationCallback, $value, $alldata ); } + if ( isset( $this->mParams['required'] ) && $value === '' ) { + return wfMsgExt( 'htmlform-required', 'parseinline' ); + } + return true; } function filter( $value, $alldata ) { - if( isset( $this->mFilterCallback ) ) { + if ( isset( $this->mFilterCallback ) ) { $value = call_user_func( $this->mFilterCallback, $value, $alldata ); } @@ -699,7 +776,7 @@ abstract class HTMLFormField { * @return String the value */ function loadDataFromRequest( $request ) { - if( $request->getCheck( $this->mName ) ) { + if ( $request->getCheck( $this->mName ) ) { return $request->getText( $this->mName ); } else { return $this->getDefault(); @@ -714,7 +791,7 @@ abstract class HTMLFormField { $this->mParams = $params; # Generate the label from a message, if possible - if( isset( $params['label-message'] ) ) { + if ( isset( $params['label-message'] ) ) { $msgInfo = $params['label-message']; if ( is_array( $msgInfo ) ) { @@ -729,15 +806,17 @@ abstract class HTMLFormField { $this->mLabel = $params['label']; } + $this->mName = "wp{$params['fieldname']}"; if ( isset( $params['name'] ) ) { - $name = $params['name']; - $validName = Sanitizer::escapeId( $name ); - if( $name != $validName ) { - throw new MWException("Invalid name '$name' passed to " . __METHOD__ ); - } - $this->mName = 'wp'.$name; - $this->mID = 'mw-input-'.$name; + $this->mName = $params['name']; } + + $validName = Sanitizer::escapeId( $this->mName ); + if ( $this->mName != $validName && !isset( $params['nodata'] ) ) { + throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ ); + } + + $this->mID = "mw-input-{$this->mName}"; if ( isset( $params['default'] ) ) { $this->mDefault = $params['default']; @@ -746,12 +825,18 @@ abstract class HTMLFormField { if ( isset( $params['id'] ) ) { $id = $params['id']; $validId = Sanitizer::escapeId( $id ); - if( $id != $validId ) { - throw new MWException("Invalid id '$id' passed to " . __METHOD__ ); + + if ( $id != $validId ) { + throw new MWException( "Invalid id '$id' passed to " . __METHOD__ ); } + $this->mID = $id; } + if ( isset( $params['cssclass'] ) ) { + $this->mClass = $params['cssclass']; + } + if ( isset( $params['validation-callback'] ) ) { $this->mValidationCallback = $params['validation-callback']; } @@ -772,22 +857,44 @@ abstract class HTMLFormField { global $wgRequest; $errors = $this->validate( $value, $this->mParent->mFieldData ); - if ( $errors === true || !$wgRequest->wasPosted() ) { + + $cellAttributes = array(); + $verticalLabel = false; + + if ( !empty($this->mParams['vertical-label']) ) { + $cellAttributes['colspan'] = 2; + $verticalLabel = true; + } + + if ( $errors === true || ( !$wgRequest->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) { $errors = ''; } else { $errors = Html::rawElement( 'span', array( 'class' => 'error' ), $errors ); } - $html = $this->getLabelHtml(); - $html .= Html::rawElement( 'td', array( 'class' => 'mw-input' ), - $this->getInputHTML( $value ) ."\n$errors" ); - + $label = $this->getLabelHtml( $cellAttributes ); + $field = Html::rawElement( + 'td', + array( 'class' => 'mw-input' ) + $cellAttributes, + $this->getInputHTML( $value ) . "\n$errors" + ); + $fieldType = get_class( $this ); - - $html = Html::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ), - $html ) . "\n"; + + if ($verticalLabel) { + $html = Html::rawElement( 'tr', + array( 'class' => 'mw-htmlform-vertical-label' ), $label ); + $html .= Html::rawElement( 'tr', + array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ), + $field ); + } else { + $html = Html::rawElement( 'tr', + array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ), + $label . $field ); + } $helptext = null; + if ( isset( $this->mParams['help-message'] ) ) { $msg = $this->mParams['help-message']; $helptext = wfMsgExt( $msg, 'parseinline' ); @@ -812,16 +919,18 @@ abstract class HTMLFormField { function getLabel() { return $this->mLabel; } - function getLabelHtml() { + function getLabelHtml( $cellAttributes = array() ) { # Don't output a for= attribute for labels with no associated input. # Kind of hacky here, possibly we don't want these to be