r64866 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r64865‎ | r64866 | r64867 >
Date:12:03, 10 April 2010
Author:happy-melon
Status:resolved (Comments)
Tags:
Comment:
Improvements and fixes to HTMLForm:
* allow css classes to be specified in form descriptors
* Don't overwrite $field['name'] if it's set
* Allow IDs on hidden fields
* Enforce 'required' validation on Text fields
* Fix type validation on Int fields to correctly handle numbers larger than PHP_MAX_INT or with leading zeroes (eg phone numbers on 32 bit systems)
* Fix weak-typing error in MultiSelect field
* Formatting and doc fixes
Modified paths:
  • /trunk/phase3/includes/HTMLForm.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/HTMLForm.php
@@ -19,11 +19,12 @@
2020 *
2121 * 'class' -- the subclass of HTMLFormField that will be used
2222 * to create the object. *NOT* the CSS class!
23 - * 'type' -- roughly translates into the <select> type attribute.
 23+ * 'type' -- roughly translates into the <select> type attribute.
2424 * if 'class' is not specified, this is used as a map
2525 * through HTMLForm::$typeMappings to get the class name.
2626 * 'default' -- default value when the form is displayed
27 - * 'id' -- HTML id attribute
 27+ * 'id' -- HTML id attribute
 28+ * 'cssclass' -- CSS class
2829 * 'options' -- varies according to the specific object.
2930 * 'label-message' -- message key for a message to use as the label.
3031 * can be an array of msg key and then parameters to
@@ -35,7 +36,7 @@
3637 * the message.
3738 * 'required' -- passed through to the object, indicating that it
3839 * is a required field.
39 - * 'size' -- the length of text fields
 40+ * 'size' -- the length of text fields
4041 * 'filter-callback -- a function name to give you the chance to
4142 * massage the inputted value before it's processed.
4243 * @see HTMLForm::filter()
@@ -111,14 +112,17 @@
112113 $this->mFlatFields = array();
113114
114115 foreach( $descriptor as $fieldname => $info ) {
115 - $section = '';
116 - if ( isset( $info['section'] ) )
117 - $section = $info['section'];
 116+ $section = isset( $info['section'] )
 117+ ? $info['section']
 118+ : '';
118119
119 - $info['name'] = $fieldname;
 120+ $info['name'] = isset( $info['name'] )
 121+ ? $info['name']
 122+ : $fieldname;
120123
121 - if ( isset( $info['type'] ) && $info['type'] == 'file' )
 124+ if ( isset( $info['type'] ) && $info['type'] == 'file' ){
122125 $this->mUseMultipart = true;
 126+ }
123127
124128 $field = self::loadInputFromParameters( $info );
125129 $field->mParent = $this;
@@ -219,8 +223,9 @@
220224 function trySubmit() {
221225 # Check for validation
222226 foreach( $this->mFlatFields as $fieldname => $field ) {
223 - if ( !empty( $field->mParams['nodata'] ) )
 227+ if ( !empty( $field->mParams['nodata'] ) ){
224228 continue;
 229+ }
225230 if ( $field->validate(
226231 $this->mFieldData[$fieldname],
227232 $this->mFieldData )
@@ -290,9 +295,10 @@
291296 * Add a hidden field to the output
292297 * @param $name String field name
293298 * @param $value String field value
 299+ * @param $attribs Array
294300 */
295 - public function addHiddenField( $name, $value ){
296 - $this->mHiddenFields[ $name ] = $value;
 301+ public function addHiddenField( $name, $value, $attribs=array() ){
 302+ $this->mHiddenFields[ $name ] = array( $value, $attribs );
297303 }
298304
299305 public function addButton( $name, $value, $id=null, $attribs=null ){
@@ -367,7 +373,8 @@
368374 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
369375
370376 foreach( $this->mHiddenFields as $name => $value ){
371 - $html .= Html::hidden( $name, $value ) . "\n";
 377+ list( $value, $attribs ) = $value;
 378+ $html .= Html::hidden( $name, $value, $attribs ) . "\n";
372379 }
373380
374381 return $html;
@@ -592,8 +599,9 @@
593600 $fieldData = array();
594601
595602 foreach( $this->mFlatFields as $fieldname => $field ) {
596 - if ( !empty( $field->mParams['nodata'] ) ) continue;
597 - if ( !empty( $field->mParams['disabled'] ) ) {
 603+ if ( !empty( $field->mParams['nodata'] ) ){
 604+ continue;
 605+ } elseif ( !empty( $field->mParams['disabled'] ) ) {
598606 $fieldData[$fieldname] = $field->getDefault();
599607 } else {
600608 $fieldData[$fieldname] = $field->loadDataFromRequest( $wgRequest );
@@ -605,7 +613,7 @@
606614 $field = $this->mFlatFields[$name];
607615 $value = $field->filter( $value, $this->mFlatFields );
608616 }
609 -
 617+
610618 $this->mFieldData = $fieldData;
611619 }
612620
@@ -642,6 +650,7 @@
643651 public $mParams;
644652 protected $mLabel; # String label. Set on construction
645653 protected $mID;
 654+ protected $mClass = '';
646655 protected $mDefault;
647656 public $mParent;
648657
@@ -660,7 +669,7 @@
661670 * field input. Don't forget to call parent::validate() to ensure
662671 * that the user-defined callback mValidationCallback is still run
663672 * @param $value String the value the field was submitted with
664 - * @param $alldata $all the data collected from the form
 673+ * @param $alldata Array the data collected from the form
665674 * @return Mixed Bool true on success, or String error to display.
666675 */
667676 function validate( $value, $alldata ) {
@@ -749,6 +758,10 @@
750759 $this->mID = $id;
751760 }
752761
 762+ if ( isset( $params['cssclass'] ) ) {
 763+ $this->mClass = $params['cssclass'];
 764+ }
 765+
753766 if ( isset( $params['validation-callback'] ) ) {
754767 $this->mValidationCallback = $params['validation-callback'];
755768 }
@@ -776,13 +789,19 @@
777790 }
778791
779792 $html = $this->getLabelHtml();
780 - $html .= Html::rawElement( 'td', array( 'class' => 'mw-input' ),
781 - $this->getInputHTML( $value ) ."\n$errors" );
 793+ $html .= Html::rawElement(
 794+ 'td',
 795+ array( 'class' => 'mw-input' ),
 796+ $this->getInputHTML( $value ) ."\n$errors"
 797+ );
782798
783799 $fieldType = get_class( $this );
784800
785 - $html = Html::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ),
786 - $html ) . "\n";
 801+ $html = Html::rawElement(
 802+ 'tr',
 803+ array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass}" ),
 804+ $html
 805+ ) . "\n";
787806
788807 $helptext = null;
789808 if ( isset( $this->mParams['help-message'] ) ) {
@@ -929,6 +948,16 @@
930949
931950 return Html::element( 'input', $attribs );
932951 }
 952+
 953+ public function validate( $value, $alldata ){
 954+ $p = parent::validate( $value, $alldata );
 955+ if( $p !== true ) return $p;
 956+
 957+ if( isset( $this->mParams['required'] ) && $value === '' ){
 958+ return wfMsgExt( 'htmlform-required', 'parseinline' );
 959+ }
 960+ return true;
 961+ }
933962 }
934963 class HTMLTextAreaField extends HTMLFormField {
935964
@@ -1018,7 +1047,9 @@
10191048
10201049 if ( $p !== true ) return $p;
10211050
1022 - if ( intval( $value ) != $value ) {
 1051+ if ( $value !== ''
 1052+ && ( !is_numeric( $value ) || round( $value ) != $value ) )
 1053+ {
10231054 return wfMsgExt( 'htmlform-int-invalid', 'parse' );
10241055 }
10251056
@@ -1223,7 +1254,7 @@
12241255 } else {
12251256 $thisAttribs = array( 'id' => $this->mID . "-$info", 'value' => $info );
12261257
1227 - $checkbox = Xml::check( $this->mName . '[]', in_array( $info, $value ),
 1258+ $checkbox = Xml::check( $this->mName . '[]', $info === $value,
12281259 $attribs + $thisAttribs );
12291260 $checkbox .= '&nbsp;' . Html::rawElement( 'label', array( 'for' => $this->mID . "-$info" ), $label );
12301261
@@ -1352,9 +1383,14 @@
13531384 class HTMLHiddenField extends HTMLFormField {
13541385
13551386 public function getTableRow( $value ){
 1387+ $params = array();
 1388+ if( $this->mID ){
 1389+ $params['id'] = $this->mID;
 1390+ }
13561391 $this->mParent->addHiddenField(
1357 - $this->mParams['name'],
1358 - $this->mParams['default']
 1392+ $this->mName,
 1393+ $this->mDefault,
 1394+ $params
13591395 );
13601396 return '';
13611397 }

Follow-up revisions

RevisionCommit summaryAuthorDate
r64901Follow-up to r64866 - add message, and apply the same validation to TextareaF...happy-melon21:10, 10 April 2010
r65029Follow-up to r65027: Prefix all hidden fields with wp. This was not necessary...btongminh14:47, 14 April 2010
r65040From r64866, more consistent behaviour of hidden field names. May affect r65...happy-melon21:42, 14 April 2010
r78246Follow-up to r64866: follow the HTML5 spec when validating floats and ints, a...happy-melon15:32, 12 December 2010
r78452Clean up the running mess that is r64866, r65040, and then r67277. Implement...happy-melon21:14, 15 December 2010

Comments

#Comment by Nikerabbit (talk | contribs)   13:12, 10 April 2010
  • Fix type validation on Int fields to correctly handle numbers larger than PHP_MAX_INT or with leading zeroes (eg phone numbers on 32 bit systems)

Phone numbers are not integeters.

+if ( $value !==  
+	&& ( !is_numeric( $value ) || round( $value ) != $value ) ) 

If you really do want is_numeric and (all the crap it accepts) here, please document it. As it is input like "0xff" is considered valid, which I think it should not.

#Comment by Happy-melon (talk | contribs)   15:55, 10 April 2010

From an academic point of view, maybe not. We're not building an academic thought experiment. What are int fields really going to be used for? If someone really wants to specify a block length in hex, or a maximum number of results as an exponential, is that actually a problem?

The fact that 0123 != 123 is a problem though...

#Comment by Nikerabbit (talk | contribs)   16:04, 10 April 2010

Yes if that input is going into database integer field. Anyway integer is integer, if you change its meaning to anything that looks like a number, don't call it integer, but something else. In any case good documentation should be provided for non-obvious code like that if condition.

#Comment by Bryan (talk | contribs)   14:37, 14 April 2010

Marking as 1.16, because a r65025 requires it.

#Comment by Bryan (talk | contribs)   14:44, 14 April 2010

Somehow this revision causes all hidden fields to be prefixed with "wp". I can't find out where exactly though. I think this is unnecessarily breaking b/c, so I suggest reverting it.

#Comment by Happy-melon (talk | contribs)   21:32, 14 April 2010

Hmm...

Test:

$fields = array(
	'FieldByKey' => array(
		'type' => 'hidden',
		'default' => 'foo', ),
	'FieldByName' => array(
		'type' => 'hidden',
		'default' => 'foo',
		'name' => 'AnotherFieldByName' ), );
$form = new HTMLForm( $fields );
$form->addHiddenField( 'FunctionByKey', 'foo' );
$form->addHiddenField( 'FunctionByName', 'foo', array( 'name' => 'AnotherFunctionByName') );

Old behaviour: FunctionByKey FunctionByName FieldByKey FieldByName

New behaviour: FunctionByKey FunctionByName wpFieldByKey wpAnotherFieldByName

I agree that's undesirable; as is the inability to force names when adding fields using addHiddenField().

#Comment by Happy-melon (talk | contribs)   21:45, 14 April 2010

Rationalised in r65040.

New behaviour:

FunctionByKey 
AnotherFunctionByName 
FieldByKey 
AnotherFieldByName 

How does this affect r65029 and its MFT? I don't really understand what the issue was there.

#Comment by Simetrical (talk | contribs)   22:23, 25 April 2010

HTML5 defines:

A string is a valid floating point number if it consists of:

  1. Optionally, a U+002D HYPHEN-MINUS character (-).
  2. A series of one or more characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9).
  3. Optionally:
    1. A single U+002E FULL STOP character (.).
    2. A series of one or more characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9).
  4. Optionally:
    1. Either a U+0065 LATIN SMALL LETTER E character (e) or a U+0045 LATIN CAPITAL LETTER E character (E).
    2. Optionally, a U+002D HYPHEN-MINUS character (-) or U+002B PLUS SIGN character (+).
    3. A series of one or more characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9).

<http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#valid-floating-point-number>

We may as well harmonize with that, because that's what will be checked on the client side in HTML5-supporting UAs, if HTML5 is enabled. Doesn't seem like we need to allow 0xff or such. For telephone numbers, you should use <input type="tel">, not type="number".

#Comment by Simetrical (talk | contribs)   22:29, 25 April 2010

. . . also, why not support required for everything in the parent class, instead of in particular subclasses? The HTML attribute applies to all input types except "hidden".

#Comment by Werdna (talk | contribs)   03:38, 9 December 2010

(late) Agreed. I'd much prefer that the support for required were put higher up the food chain.

#Comment by Happy-melon (talk | contribs)   15:33, 12 December 2010

Both done in r78246.

#Comment by Werdna (talk | contribs)   03:29, 9 December 2010

- $section = ; - if ( isset( $info['section'] ) ) - $section = $info['section']; + $section = isset( $info['section'] ) + ? $info['section'] + : ;

- $info['name'] = $fieldname; + $info['name'] = isset( $info['name'] ) + ? $info['name'] + : $fieldname;

Please don't. Ternaries suck, if blocks are so much clearer.

#Comment by Werdna (talk | contribs)   03:30, 9 December 2010

Make that:

-			$section = '';
-			if ( isset( $info['section'] ) )
-				$section = $info['section'];
+			$section = isset( $info['section'] ) 
+				? $info['section']
+				: '';
 
-			$info['name'] = $fieldname;
+			$info['name'] = isset( $info['name'] )
+				? $info['name']
+				: $fieldname;
#Comment by Happy-melon (talk | contribs)   12:32, 12 December 2010

I disagree, in this instance. The semantics of "this variable is either set to one thing or another depending on something" is more clearly conveyed by a ternary than if/else. And I'm not seeing anything in Coding Conventions to suggest otherwise.

#Comment by Nikerabbit (talk | contribs)   12:47, 12 December 2010

To me it looks like it should be a class with default values. This isn't about either-or.

#Comment by Werdna (talk | contribs)   03:37, 9 December 2010

I also don't understand this:

-				$checkbox = Xml::check( $this->mName . '[]', in_array( $info, $value ),
+				$checkbox = Xml::check( $this->mName . '[]', $info === $value,

If it's a multi-select, surely you should be using in_array(), and the change here should have been to pass true as the final parameter (to insist on strict comparisons).

#Comment by Catrope (talk | contribs)   14:59, 9 December 2010

This is almost certainly a bug, marking fixme.

#Comment by Happy-melon (talk | contribs)   12:58, 12 December 2010

This was fixed way back in r65216.

Status & tagging log