00001 <?php
00002
00048 class HTMLForm {
00049 static $jsAdded = false;
00050
00051 # A mapping of 'type' inputs onto standard HTMLFormField subclasses
00052 static $typeMappings = array(
00053 'text' => 'HTMLTextField',
00054 'textarea' => 'HTMLTextAreaField',
00055 'select' => 'HTMLSelectField',
00056 'radio' => 'HTMLRadioField',
00057 'multiselect' => 'HTMLMultiSelectField',
00058 'check' => 'HTMLCheckField',
00059 'toggle' => 'HTMLCheckField',
00060 'int' => 'HTMLIntField',
00061 'float' => 'HTMLFloatField',
00062 'info' => 'HTMLInfoField',
00063 'selectorother' => 'HTMLSelectOrOtherField',
00064 'submit' => 'HTMLSubmitField',
00065 'hidden' => 'HTMLHiddenField',
00066 'edittools' => 'HTMLEditTools',
00067
00068 # HTMLTextField will output the correct type="" attribute automagically.
00069 # There are about four zillion other HTML5 input types, like url, but
00070 # we don't use those at the moment, so no point in adding all of them.
00071 'email' => 'HTMLTextField',
00072 'password' => 'HTMLTextField',
00073 );
00074
00075 protected $mMessagePrefix;
00076 protected $mFlatFields;
00077 protected $mFieldTree;
00078 protected $mShowReset = false;
00079 public $mFieldData;
00080
00081 protected $mSubmitCallback;
00082 protected $mValidationErrorMessage;
00083
00084 protected $mPre = '';
00085 protected $mHeader = '';
00086 protected $mPost = '';
00087 protected $mId;
00088
00089 protected $mSubmitID;
00090 protected $mSubmitName;
00091 protected $mSubmitText;
00092 protected $mSubmitTooltip;
00093 protected $mTitle;
00094
00095 protected $mUseMultipart = false;
00096 protected $mHiddenFields = array();
00097 protected $mButtons = array();
00098
00099 protected $mWrapperLegend = false;
00100
00106 public function __construct( $descriptor, $messagePrefix='' ) {
00107 $this->mMessagePrefix = $messagePrefix;
00108
00109 // Expand out into a tree.
00110 $loadedDescriptor = array();
00111 $this->mFlatFields = array();
00112
00113 foreach( $descriptor as $fieldname => $info ) {
00114 $section = '';
00115 if ( isset( $info['section'] ) )
00116 $section = $info['section'];
00117
00118 $info['name'] = $fieldname;
00119
00120 if ( isset( $info['type'] ) && $info['type'] == 'file' )
00121 $this->mUseMultipart = true;
00122
00123 $field = self::loadInputFromParameters( $info );
00124 $field->mParent = $this;
00125
00126 $setSection =& $loadedDescriptor;
00127 if( $section ) {
00128 $sectionParts = explode( '/', $section );
00129
00130 while( count( $sectionParts ) ) {
00131 $newName = array_shift( $sectionParts );
00132
00133 if ( !isset( $setSection[$newName] ) ) {
00134 $setSection[$newName] = array();
00135 }
00136
00137 $setSection =& $setSection[$newName];
00138 }
00139 }
00140
00141 $setSection[$fieldname] = $field;
00142 $this->mFlatFields[$fieldname] = $field;
00143 }
00144
00145 $this->mFieldTree = $loadedDescriptor;
00146 }
00147
00152 static function addJS() {
00153 if( self::$jsAdded ) return;
00154
00155 global $wgOut, $wgStylePath;
00156
00157 $wgOut->addScriptFile( "$wgStylePath/common/htmlform.js" );
00158 }
00159
00165 static function loadInputFromParameters( $descriptor ) {
00166 if ( isset( $descriptor['class'] ) ) {
00167 $class = $descriptor['class'];
00168 } elseif ( isset( $descriptor['type'] ) ) {
00169 $class = self::$typeMappings[$descriptor['type']];
00170 $descriptor['class'] = $class;
00171 }
00172
00173 if( !$class ) {
00174 throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
00175 }
00176
00177 $obj = new $class( $descriptor );
00178
00179 return $obj;
00180 }
00181
00188 function show() {
00189 $html = '';
00190
00191 self::addJS();
00192
00193 # Load data from the request.
00194 $this->loadData();
00195
00196 # Try a submission
00197 global $wgUser, $wgRequest;
00198 $editToken = $wgRequest->getVal( 'wpEditToken' );
00199
00200 $result = false;
00201 if ( $wgUser->matchEditToken( $editToken ) )
00202 $result = $this->trySubmit();
00203
00204 if( $result === true )
00205 return $result;
00206
00207 # Display form.
00208 $this->displayForm( $result );
00209 return false;
00210 }
00211
00219 function trySubmit() {
00220 # Check for validation
00221 foreach( $this->mFlatFields as $fieldname => $field ) {
00222 if ( !empty( $field->mParams['nodata'] ) )
00223 continue;
00224 if ( $field->validate(
00225 $this->mFieldData[$fieldname],
00226 $this->mFieldData )
00227 !== true )
00228 {
00229 return isset( $this->mValidationErrorMessage )
00230 ? $this->mValidationErrorMessage
00231 : array( 'htmlform-invalid-input' );
00232 }
00233 }
00234
00235 $callback = $this->mSubmitCallback;
00236
00237 $data = $this->filterDataForSubmit( $this->mFieldData );
00238
00239 $res = call_user_func( $callback, $data );
00240
00241 return $res;
00242 }
00243
00252 function setSubmitCallback( $cb ) {
00253 $this->mSubmitCallback = $cb;
00254 }
00255
00261 function setValidationErrorMessage( $msg ) {
00262 $this->mValidationErrorMessage = $msg;
00263 }
00264
00269 function setIntro( $msg ) { $this->mPre = $msg; }
00270
00275 function addPreText( $msg ) { $this->mPre .= $msg; }
00276
00281 function addHeaderText( $msg ) { $this->mHeader .= $msg; }
00282
00287 function addPostText( $msg ) { $this->mPost .= $msg; }
00288
00294 public function addHiddenField( $name, $value ){
00295 $this->mHiddenFields[ $name ] = $value;
00296 }
00297
00298 public function addButton( $name, $value, $id=null, $attribs=null ){
00299 $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
00300 }
00301
00307 function displayForm( $submitResult ) {
00308 global $wgOut;
00309
00310 if ( $submitResult !== false ) {
00311 $this->displayErrors( $submitResult );
00312 }
00313
00314 # For good measure (it is the default)
00315 $wgOut->preventClickjacking();
00316
00317 $html = ''
00318 . $this->mHeader
00319 . $this->getBody()
00320 . $this->getHiddenFields()
00321 . $this->getButtons()
00322 ;
00323
00324 $html = $this->wrapForm( $html );
00325
00326 $wgOut->addHTML( ''
00327 . $this->mPre
00328 . $html
00329 . $this->mPost
00330 );
00331 }
00332
00338 function wrapForm( $html ) {
00339
00340 # Include a <fieldset> wrapper for style, if requested.
00341 if ( $this->mWrapperLegend !== false ){
00342 $html = Xml::fieldset( $this->mWrapperLegend, $html );
00343 }
00344 # Use multipart/form-data
00345 $encType = $this->mUseMultipart
00346 ? 'multipart/form-data'
00347 : 'application/x-www-form-urlencoded';
00348 # Attributes
00349 $attribs = array(
00350 'action' => $this->getTitle()->getFullURL(),
00351 'method' => 'post',
00352 'class' => 'visualClear',
00353 'enctype' => $encType,
00354 );
00355 if ( !empty( $this->mId ) )
00356 $attribs['id'] = $this->mId;
00357
00358 return Html::rawElement( 'form', $attribs, $html );
00359 }
00360
00365 function getHiddenFields() {
00366 global $wgUser;
00367 $html = '';
00368
00369 $html .= Html::hidden( 'wpEditToken', $wgUser->editToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
00370 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00371
00372 foreach( $this->mHiddenFields as $name => $value ){
00373 $html .= Html::hidden( $name, $value ) . "\n";
00374 }
00375
00376 return $html;
00377 }
00378
00383 function getButtons() {
00384 $html = '';
00385
00386 $attribs = array();
00387
00388 if ( isset( $this->mSubmitID ) )
00389 $attribs['id'] = $this->mSubmitID;
00390 if ( isset( $this->mSubmitName ) )
00391 $attribs['name'] = $this->mSubmitName;
00392 if ( isset( $this->mSubmitTooltip ) ) {
00393 global $wgUser;
00394 $attribs += $wgUser->getSkin()->tooltipAndAccessKeyAttribs( $this->mSubmitTooltip );
00395 }
00396
00397 $attribs['class'] = 'mw-htmlform-submit';
00398
00399 $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
00400
00401 if( $this->mShowReset ) {
00402 $html .= Html::element(
00403 'input',
00404 array(
00405 'type' => 'reset',
00406 'value' => wfMsg( 'htmlform-reset' )
00407 )
00408 ) . "\n";
00409 }
00410
00411 foreach( $this->mButtons as $button ){
00412 $attrs = array(
00413 'type' => 'submit',
00414 'name' => $button['name'],
00415 'value' => $button['value']
00416 );
00417 if ( $button['attribs'] )
00418 $attrs += $button['attribs'];
00419 if( isset( $button['id'] ) )
00420 $attrs['id'] = $button['id'];
00421 $html .= Html::element( 'input', $attrs );
00422 }
00423
00424 return $html;
00425 }
00426
00430 function getBody() {
00431 return $this->displaySection( $this->mFieldTree );
00432 }
00433
00438 function displayErrors( $errors ) {
00439 if ( is_array( $errors ) ) {
00440 $errorstr = $this->formatErrors( $errors );
00441 } else {
00442 $errorstr = $errors;
00443 }
00444
00445 $errorstr = Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr );
00446
00447 global $wgOut;
00448 $wgOut->addHTML( $errorstr );
00449 }
00450
00456 static function formatErrors( $errors ) {
00457 $errorstr = '';
00458 foreach ( $errors as $error ) {
00459 if( is_array( $error ) ) {
00460 $msg = array_shift( $error );
00461 } else {
00462 $msg = $error;
00463 $error = array();
00464 }
00465 $errorstr .= Html::rawElement(
00466 'li',
00467 null,
00468 wfMsgExt( $msg, array( 'parseinline' ), $error )
00469 );
00470 }
00471
00472 $errorstr = Html::rawElement( 'ul', array(), $errorstr );
00473
00474 return $errorstr;
00475 }
00476
00481 function setSubmitText( $t ) {
00482 $this->mSubmitText = $t;
00483 }
00484
00489 function getSubmitText() {
00490 return $this->mSubmitText
00491 ? $this->mSubmitText
00492 : wfMsg( 'htmlform-submit' );
00493 }
00494
00495 public function setSubmitName( $name ) {
00496 $this->mSubmitName = $name;
00497 }
00498
00499 public function setSubmitTooltip( $name ) {
00500 $this->mSubmitTooltip = $name;
00501 }
00502
00503
00508 function setSubmitID( $t ) {
00509 $this->mSubmitID = $t;
00510 }
00511
00512 public function setId( $id ) {
00513 $this->mId = $id;
00514 }
00521 public function setWrapperLegend( $legend ){ $this->mWrapperLegend = $legend; }
00522
00529 function setMessagePrefix( $p ) {
00530 $this->mMessagePrefix = $p;
00531 }
00532
00537 function setTitle( $t ) {
00538 $this->mTitle = $t;
00539 }
00540
00545 function getTitle() {
00546 return $this->mTitle;
00547 }
00548
00553 function displaySection( $fields, $sectionName = '' ) {
00554 $tableHtml = '';
00555 $subsectionHtml = '';
00556 $hasLeftColumn = false;
00557
00558 foreach( $fields as $key => $value ) {
00559 if ( is_object( $value ) ) {
00560 $v = empty( $value->mParams['nodata'] )
00561 ? $this->mFieldData[$key]
00562 : $value->getDefault();
00563 $tableHtml .= $value->getTableRow( $v );
00564
00565 if( $value->getLabel() != ' ' )
00566 $hasLeftColumn = true;
00567 } elseif ( is_array( $value ) ) {
00568 $section = $this->displaySection( $value, $key );
00569 $legend = wfMsg( "{$this->mMessagePrefix}-$key" );
00570 $subsectionHtml .= Xml::fieldset( $legend, $section ) . "\n";
00571 }
00572 }
00573
00574 $classes = array();
00575 if( !$hasLeftColumn ) // Avoid strange spacing when no labels exist
00576 $classes[] = 'mw-htmlform-nolabel';
00577 $attribs = array(
00578 'class' => implode( ' ', $classes ),
00579 );
00580 if ( $sectionName )
00581 $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
00582
00583 $tableHtml = Html::rawElement( 'table', $attribs,
00584 Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n";
00585
00586 return $subsectionHtml . "\n" . $tableHtml;
00587 }
00588
00592 function loadData() {
00593 global $wgRequest;
00594
00595 $fieldData = array();
00596
00597 foreach( $this->mFlatFields as $fieldname => $field ) {
00598 if ( !empty( $field->mParams['nodata'] ) ) continue;
00599 if ( !empty( $field->mParams['disabled'] ) ) {
00600 $fieldData[$fieldname] = $field->getDefault();
00601 } else {
00602 $fieldData[$fieldname] = $field->loadDataFromRequest( $wgRequest );
00603 }
00604 }
00605
00606 # Filter data.
00607 foreach( $fieldData as $name => &$value ) {
00608 $field = $this->mFlatFields[$name];
00609 $value = $field->filter( $value, $this->mFlatFields );
00610 }
00611
00612 $this->mFieldData = $fieldData;
00613 }
00614
00620 function suppressReset( $suppressReset = true ) {
00621 $this->mShowReset = !$suppressReset;
00622 }
00623
00631 function filterDataForSubmit( $data ) {
00632 return $data;
00633 }
00634 }
00635
00640 abstract class HTMLFormField {
00641
00642 protected $mValidationCallback;
00643 protected $mFilterCallback;
00644 protected $mName;
00645 public $mParams;
00646 protected $mLabel; # String label. Set on construction
00647 protected $mID;
00648 protected $mDefault;
00649 public $mParent;
00650
00659 abstract function getInputHTML( $value );
00660
00669 function validate( $value, $alldata ) {
00670 if ( isset( $this->mValidationCallback ) ) {
00671 return call_user_func( $this->mValidationCallback, $value, $alldata );
00672 }
00673
00674 return true;
00675 }
00676
00677 function filter( $value, $alldata ) {
00678 if( isset( $this->mFilterCallback ) ) {
00679 $value = call_user_func( $this->mFilterCallback, $value, $alldata );
00680 }
00681
00682 return $value;
00683 }
00684
00691 protected function needsLabel() {
00692 return true;
00693 }
00694
00701 function loadDataFromRequest( $request ) {
00702 if( $request->getCheck( $this->mName ) ) {
00703 return $request->getText( $this->mName );
00704 } else {
00705 return $this->getDefault();
00706 }
00707 }
00708
00713 function __construct( $params ) {
00714 $this->mParams = $params;
00715
00716 # Generate the label from a message, if possible
00717 if( isset( $params['label-message'] ) ) {
00718 $msgInfo = $params['label-message'];
00719
00720 if ( is_array( $msgInfo ) ) {
00721 $msg = array_shift( $msgInfo );
00722 } else {
00723 $msg = $msgInfo;
00724 $msgInfo = array();
00725 }
00726
00727 $this->mLabel = wfMsgExt( $msg, 'parseinline', $msgInfo );
00728 } elseif ( isset( $params['label'] ) ) {
00729 $this->mLabel = $params['label'];
00730 }
00731
00732 if ( isset( $params['name'] ) ) {
00733 $name = $params['name'];
00734 $validName = Sanitizer::escapeId( $name );
00735 if( $name != $validName ) {
00736 throw new MWException("Invalid name '$name' passed to " . __METHOD__ );
00737 }
00738 $this->mName = 'wp'.$name;
00739 $this->mID = 'mw-input-'.$name;
00740 }
00741
00742 if ( isset( $params['default'] ) ) {
00743 $this->mDefault = $params['default'];
00744 }
00745
00746 if ( isset( $params['id'] ) ) {
00747 $id = $params['id'];
00748 $validId = Sanitizer::escapeId( $id );
00749 if( $id != $validId ) {
00750 throw new MWException("Invalid id '$id' passed to " . __METHOD__ );
00751 }
00752 $this->mID = $id;
00753 }
00754
00755 if ( isset( $params['validation-callback'] ) ) {
00756 $this->mValidationCallback = $params['validation-callback'];
00757 }
00758
00759 if ( isset( $params['filter-callback'] ) ) {
00760 $this->mFilterCallback = $params['filter-callback'];
00761 }
00762 }
00763
00770 function getTableRow( $value ) {
00771 # Check for invalid data.
00772 global $wgRequest;
00773
00774 $errors = $this->validate( $value, $this->mParent->mFieldData );
00775 if ( $errors === true || !$wgRequest->wasPosted() ) {
00776 $errors = '';
00777 } else {
00778 $errors = Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
00779 }
00780
00781 $html = $this->getLabelHtml();
00782 $html .= Html::rawElement( 'td', array( 'class' => 'mw-input' ),
00783 $this->getInputHTML( $value ) ."\n$errors" );
00784
00785 $fieldType = get_class( $this );
00786
00787 $html = Html::rawElement( 'tr', array( 'class' => "mw-htmlform-field-$fieldType" ),
00788 $html ) . "\n";
00789
00790 $helptext = null;
00791 if ( isset( $this->mParams['help-message'] ) ) {
00792 $msg = $this->mParams['help-message'];
00793 $helptext = wfMsgExt( $msg, 'parseinline' );
00794 if ( wfEmptyMsg( $msg, $helptext ) ) {
00795 # Never mind
00796 $helptext = null;
00797 }
00798 } elseif ( isset( $this->mParams['help'] ) ) {
00799 $helptext = $this->mParams['help'];
00800 }
00801
00802 if ( !is_null( $helptext ) ) {
00803 $row = Html::rawElement( 'td', array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
00804 $helptext );
00805 $row = Html::rawElement( 'tr', array(), $row );
00806 $html .= "$row\n";
00807 }
00808
00809 return $html;
00810 }
00811
00812 function getLabel() {
00813 return $this->mLabel;
00814 }
00815 function getLabelHtml() {
00816 # Don't output a for= attribute for labels with no associated input.
00817 # Kind of hacky here, possibly we don't want these to be <label>s at all.
00818 $for = array();
00819 if ( $this->needsLabel() ) {
00820 $for['for'] = $this->mID;
00821 }
00822 return Html::rawElement( 'td', array( 'class' => 'mw-label' ),
00823 Html::rawElement( 'label', $for, $this->getLabel() )
00824 );
00825 }
00826
00827 function getDefault() {
00828 if ( isset( $this->mDefault ) ) {
00829 return $this->mDefault;
00830 } else {
00831 return null;
00832 }
00833 }
00834
00840 public function getTooltipAndAccessKey() {
00841 if ( empty( $this->mParams['tooltip'] ) )
00842 return array();
00843
00844 global $wgUser;
00845 return $wgUser->getSkin()->tooltipAndAccessKeyAttribs();
00846 }
00847
00855 public static function flattenOptions( $options ) {
00856 $flatOpts = array();
00857
00858 foreach( $options as $key => $value ) {
00859 if ( is_array( $value ) ) {
00860 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
00861 } else {
00862 $flatOpts[] = $value;
00863 }
00864 }
00865
00866 return $flatOpts;
00867 }
00868
00869 }
00870
00871 class HTMLTextField extends HTMLFormField {
00872
00873 function getSize() {
00874 return isset( $this->mParams['size'] )
00875 ? $this->mParams['size']
00876 : 45;
00877 }
00878
00879 function getInputHTML( $value ) {
00880 $attribs = array(
00881 'id' => $this->mID,
00882 'name' => $this->mName,
00883 'size' => $this->getSize(),
00884 'value' => $value,
00885 ) + $this->getTooltipAndAccessKey();
00886
00887 if ( isset( $this->mParams['maxlength'] ) ) {
00888 $attribs['maxlength'] = $this->mParams['maxlength'];
00889 }
00890
00891 if ( !empty( $this->mParams['disabled'] ) ) {
00892 $attribs['disabled'] = 'disabled';
00893 }
00894
00895 # TODO: Enforce pattern, step, required, readonly on the server side as
00896 # well
00897 foreach ( array( 'min', 'max', 'pattern', 'title', 'step',
00898 'placeholder' ) as $param ) {
00899 if ( isset( $this->mParams[$param] ) ) {
00900 $attribs[$param] = $this->mParams[$param];
00901 }
00902 }
00903 foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as
00904 $param ) {
00905 if ( isset( $this->mParams[$param] ) ) {
00906 $attribs[$param] = '';
00907 }
00908 }
00909
00910 # Implement tiny differences between some field variants
00911 # here, rather than creating a new class for each one which
00912 # is essentially just a clone of this one.
00913 if ( isset( $this->mParams['type'] ) ) {
00914 switch ( $this->mParams['type'] ) {
00915 case 'email':
00916 $attribs['type'] = 'email';
00917 break;
00918 case 'int':
00919 $attribs['type'] = 'number';
00920 break;
00921 case 'float':
00922 $attribs['type'] = 'number';
00923 $attribs['step'] = 'any';
00924 break;
00925 # Pass through
00926 case 'password':
00927 case 'file':
00928 $attribs['type'] = $this->mParams['type'];
00929 break;
00930 }
00931 }
00932
00933 return Html::element( 'input', $attribs );
00934 }
00935 }
00936 class HTMLTextAreaField extends HTMLFormField {
00937
00938 function getCols() {
00939 return isset( $this->mParams['cols'] )
00940 ? $this->mParams['cols']
00941 : 80;
00942 }
00943 function getRows() {
00944 return isset( $this->mParams['rows'] )
00945 ? $this->mParams['rows']
00946 : 25;
00947 }
00948
00949 function getInputHTML( $value ) {
00950 $attribs = array(
00951 'id' => $this->mID,
00952 'name' => $this->mName,
00953 'cols' => $this->getCols(),
00954 'rows' => $this->getRows(),
00955 ) + $this->getTooltipAndAccessKey();
00956
00957
00958 if ( !empty( $this->mParams['disabled'] ) ) {
00959 $attribs['disabled'] = 'disabled';
00960 }
00961 if ( !empty( $this->mParams['readonly'] ) ) {
00962 $attribs['readonly'] = 'readonly';
00963 }
00964
00965 foreach ( array( 'required', 'autofocus' ) as $param ) {
00966 if ( isset( $this->mParams[$param] ) ) {
00967 $attribs[$param] = '';
00968 }
00969 }
00970
00971 return Html::element( 'textarea', $attribs, $value );
00972 }
00973 }
00974
00978 class HTMLFloatField extends HTMLTextField {
00979
00980 function getSize() {
00981 return isset( $this->mParams['size'] )
00982 ? $this->mParams['size']
00983 : 20;
00984 }
00985
00986 function validate( $value, $alldata ) {
00987 $p = parent::validate( $value, $alldata );
00988
00989 if ( $p !== true ) return $p;
00990
00991 if ( floatval( $value ) != $value ) {
00992 return wfMsgExt( 'htmlform-float-invalid', 'parse' );
00993 }
00994
00995 $in_range = true;
00996
00997 # The "int" part of these message names is rather confusing.
00998 # They make equal sense for all numbers.
00999 if ( isset( $this->mParams['min'] ) ) {
01000 $min = $this->mParams['min'];
01001 if ( $min > $value )
01002 return wfMsgExt( 'htmlform-int-toolow', 'parse', array( $min ) );
01003 }
01004
01005 if ( isset( $this->mParams['max'] ) ) {
01006 $max = $this->mParams['max'];
01007 if( $max < $value )
01008 return wfMsgExt( 'htmlform-int-toohigh', 'parse', array( $max ) );
01009 }
01010
01011 return true;
01012 }
01013 }
01014
01018 class HTMLIntField extends HTMLFloatField {
01019 function validate( $value, $alldata ) {
01020 $p = parent::validate( $value, $alldata );
01021
01022 if ( $p !== true ) return $p;
01023
01024 if ( intval( $value ) != $value ) {
01025 return wfMsgExt( 'htmlform-int-invalid', 'parse' );
01026 }
01027
01028 return true;
01029 }
01030 }
01031
01035 class HTMLCheckField extends HTMLFormField {
01036 function getInputHTML( $value ) {
01037 if ( !empty( $this->mParams['invert'] ) )
01038 $value = !$value;
01039
01040 $attr = $this->getTooltipAndAccessKey();
01041 $attr['id'] = $this->mID;
01042 if( !empty( $this->mParams['disabled'] ) ) {
01043 $attr['disabled'] = 'disabled';
01044 }
01045
01046 return Xml::check( $this->mName, $value, $attr ) . ' ' .
01047 Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
01048 }
01049
01054 function getLabel() {
01055 return ' ';
01056 }
01057
01058 function loadDataFromRequest( $request ) {
01059 $invert = false;
01060 if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
01061 $invert = true;
01062 }
01063
01064 // GetCheck won't work like we want for checks.
01065 if( $request->getCheck( 'wpEditToken' ) ) {
01066
01067
01068
01069
01070
01071
01072 return $request->getBool( $this->mName ) xor $invert;
01073 } else {
01074 return $this->getDefault();
01075 }
01076 }
01077 }
01078
01082 class HTMLSelectField extends HTMLFormField {
01083
01084 function validate( $value, $alldata ) {
01085 $p = parent::validate( $value, $alldata );
01086 if( $p !== true ) return $p;
01087
01088 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01089 if ( in_array( $value, $validOptions ) )
01090 return true;
01091 else
01092 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
01093 }
01094
01095 function getInputHTML( $value ) {
01096 $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
01097
01098 # If one of the options' 'name' is int(0), it is automatically selected.
01099 # because PHP sucks and things int(0) == 'some string'.
01100 # Working around this by forcing all of them to strings.
01101 $options = array_map( 'strval', $this->mParams['options'] );
01102
01103 if( !empty( $this->mParams['disabled'] ) ) {
01104 $select->setAttribute( 'disabled', 'disabled' );
01105 }
01106
01107 $select->addOptions( $options );
01108
01109 return $select->getHTML();
01110 }
01111 }
01112
01116 class HTMLSelectOrOtherField extends HTMLTextField {
01117 static $jsAdded = false;
01118
01119 function __construct( $params ) {
01120 if( !in_array( 'other', $params['options'], true ) ) {
01121 $params['options'][wfMsg( 'htmlform-selectorother-other' )] = 'other';
01122 }
01123
01124 parent::__construct( $params );
01125 }
01126
01127 static function forceToStringRecursive( $array ) {
01128 if ( is_array($array) ) {
01129 return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array);
01130 } else {
01131 return strval($array);
01132 }
01133 }
01134
01135 function getInputHTML( $value ) {
01136 $valInSelect = false;
01137
01138 if( $value !== false )
01139 $valInSelect = in_array( $value,
01140 HTMLFormField::flattenOptions( $this->mParams['options'] ) );
01141
01142 $selected = $valInSelect ? $value : 'other';
01143
01144 $opts = self::forceToStringRecursive( $this->mParams['options'] );
01145
01146 $select = new XmlSelect( $this->mName, $this->mID, $selected );
01147 $select->addOptions( $opts );
01148
01149 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
01150
01151 $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
01152 if( !empty( $this->mParams['disabled'] ) ) {
01153 $select->setAttribute( 'disabled', 'disabled' );
01154 $tbAttribs['disabled'] = 'disabled';
01155 }
01156
01157 $select = $select->getHTML();
01158
01159 if ( isset( $this->mParams['maxlength'] ) ) {
01160 $tbAttribs['maxlength'] = $this->mParams['maxlength'];
01161 }
01162
01163 $textbox = Html::input( $this->mName . '-other',
01164 $valInSelect ? '' : $value,
01165 'text',
01166 $tbAttribs );
01167
01168 return "$select<br />\n$textbox";
01169 }
01170
01171 function loadDataFromRequest( $request ) {
01172 if( $request->getCheck( $this->mName ) ) {
01173 $val = $request->getText( $this->mName );
01174
01175 if( $val == 'other' ) {
01176 $val = $request->getText( $this->mName . '-other' );
01177 }
01178
01179 return $val;
01180 } else {
01181 return $this->getDefault();
01182 }
01183 }
01184 }
01185
01189 class HTMLMultiSelectField extends HTMLFormField {
01190
01191 function validate( $value, $alldata ) {
01192 $p = parent::validate( $value, $alldata );
01193 if( $p !== true ) return $p;
01194
01195 if( !is_array( $value ) ) return false;
01196
01197 # If all options are valid, array_intersect of the valid options
01198 # and the provided options will return the provided options.
01199 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01200
01201 $validValues = array_intersect( $value, $validOptions );
01202 if ( count( $validValues ) == count( $value ) )
01203 return true;
01204 else
01205 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
01206 }
01207
01208 function getInputHTML( $value ) {
01209 $html = $this->formatOptions( $this->mParams['options'], $value );
01210
01211 return $html;
01212 }
01213
01214 function formatOptions( $options, $value ) {
01215 $html = '';
01216
01217 $attribs = array();
01218 if ( !empty( $this->mParams['disabled'] ) ) {
01219 $attribs['disabled'] = 'disabled';
01220 }
01221
01222 foreach( $options as $label => $info ) {
01223 if( is_array( $info ) ) {
01224 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
01225 $html .= $this->formatOptions( $info, $value );
01226 } else {
01227 $thisAttribs = array( 'id' => $this->mID . "-$info", 'value' => $info );
01228
01229 $checkbox = Xml::check( $this->mName . '[]', in_array( $info, $value ),
01230 $attribs + $thisAttribs );
01231 $checkbox .= ' ' . Html::rawElement( 'label', array( 'for' => $this->mID . "-$info" ), $label );
01232
01233 $html .= $checkbox . '<br />';
01234 }
01235 }
01236
01237 return $html;
01238 }
01239
01240 function loadDataFromRequest( $request ) {
01241 # won't work with getCheck
01242 if( $request->getCheck( 'wpEditToken' ) ) {
01243 $arr = $request->getArray( $this->mName );
01244
01245 if( !$arr )
01246 $arr = array();
01247
01248 return $arr;
01249 } else {
01250 return $this->getDefault();
01251 }
01252 }
01253
01254 function getDefault() {
01255 if ( isset( $this->mDefault ) ) {
01256 return $this->mDefault;
01257 } else {
01258 return array();
01259 }
01260 }
01261
01262 protected function needsLabel() {
01263 return false;
01264 }
01265 }
01266
01270 class HTMLRadioField extends HTMLFormField {
01271 function validate( $value, $alldata ) {
01272 $p = parent::validate( $value, $alldata );
01273 if( $p !== true ) return $p;
01274
01275 if( !is_string( $value ) && !is_int( $value ) )
01276 return false;
01277
01278 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01279
01280 if ( in_array( $value, $validOptions ) )
01281 return true;
01282 else
01283 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' );
01284 }
01285
01290 function getInputHTML( $value ) {
01291 $html = $this->formatOptions( $this->mParams['options'], $value );
01292
01293 return $html;
01294 }
01295
01296 function formatOptions( $options, $value ) {
01297 $html = '';
01298
01299 $attribs = array();
01300 if ( !empty( $this->mParams['disabled'] ) ) {
01301 $attribs['disabled'] = 'disabled';
01302 }
01303
01304 # TODO: should this produce an unordered list perhaps?
01305 foreach( $options as $label => $info ) {
01306 if( is_array( $info ) ) {
01307 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
01308 $html .= $this->formatOptions( $info, $value );
01309 } else {
01310 $id = Sanitizer::escapeId( $this->mID . "-$info" );
01311 $html .= Xml::radio( $this->mName, $info, $info == $value,
01312 $attribs + array( 'id' => $id ) );
01313 $html .= ' ' .
01314 Html::rawElement( 'label', array( 'for' => $id ), $label );
01315
01316 $html .= "<br />\n";
01317 }
01318 }
01319
01320 return $html;
01321 }
01322
01323 protected function needsLabel() {
01324 return false;
01325 }
01326 }
01327
01331 class HTMLInfoField extends HTMLFormField {
01332 function __construct( $info ) {
01333 $info['nodata'] = true;
01334
01335 parent::__construct( $info );
01336 }
01337
01338 function getInputHTML( $value ) {
01339 return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
01340 }
01341
01342 function getTableRow( $value ) {
01343 if ( !empty( $this->mParams['rawrow'] ) ) {
01344 return $value;
01345 }
01346
01347 return parent::getTableRow( $value );
01348 }
01349
01350 protected function needsLabel() {
01351 return false;
01352 }
01353 }
01354
01355 class HTMLHiddenField extends HTMLFormField {
01356
01357 public function getTableRow( $value ){
01358 $this->mParent->addHiddenField(
01359 $this->mParams['name'],
01360 $this->mParams['default']
01361 );
01362 return '';
01363 }
01364
01365 public function getInputHTML( $value ){ return ''; }
01366 }
01367
01368 class HTMLSubmitField extends HTMLFormField {
01369
01370 public function getTableRow( $value ){
01371 $this->mParent->addButton(
01372 $this->mParams['name'],
01373 $this->mParams['default'],
01374 isset($this->mParams['id']) ? $this->mParams['id'] : null,
01375 $this->getTooltipAndAccessKey()
01376 );
01377 }
01378
01379 public function getInputHTML( $value ){ return ''; }
01380 }
01381
01382 class HTMLEditTools extends HTMLFormField {
01383 public function getInputHTML( $value ) {
01384 return '';
01385 }
01386 public function getTableRow( $value ) {
01387 return "<tr><td></td><td class=\"mw-input\">"
01388 . '<div class="mw-editTools">'
01389 . wfMsgExt( empty( $this->mParams['message'] )
01390 ? 'edittools' : $this->mParams['message'],
01391 array( 'parse', 'content' ) )
01392 . "</div></td></tr>\n";
01393 }
01394 }