MediaWiki
REL1_19
|
00001 <?php 00056 class HTMLForm extends ContextSource { 00057 00058 // A mapping of 'type' inputs onto standard HTMLFormField subclasses 00059 static $typeMappings = array( 00060 'api' => 'HTMLApiField', 00061 'text' => 'HTMLTextField', 00062 'textarea' => 'HTMLTextAreaField', 00063 'select' => 'HTMLSelectField', 00064 'radio' => 'HTMLRadioField', 00065 'multiselect' => 'HTMLMultiSelectField', 00066 'check' => 'HTMLCheckField', 00067 'toggle' => 'HTMLCheckField', 00068 'int' => 'HTMLIntField', 00069 'float' => 'HTMLFloatField', 00070 'info' => 'HTMLInfoField', 00071 'selectorother' => 'HTMLSelectOrOtherField', 00072 'selectandother' => 'HTMLSelectAndOtherField', 00073 'submit' => 'HTMLSubmitField', 00074 'hidden' => 'HTMLHiddenField', 00075 'edittools' => 'HTMLEditTools', 00076 00077 // HTMLTextField will output the correct type="" attribute automagically. 00078 // There are about four zillion other HTML5 input types, like url, but 00079 // we don't use those at the moment, so no point in adding all of them. 00080 'email' => 'HTMLTextField', 00081 'password' => 'HTMLTextField', 00082 ); 00083 00084 protected $mMessagePrefix; 00085 00087 protected $mFlatFields; 00088 00089 protected $mFieldTree; 00090 protected $mShowReset = false; 00091 public $mFieldData; 00092 00093 protected $mSubmitCallback; 00094 protected $mValidationErrorMessage; 00095 00096 protected $mPre = ''; 00097 protected $mHeader = ''; 00098 protected $mFooter = ''; 00099 protected $mSectionHeaders = array(); 00100 protected $mSectionFooters = array(); 00101 protected $mPost = ''; 00102 protected $mId; 00103 00104 protected $mSubmitID; 00105 protected $mSubmitName; 00106 protected $mSubmitText; 00107 protected $mSubmitTooltip; 00108 00109 protected $mTitle; 00110 protected $mMethod = 'post'; 00111 00117 protected $mAction = false; 00118 00119 protected $mUseMultipart = false; 00120 protected $mHiddenFields = array(); 00121 protected $mButtons = array(); 00122 00123 protected $mWrapperLegend = false; 00124 00132 protected $mSubSectionBeforeFields = true; 00133 00141 public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) { 00142 if( $context instanceof IContextSource ){ 00143 $this->setContext( $context ); 00144 $this->mTitle = false; // We don't need them to set a title 00145 $this->mMessagePrefix = $messagePrefix; 00146 } else { 00147 // B/C since 1.18 00148 if( is_string( $context ) && $messagePrefix === '' ){ 00149 // it's actually $messagePrefix 00150 $this->mMessagePrefix = $context; 00151 } 00152 } 00153 00154 // Expand out into a tree. 00155 $loadedDescriptor = array(); 00156 $this->mFlatFields = array(); 00157 00158 foreach ( $descriptor as $fieldname => $info ) { 00159 $section = isset( $info['section'] ) 00160 ? $info['section'] 00161 : ''; 00162 00163 if ( isset( $info['type'] ) && $info['type'] == 'file' ) { 00164 $this->mUseMultipart = true; 00165 } 00166 00167 $field = self::loadInputFromParameters( $fieldname, $info ); 00168 $field->mParent = $this; 00169 00170 $setSection =& $loadedDescriptor; 00171 if ( $section ) { 00172 $sectionParts = explode( '/', $section ); 00173 00174 while ( count( $sectionParts ) ) { 00175 $newName = array_shift( $sectionParts ); 00176 00177 if ( !isset( $setSection[$newName] ) ) { 00178 $setSection[$newName] = array(); 00179 } 00180 00181 $setSection =& $setSection[$newName]; 00182 } 00183 } 00184 00185 $setSection[$fieldname] = $field; 00186 $this->mFlatFields[$fieldname] = $field; 00187 } 00188 00189 $this->mFieldTree = $loadedDescriptor; 00190 } 00191 00197 static function addJS() { wfDeprecated( __METHOD__, '1.18' ); } 00198 00205 static function loadInputFromParameters( $fieldname, $descriptor ) { 00206 if ( isset( $descriptor['class'] ) ) { 00207 $class = $descriptor['class']; 00208 } elseif ( isset( $descriptor['type'] ) ) { 00209 $class = self::$typeMappings[$descriptor['type']]; 00210 $descriptor['class'] = $class; 00211 } else { 00212 $class = null; 00213 } 00214 00215 if ( !$class ) { 00216 throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) ); 00217 } 00218 00219 $descriptor['fieldname'] = $fieldname; 00220 00221 $obj = new $class( $descriptor ); 00222 00223 return $obj; 00224 } 00225 00229 function prepareForm() { 00230 # Check if we have the info we need 00231 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) { 00232 throw new MWException( "You must call setTitle() on an HTMLForm" ); 00233 } 00234 00235 # Load data from the request. 00236 $this->loadData(); 00237 } 00238 00243 function tryAuthorizedSubmit() { 00244 $result = false; 00245 00246 $submit = false; 00247 if ( $this->getMethod() != 'post' ) { 00248 $submit = true; // no session check needed 00249 } elseif ( $this->getRequest()->wasPosted() ) { 00250 $editToken = $this->getRequest()->getVal( 'wpEditToken' ); 00251 if ( $this->getUser()->isLoggedIn() || $editToken != null ) { 00252 // Session tokens for logged-out users have no security value. 00253 // However, if the user gave one, check it in order to give a nice 00254 // "session expired" error instead of "permission denied" or such. 00255 $submit = $this->getUser()->matchEditToken( $editToken ); 00256 } else { 00257 $submit = true; 00258 } 00259 } 00260 00261 if ( $submit ) { 00262 $result = $this->trySubmit(); 00263 } 00264 00265 return $result; 00266 } 00267 00274 function show() { 00275 $this->prepareForm(); 00276 00277 $result = $this->tryAuthorizedSubmit(); 00278 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ){ 00279 return $result; 00280 } 00281 00282 $this->displayForm( $result ); 00283 return false; 00284 } 00285 00293 function trySubmit() { 00294 # Check for validation 00295 foreach ( $this->mFlatFields as $fieldname => $field ) { 00296 if ( !empty( $field->mParams['nodata'] ) ) { 00297 continue; 00298 } 00299 if ( $field->validate( 00300 $this->mFieldData[$fieldname], 00301 $this->mFieldData ) 00302 !== true 00303 ) { 00304 return isset( $this->mValidationErrorMessage ) 00305 ? $this->mValidationErrorMessage 00306 : array( 'htmlform-invalid-input' ); 00307 } 00308 } 00309 00310 $callback = $this->mSubmitCallback; 00311 00312 $data = $this->filterDataForSubmit( $this->mFieldData ); 00313 00314 $res = call_user_func( $callback, $data, $this ); 00315 00316 return $res; 00317 } 00318 00327 function setSubmitCallback( $cb ) { 00328 $this->mSubmitCallback = $cb; 00329 } 00330 00336 function setValidationErrorMessage( $msg ) { 00337 $this->mValidationErrorMessage = $msg; 00338 } 00339 00344 function setIntro( $msg ) { 00345 $this->setPreText( $msg ); 00346 } 00347 00353 function setPreText( $msg ) { $this->mPre = $msg; } 00354 00359 function addPreText( $msg ) { $this->mPre .= $msg; } 00360 00366 function addHeaderText( $msg, $section = null ) { 00367 if ( is_null( $section ) ) { 00368 $this->mHeader .= $msg; 00369 } else { 00370 if ( !isset( $this->mSectionHeaders[$section] ) ) { 00371 $this->mSectionHeaders[$section] = ''; 00372 } 00373 $this->mSectionHeaders[$section] .= $msg; 00374 } 00375 } 00376 00383 function setHeaderText( $msg, $section = null ) { 00384 if ( is_null( $section ) ) { 00385 $this->mHeader = $msg; 00386 } else { 00387 $this->mSectionHeaders[$section] = $msg; 00388 } 00389 } 00390 00396 function addFooterText( $msg, $section = null ) { 00397 if ( is_null( $section ) ) { 00398 $this->mFooter .= $msg; 00399 } else { 00400 if ( !isset( $this->mSectionFooters[$section] ) ) { 00401 $this->mSectionFooters[$section] = ''; 00402 } 00403 $this->mSectionFooters[$section] .= $msg; 00404 } 00405 } 00406 00413 function setFooterText( $msg, $section = null ) { 00414 if ( is_null( $section ) ) { 00415 $this->mFooter = $msg; 00416 } else { 00417 $this->mSectionFooters[$section] = $msg; 00418 } 00419 } 00420 00425 function addPostText( $msg ) { $this->mPost .= $msg; } 00426 00431 function setPostText( $msg ) { $this->mPost = $msg; } 00432 00439 public function addHiddenField( $name, $value, $attribs = array() ) { 00440 $attribs += array( 'name' => $name ); 00441 $this->mHiddenFields[] = array( $value, $attribs ); 00442 } 00443 00444 public function addButton( $name, $value, $id = null, $attribs = null ) { 00445 $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' ); 00446 } 00447 00453 function displayForm( $submitResult ) { 00454 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) ); 00455 } 00456 00462 function getHTML( $submitResult ) { 00463 # For good measure (it is the default) 00464 $this->getOutput()->preventClickjacking(); 00465 $this->getOutput()->addModules( 'mediawiki.htmlform' ); 00466 00467 $html = '' 00468 . $this->getErrors( $submitResult ) 00469 . $this->mHeader 00470 . $this->getBody() 00471 . $this->getHiddenFields() 00472 . $this->getButtons() 00473 . $this->mFooter 00474 ; 00475 00476 $html = $this->wrapForm( $html ); 00477 00478 return '' . $this->mPre . $html . $this->mPost; 00479 } 00480 00486 function wrapForm( $html ) { 00487 00488 # Include a <fieldset> wrapper for style, if requested. 00489 if ( $this->mWrapperLegend !== false ) { 00490 $html = Xml::fieldset( $this->mWrapperLegend, $html ); 00491 } 00492 # Use multipart/form-data 00493 $encType = $this->mUseMultipart 00494 ? 'multipart/form-data' 00495 : 'application/x-www-form-urlencoded'; 00496 # Attributes 00497 $attribs = array( 00498 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction, 00499 'method' => $this->mMethod, 00500 'class' => 'visualClear', 00501 'enctype' => $encType, 00502 ); 00503 if ( !empty( $this->mId ) ) { 00504 $attribs['id'] = $this->mId; 00505 } 00506 00507 return Html::rawElement( 'form', $attribs, $html ); 00508 } 00509 00514 function getHiddenFields() { 00515 global $wgUsePathInfo; 00516 00517 $html = ''; 00518 if( $this->getMethod() == 'post' ){ 00519 $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; 00520 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; 00521 } 00522 00523 if ( !$wgUsePathInfo && $this->getMethod() == 'get' ) { 00524 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; 00525 } 00526 00527 foreach ( $this->mHiddenFields as $data ) { 00528 list( $value, $attribs ) = $data; 00529 $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n"; 00530 } 00531 00532 return $html; 00533 } 00534 00539 function getButtons() { 00540 $html = ''; 00541 $attribs = array(); 00542 00543 if ( isset( $this->mSubmitID ) ) { 00544 $attribs['id'] = $this->mSubmitID; 00545 } 00546 00547 if ( isset( $this->mSubmitName ) ) { 00548 $attribs['name'] = $this->mSubmitName; 00549 } 00550 00551 if ( isset( $this->mSubmitTooltip ) ) { 00552 $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip ); 00553 } 00554 00555 $attribs['class'] = 'mw-htmlform-submit'; 00556 00557 $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; 00558 00559 if ( $this->mShowReset ) { 00560 $html .= Html::element( 00561 'input', 00562 array( 00563 'type' => 'reset', 00564 'value' => wfMsg( 'htmlform-reset' ) 00565 ) 00566 ) . "\n"; 00567 } 00568 00569 foreach ( $this->mButtons as $button ) { 00570 $attrs = array( 00571 'type' => 'submit', 00572 'name' => $button['name'], 00573 'value' => $button['value'] 00574 ); 00575 00576 if ( $button['attribs'] ) { 00577 $attrs += $button['attribs']; 00578 } 00579 00580 if ( isset( $button['id'] ) ) { 00581 $attrs['id'] = $button['id']; 00582 } 00583 00584 $html .= Html::element( 'input', $attrs ); 00585 } 00586 00587 return $html; 00588 } 00589 00594 function getBody() { 00595 return $this->displaySection( $this->mFieldTree ); 00596 } 00597 00603 function getErrors( $errors ) { 00604 if ( $errors instanceof Status ) { 00605 if ( $errors->isOK() ) { 00606 $errorstr = ''; 00607 } else { 00608 $errorstr = $this->getOutput()->parse( $errors->getWikiText() ); 00609 } 00610 } elseif ( is_array( $errors ) ) { 00611 $errorstr = $this->formatErrors( $errors ); 00612 } else { 00613 $errorstr = $errors; 00614 } 00615 00616 return $errorstr 00617 ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr ) 00618 : ''; 00619 } 00620 00626 public static function formatErrors( $errors ) { 00627 $errorstr = ''; 00628 00629 foreach ( $errors as $error ) { 00630 if ( is_array( $error ) ) { 00631 $msg = array_shift( $error ); 00632 } else { 00633 $msg = $error; 00634 $error = array(); 00635 } 00636 00637 $errorstr .= Html::rawElement( 00638 'li', 00639 array(), 00640 wfMsgExt( $msg, array( 'parseinline' ), $error ) 00641 ); 00642 } 00643 00644 $errorstr = Html::rawElement( 'ul', array(), $errorstr ); 00645 00646 return $errorstr; 00647 } 00648 00653 function setSubmitText( $t ) { 00654 $this->mSubmitText = $t; 00655 } 00656 00662 public function setSubmitTextMsg( $msg ) { 00663 return $this->setSubmitText( $this->msg( $msg )->escaped() ); 00664 } 00665 00670 function getSubmitText() { 00671 return $this->mSubmitText 00672 ? $this->mSubmitText 00673 : wfMsg( 'htmlform-submit' ); 00674 } 00675 00676 public function setSubmitName( $name ) { 00677 $this->mSubmitName = $name; 00678 } 00679 00680 public function setSubmitTooltip( $name ) { 00681 $this->mSubmitTooltip = $name; 00682 } 00683 00689 function setSubmitID( $t ) { 00690 $this->mSubmitID = $t; 00691 } 00692 00693 public function setId( $id ) { 00694 $this->mId = $id; 00695 } 00702 public function setWrapperLegend( $legend ) { $this->mWrapperLegend = $legend; } 00703 00710 public function setWrapperLegendMsg( $msg ) { 00711 return $this->setWrapperLegend( $this->msg( $msg )->escaped() ); 00712 } 00713 00720 function setMessagePrefix( $p ) { 00721 $this->mMessagePrefix = $p; 00722 } 00723 00728 function setTitle( $t ) { 00729 $this->mTitle = $t; 00730 } 00731 00736 function getTitle() { 00737 return $this->mTitle === false 00738 ? $this->getContext()->getTitle() 00739 : $this->mTitle; 00740 } 00741 00746 public function setMethod( $method='post' ){ 00747 $this->mMethod = $method; 00748 } 00749 00750 public function getMethod(){ 00751 return $this->mMethod; 00752 } 00753 00761 function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) { 00762 $tableHtml = ''; 00763 $subsectionHtml = ''; 00764 $hasLeftColumn = false; 00765 00766 foreach ( $fields as $key => $value ) { 00767 if ( is_object( $value ) ) { 00768 $v = empty( $value->mParams['nodata'] ) 00769 ? $this->mFieldData[$key] 00770 : $value->getDefault(); 00771 $tableHtml .= $value->getTableRow( $v ); 00772 00773 if ( $value->getLabel() != ' ' ) { 00774 $hasLeftColumn = true; 00775 } 00776 } elseif ( is_array( $value ) ) { 00777 $section = $this->displaySection( $value, $key ); 00778 $legend = $this->getLegend( $key ); 00779 if ( isset( $this->mSectionHeaders[$key] ) ) { 00780 $section = $this->mSectionHeaders[$key] . $section; 00781 } 00782 if ( isset( $this->mSectionFooters[$key] ) ) { 00783 $section .= $this->mSectionFooters[$key]; 00784 } 00785 $attributes = array(); 00786 if ( $fieldsetIDPrefix ) { 00787 $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" ); 00788 } 00789 $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n"; 00790 } 00791 } 00792 00793 $classes = array(); 00794 00795 if ( !$hasLeftColumn ) { // Avoid strange spacing when no labels exist 00796 $classes[] = 'mw-htmlform-nolabel'; 00797 } 00798 00799 $attribs = array( 00800 'class' => implode( ' ', $classes ), 00801 ); 00802 00803 if ( $sectionName ) { 00804 $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" ); 00805 } 00806 00807 $tableHtml = Html::rawElement( 'table', $attribs, 00808 Html::rawElement( 'tbody', array(), "\n$tableHtml\n" ) ) . "\n"; 00809 00810 if ( $this->mSubSectionBeforeFields ) { 00811 return $subsectionHtml . "\n" . $tableHtml; 00812 } else { 00813 return $tableHtml . "\n" . $subsectionHtml; 00814 } 00815 } 00816 00820 function loadData() { 00821 $fieldData = array(); 00822 00823 foreach ( $this->mFlatFields as $fieldname => $field ) { 00824 if ( !empty( $field->mParams['nodata'] ) ) { 00825 continue; 00826 } elseif ( !empty( $field->mParams['disabled'] ) ) { 00827 $fieldData[$fieldname] = $field->getDefault(); 00828 } else { 00829 $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() ); 00830 } 00831 } 00832 00833 # Filter data. 00834 foreach ( $fieldData as $name => &$value ) { 00835 $field = $this->mFlatFields[$name]; 00836 $value = $field->filter( $value, $this->mFlatFields ); 00837 } 00838 00839 $this->mFieldData = $fieldData; 00840 } 00841 00847 function suppressReset( $suppressReset = true ) { 00848 $this->mShowReset = !$suppressReset; 00849 } 00850 00858 function filterDataForSubmit( $data ) { 00859 return $data; 00860 } 00861 00868 public function getLegend( $key ) { 00869 return wfMsg( "{$this->mMessagePrefix}-$key" ); 00870 } 00871 00880 public function setAction( $action ) { 00881 $this->mAction = $action; 00882 } 00883 00884 } 00885 00890 abstract class HTMLFormField { 00891 00892 protected $mValidationCallback; 00893 protected $mFilterCallback; 00894 protected $mName; 00895 public $mParams; 00896 protected $mLabel; # String label. Set on construction 00897 protected $mID; 00898 protected $mClass = ''; 00899 protected $mDefault; 00900 00904 public $mParent; 00905 00914 abstract function getInputHTML( $value ); 00915 00924 function validate( $value, $alldata ) { 00925 if ( isset( $this->mParams['required'] ) && $value === '' ) { 00926 return wfMsgExt( 'htmlform-required', 'parseinline' ); 00927 } 00928 00929 if ( isset( $this->mValidationCallback ) ) { 00930 return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent ); 00931 } 00932 00933 return true; 00934 } 00935 00936 function filter( $value, $alldata ) { 00937 if ( isset( $this->mFilterCallback ) ) { 00938 $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent ); 00939 } 00940 00941 return $value; 00942 } 00943 00950 protected function needsLabel() { 00951 return true; 00952 } 00953 00960 function loadDataFromRequest( $request ) { 00961 if ( $request->getCheck( $this->mName ) ) { 00962 return $request->getText( $this->mName ); 00963 } else { 00964 return $this->getDefault(); 00965 } 00966 } 00967 00972 function __construct( $params ) { 00973 $this->mParams = $params; 00974 00975 # Generate the label from a message, if possible 00976 if ( isset( $params['label-message'] ) ) { 00977 $msgInfo = $params['label-message']; 00978 00979 if ( is_array( $msgInfo ) ) { 00980 $msg = array_shift( $msgInfo ); 00981 } else { 00982 $msg = $msgInfo; 00983 $msgInfo = array(); 00984 } 00985 00986 $this->mLabel = wfMsgExt( $msg, 'parseinline', $msgInfo ); 00987 } elseif ( isset( $params['label'] ) ) { 00988 $this->mLabel = $params['label']; 00989 } 00990 00991 $this->mName = "wp{$params['fieldname']}"; 00992 if ( isset( $params['name'] ) ) { 00993 $this->mName = $params['name']; 00994 } 00995 00996 $validName = Sanitizer::escapeId( $this->mName ); 00997 if ( $this->mName != $validName && !isset( $params['nodata'] ) ) { 00998 throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ ); 00999 } 01000 01001 $this->mID = "mw-input-{$this->mName}"; 01002 01003 if ( isset( $params['default'] ) ) { 01004 $this->mDefault = $params['default']; 01005 } 01006 01007 if ( isset( $params['id'] ) ) { 01008 $id = $params['id']; 01009 $validId = Sanitizer::escapeId( $id ); 01010 01011 if ( $id != $validId ) { 01012 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ ); 01013 } 01014 01015 $this->mID = $id; 01016 } 01017 01018 if ( isset( $params['cssclass'] ) ) { 01019 $this->mClass = $params['cssclass']; 01020 } 01021 01022 if ( isset( $params['validation-callback'] ) ) { 01023 $this->mValidationCallback = $params['validation-callback']; 01024 } 01025 01026 if ( isset( $params['filter-callback'] ) ) { 01027 $this->mFilterCallback = $params['filter-callback']; 01028 } 01029 01030 if ( isset( $params['flatlist'] ) ){ 01031 $this->mClass .= ' mw-htmlform-flatlist'; 01032 } 01033 } 01034 01041 function getTableRow( $value ) { 01042 # Check for invalid data. 01043 01044 $errors = $this->validate( $value, $this->mParent->mFieldData ); 01045 01046 $cellAttributes = array(); 01047 $verticalLabel = false; 01048 01049 if ( !empty($this->mParams['vertical-label']) ) { 01050 $cellAttributes['colspan'] = 2; 01051 $verticalLabel = true; 01052 } 01053 01054 if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) { 01055 $errors = ''; 01056 $errorClass = ''; 01057 } else { 01058 $errors = self::formatErrors( $errors ); 01059 $errorClass = 'mw-htmlform-invalid-input'; 01060 } 01061 01062 $label = $this->getLabelHtml( $cellAttributes ); 01063 $field = Html::rawElement( 01064 'td', 01065 array( 'class' => 'mw-input' ) + $cellAttributes, 01066 $this->getInputHTML( $value ) . "\n$errors" 01067 ); 01068 01069 $fieldType = get_class( $this ); 01070 01071 if ( $verticalLabel ) { 01072 $html = Html::rawElement( 'tr', 01073 array( 'class' => 'mw-htmlform-vertical-label' ), $label ); 01074 $html .= Html::rawElement( 'tr', 01075 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01076 $field ); 01077 } else { 01078 $html = Html::rawElement( 'tr', 01079 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01080 $label . $field ); 01081 } 01082 01083 $helptext = null; 01084 01085 if ( isset( $this->mParams['help-message'] ) ) { 01086 $msg = wfMessage( $this->mParams['help-message'] ); 01087 if ( $msg->exists() ) { 01088 $helptext = $msg->parse(); 01089 } 01090 } elseif ( isset( $this->mParams['help-messages'] ) ) { 01091 # help-message can be passed a message key (string) or an array containing 01092 # a message key and additional parameters. This makes it impossible to pass 01093 # an array of message key 01094 foreach( $this->mParams['help-messages'] as $name ) { 01095 $msg = wfMessage( $name ); 01096 if( $msg->exists() ) { 01097 $helptext .= $msg->parse(); // append message 01098 } 01099 } 01100 } elseif ( isset( $this->mParams['help'] ) ) { 01101 $helptext = $this->mParams['help']; 01102 } 01103 01104 if ( !is_null( $helptext ) ) { 01105 $row = Html::rawElement( 'td', array( 'colspan' => 2, 'class' => 'htmlform-tip' ), 01106 $helptext ); 01107 $row = Html::rawElement( 'tr', array(), $row ); 01108 $html .= "$row\n"; 01109 } 01110 01111 return $html; 01112 } 01113 01114 function getLabel() { 01115 return $this->mLabel; 01116 } 01117 function getLabelHtml( $cellAttributes = array() ) { 01118 # Don't output a for= attribute for labels with no associated input. 01119 # Kind of hacky here, possibly we don't want these to be <label>s at all. 01120 $for = array(); 01121 01122 if ( $this->needsLabel() ) { 01123 $for['for'] = $this->mID; 01124 } 01125 01126 return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, 01127 Html::rawElement( 'label', $for, $this->getLabel() ) 01128 ); 01129 } 01130 01131 function getDefault() { 01132 if ( isset( $this->mDefault ) ) { 01133 return $this->mDefault; 01134 } else { 01135 return null; 01136 } 01137 } 01138 01144 public function getTooltipAndAccessKey() { 01145 if ( empty( $this->mParams['tooltip'] ) ) { 01146 return array(); 01147 } 01148 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] ); 01149 } 01150 01158 public static function flattenOptions( $options ) { 01159 $flatOpts = array(); 01160 01161 foreach ( $options as $value ) { 01162 if ( is_array( $value ) ) { 01163 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) ); 01164 } else { 01165 $flatOpts[] = $value; 01166 } 01167 } 01168 01169 return $flatOpts; 01170 } 01171 01178 protected static function formatErrors( $errors ) { 01179 if ( is_array( $errors ) && count( $errors ) === 1 ) { 01180 $errors = array_shift( $errors ); 01181 } 01182 01183 if ( is_array( $errors ) ) { 01184 $lines = array(); 01185 foreach ( $errors as $error ) { 01186 if ( $error instanceof Message ) { 01187 $lines[] = Html::rawElement( 'li', array(), $error->parse() ); 01188 } else { 01189 $lines[] = Html::rawElement( 'li', array(), $error ); 01190 } 01191 } 01192 return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) ); 01193 } else { 01194 if ( $errors instanceof Message ) { 01195 $errors = $errors->parse(); 01196 } 01197 return Html::rawElement( 'span', array( 'class' => 'error' ), $errors ); 01198 } 01199 } 01200 } 01201 01202 class HTMLTextField extends HTMLFormField { 01203 function getSize() { 01204 return isset( $this->mParams['size'] ) 01205 ? $this->mParams['size'] 01206 : 45; 01207 } 01208 01209 function getInputHTML( $value ) { 01210 $attribs = array( 01211 'id' => $this->mID, 01212 'name' => $this->mName, 01213 'size' => $this->getSize(), 01214 'value' => $value, 01215 ) + $this->getTooltipAndAccessKey(); 01216 01217 if ( $this->mClass !== '' ) { 01218 $attribs['class'] = $this->mClass; 01219 } 01220 01221 if ( isset( $this->mParams['maxlength'] ) ) { 01222 $attribs['maxlength'] = $this->mParams['maxlength']; 01223 } 01224 01225 if ( !empty( $this->mParams['disabled'] ) ) { 01226 $attribs['disabled'] = 'disabled'; 01227 } 01228 01229 # TODO: Enforce pattern, step, required, readonly on the server side as 01230 # well 01231 foreach ( array( 'min', 'max', 'pattern', 'title', 'step', 01232 'placeholder' ) as $param ) { 01233 if ( isset( $this->mParams[$param] ) ) { 01234 $attribs[$param] = $this->mParams[$param]; 01235 } 01236 } 01237 01238 foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) { 01239 if ( isset( $this->mParams[$param] ) ) { 01240 $attribs[$param] = ''; 01241 } 01242 } 01243 01244 # Implement tiny differences between some field variants 01245 # here, rather than creating a new class for each one which 01246 # is essentially just a clone of this one. 01247 if ( isset( $this->mParams['type'] ) ) { 01248 switch ( $this->mParams['type'] ) { 01249 case 'email': 01250 $attribs['type'] = 'email'; 01251 break; 01252 case 'int': 01253 $attribs['type'] = 'number'; 01254 break; 01255 case 'float': 01256 $attribs['type'] = 'number'; 01257 $attribs['step'] = 'any'; 01258 break; 01259 # Pass through 01260 case 'password': 01261 case 'file': 01262 $attribs['type'] = $this->mParams['type']; 01263 break; 01264 } 01265 } 01266 01267 return Html::element( 'input', $attribs ); 01268 } 01269 } 01270 class HTMLTextAreaField extends HTMLFormField { 01271 function getCols() { 01272 return isset( $this->mParams['cols'] ) 01273 ? $this->mParams['cols'] 01274 : 80; 01275 } 01276 01277 function getRows() { 01278 return isset( $this->mParams['rows'] ) 01279 ? $this->mParams['rows'] 01280 : 25; 01281 } 01282 01283 function getInputHTML( $value ) { 01284 $attribs = array( 01285 'id' => $this->mID, 01286 'name' => $this->mName, 01287 'cols' => $this->getCols(), 01288 'rows' => $this->getRows(), 01289 ) + $this->getTooltipAndAccessKey(); 01290 01291 if ( $this->mClass !== '' ) { 01292 $attribs['class'] = $this->mClass; 01293 } 01294 01295 if ( !empty( $this->mParams['disabled'] ) ) { 01296 $attribs['disabled'] = 'disabled'; 01297 } 01298 01299 if ( !empty( $this->mParams['readonly'] ) ) { 01300 $attribs['readonly'] = 'readonly'; 01301 } 01302 01303 foreach ( array( 'required', 'autofocus' ) as $param ) { 01304 if ( isset( $this->mParams[$param] ) ) { 01305 $attribs[$param] = ''; 01306 } 01307 } 01308 01309 return Html::element( 'textarea', $attribs, $value ); 01310 } 01311 } 01312 01316 class HTMLFloatField extends HTMLTextField { 01317 function getSize() { 01318 return isset( $this->mParams['size'] ) 01319 ? $this->mParams['size'] 01320 : 20; 01321 } 01322 01323 function validate( $value, $alldata ) { 01324 $p = parent::validate( $value, $alldata ); 01325 01326 if ( $p !== true ) { 01327 return $p; 01328 } 01329 01330 $value = trim( $value ); 01331 01332 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers 01333 # with the addition that a leading '+' sign is ok. 01334 if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) { 01335 return wfMsgExt( 'htmlform-float-invalid', 'parse' ); 01336 } 01337 01338 # The "int" part of these message names is rather confusing. 01339 # They make equal sense for all numbers. 01340 if ( isset( $this->mParams['min'] ) ) { 01341 $min = $this->mParams['min']; 01342 01343 if ( $min > $value ) { 01344 return wfMsgExt( 'htmlform-int-toolow', 'parse', array( $min ) ); 01345 } 01346 } 01347 01348 if ( isset( $this->mParams['max'] ) ) { 01349 $max = $this->mParams['max']; 01350 01351 if ( $max < $value ) { 01352 return wfMsgExt( 'htmlform-int-toohigh', 'parse', array( $max ) ); 01353 } 01354 } 01355 01356 return true; 01357 } 01358 } 01359 01363 class HTMLIntField extends HTMLFloatField { 01364 function validate( $value, $alldata ) { 01365 $p = parent::validate( $value, $alldata ); 01366 01367 if ( $p !== true ) { 01368 return $p; 01369 } 01370 01371 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers 01372 # with the addition that a leading '+' sign is ok. Note that leading zeros 01373 # are fine, and will be left in the input, which is useful for things like 01374 # phone numbers when you know that they are integers (the HTML5 type=tel 01375 # input does not require its value to be numeric). If you want a tidier 01376 # value to, eg, save in the DB, clean it up with intval(). 01377 if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) ) 01378 ) { 01379 return wfMsgExt( 'htmlform-int-invalid', 'parse' ); 01380 } 01381 01382 return true; 01383 } 01384 } 01385 01389 class HTMLCheckField extends HTMLFormField { 01390 function getInputHTML( $value ) { 01391 if ( !empty( $this->mParams['invert'] ) ) { 01392 $value = !$value; 01393 } 01394 01395 $attr = $this->getTooltipAndAccessKey(); 01396 $attr['id'] = $this->mID; 01397 01398 if ( !empty( $this->mParams['disabled'] ) ) { 01399 $attr['disabled'] = 'disabled'; 01400 } 01401 01402 if ( $this->mClass !== '' ) { 01403 $attr['class'] = $this->mClass; 01404 } 01405 01406 return Xml::check( $this->mName, $value, $attr ) . ' ' . 01407 Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); 01408 } 01409 01415 function getLabel() { 01416 return ' '; 01417 } 01418 01423 function loadDataFromRequest( $request ) { 01424 $invert = false; 01425 if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) { 01426 $invert = true; 01427 } 01428 01429 // GetCheck won't work like we want for checks. 01430 // Fetch the value in either one of the two following case: 01431 // - we have a valid token (form got posted or GET forged by the user) 01432 // - checkbox name has a value (false or true), ie is not null 01433 if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName )!== null ) { 01434 // XOR has the following truth table, which is what we want 01435 // INVERT VALUE | OUTPUT 01436 // true true | false 01437 // false true | true 01438 // false false | false 01439 // true false | true 01440 return $request->getBool( $this->mName ) xor $invert; 01441 } else { 01442 return $this->getDefault(); 01443 } 01444 } 01445 } 01446 01450 class HTMLSelectField extends HTMLFormField { 01451 function validate( $value, $alldata ) { 01452 $p = parent::validate( $value, $alldata ); 01453 01454 if ( $p !== true ) { 01455 return $p; 01456 } 01457 01458 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 01459 01460 if ( in_array( $value, $validOptions ) ) 01461 return true; 01462 else 01463 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' ); 01464 } 01465 01466 function getInputHTML( $value ) { 01467 $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) ); 01468 01469 # If one of the options' 'name' is int(0), it is automatically selected. 01470 # because PHP sucks and thinks int(0) == 'some string'. 01471 # Working around this by forcing all of them to strings. 01472 foreach( $this->mParams['options'] as &$opt ){ 01473 if( is_int( $opt ) ){ 01474 $opt = strval( $opt ); 01475 } 01476 } 01477 unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary 01478 01479 if ( !empty( $this->mParams['disabled'] ) ) { 01480 $select->setAttribute( 'disabled', 'disabled' ); 01481 } 01482 01483 if ( $this->mClass !== '' ) { 01484 $select->setAttribute( 'class', $this->mClass ); 01485 } 01486 01487 $select->addOptions( $this->mParams['options'] ); 01488 01489 return $select->getHTML(); 01490 } 01491 } 01492 01496 class HTMLSelectOrOtherField extends HTMLTextField { 01497 static $jsAdded = false; 01498 01499 function __construct( $params ) { 01500 if ( !in_array( 'other', $params['options'], true ) ) { 01501 $msg = isset( $params['other'] ) ? $params['other'] : wfMsg( 'htmlform-selectorother-other' ); 01502 $params['options'][$msg] = 'other'; 01503 } 01504 01505 parent::__construct( $params ); 01506 } 01507 01508 static function forceToStringRecursive( $array ) { 01509 if ( is_array( $array ) ) { 01510 return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array ); 01511 } else { 01512 return strval( $array ); 01513 } 01514 } 01515 01516 function getInputHTML( $value ) { 01517 $valInSelect = false; 01518 01519 if ( $value !== false ) { 01520 $valInSelect = in_array( 01521 $value, 01522 HTMLFormField::flattenOptions( $this->mParams['options'] ) 01523 ); 01524 } 01525 01526 $selected = $valInSelect ? $value : 'other'; 01527 01528 $opts = self::forceToStringRecursive( $this->mParams['options'] ); 01529 01530 $select = new XmlSelect( $this->mName, $this->mID, $selected ); 01531 $select->addOptions( $opts ); 01532 01533 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' ); 01534 01535 $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() ); 01536 01537 if ( !empty( $this->mParams['disabled'] ) ) { 01538 $select->setAttribute( 'disabled', 'disabled' ); 01539 $tbAttribs['disabled'] = 'disabled'; 01540 } 01541 01542 $select = $select->getHTML(); 01543 01544 if ( isset( $this->mParams['maxlength'] ) ) { 01545 $tbAttribs['maxlength'] = $this->mParams['maxlength']; 01546 } 01547 01548 if ( $this->mClass !== '' ) { 01549 $tbAttribs['class'] = $this->mClass; 01550 } 01551 01552 $textbox = Html::input( 01553 $this->mName . '-other', 01554 $valInSelect ? '' : $value, 01555 'text', 01556 $tbAttribs 01557 ); 01558 01559 return "$select<br />\n$textbox"; 01560 } 01561 01566 function loadDataFromRequest( $request ) { 01567 if ( $request->getCheck( $this->mName ) ) { 01568 $val = $request->getText( $this->mName ); 01569 01570 if ( $val == 'other' ) { 01571 $val = $request->getText( $this->mName . '-other' ); 01572 } 01573 01574 return $val; 01575 } else { 01576 return $this->getDefault(); 01577 } 01578 } 01579 } 01580 01584 class HTMLMultiSelectField extends HTMLFormField { 01585 01586 function validate( $value, $alldata ) { 01587 $p = parent::validate( $value, $alldata ); 01588 01589 if ( $p !== true ) { 01590 return $p; 01591 } 01592 01593 if ( !is_array( $value ) ) { 01594 return false; 01595 } 01596 01597 # If all options are valid, array_intersect of the valid options 01598 # and the provided options will return the provided options. 01599 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 01600 01601 $validValues = array_intersect( $value, $validOptions ); 01602 if ( count( $validValues ) == count( $value ) ) { 01603 return true; 01604 } else { 01605 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' ); 01606 } 01607 } 01608 01609 function getInputHTML( $value ) { 01610 $html = $this->formatOptions( $this->mParams['options'], $value ); 01611 01612 return $html; 01613 } 01614 01615 function formatOptions( $options, $value ) { 01616 $html = ''; 01617 01618 $attribs = array(); 01619 01620 if ( !empty( $this->mParams['disabled'] ) ) { 01621 $attribs['disabled'] = 'disabled'; 01622 } 01623 01624 foreach ( $options as $label => $info ) { 01625 if ( is_array( $info ) ) { 01626 $html .= Html::rawElement( 'h1', array(), $label ) . "\n"; 01627 $html .= $this->formatOptions( $info, $value ); 01628 } else { 01629 $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info ); 01630 01631 $checkbox = Xml::check( 01632 $this->mName . '[]', 01633 in_array( $info, $value, true ), 01634 $attribs + $thisAttribs ); 01635 $checkbox .= ' ' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label ); 01636 01637 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox ); 01638 } 01639 } 01640 01641 return $html; 01642 } 01643 01648 function loadDataFromRequest( $request ) { 01649 if ( $this->mParent->getMethod() == 'post' ) { 01650 if( $request->wasPosted() ){ 01651 # Checkboxes are just not added to the request arrays if they're not checked, 01652 # so it's perfectly possible for there not to be an entry at all 01653 return $request->getArray( $this->mName, array() ); 01654 } else { 01655 # That's ok, the user has not yet submitted the form, so show the defaults 01656 return $this->getDefault(); 01657 } 01658 } else { 01659 # This is the impossible case: if we look at $_GET and see no data for our 01660 # field, is it because the user has not yet submitted the form, or that they 01661 # have submitted it with all the options unchecked? We will have to assume the 01662 # latter, which basically means that you can't specify 'positive' defaults 01663 # for GET forms. 01664 # @todo FIXME... 01665 return $request->getArray( $this->mName, array() ); 01666 } 01667 } 01668 01669 function getDefault() { 01670 if ( isset( $this->mDefault ) ) { 01671 return $this->mDefault; 01672 } else { 01673 return array(); 01674 } 01675 } 01676 01677 protected function needsLabel() { 01678 return false; 01679 } 01680 } 01681 01692 class HTMLSelectAndOtherField extends HTMLSelectField { 01693 01694 function __construct( $params ) { 01695 if ( array_key_exists( 'other', $params ) ) { 01696 } elseif( array_key_exists( 'other-message', $params ) ){ 01697 $params['other'] = wfMessage( $params['other-message'] )->plain(); 01698 } else { 01699 $params['other'] = null; 01700 } 01701 01702 if ( array_key_exists( 'options', $params ) ) { 01703 # Options array already specified 01704 } elseif( array_key_exists( 'options-message', $params ) ){ 01705 # Generate options array from a system message 01706 $params['options'] = self::parseMessage( 01707 wfMessage( $params['options-message'] )->inContentLanguage()->plain(), 01708 $params['other'] 01709 ); 01710 } else { 01711 # Sulk 01712 throw new MWException( 'HTMLSelectAndOtherField called without any options' ); 01713 } 01714 $this->mFlatOptions = self::flattenOptions( $params['options'] ); 01715 01716 parent::__construct( $params ); 01717 } 01718 01726 public static function parseMessage( $string, $otherName=null ) { 01727 if( $otherName === null ){ 01728 $otherName = wfMessage( 'htmlform-selectorother-other' )->plain(); 01729 } 01730 01731 $optgroup = false; 01732 $options = array( $otherName => 'other' ); 01733 01734 foreach ( explode( "\n", $string ) as $option ) { 01735 $value = trim( $option ); 01736 if ( $value == '' ) { 01737 continue; 01738 } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) { 01739 # A new group is starting... 01740 $value = trim( substr( $value, 1 ) ); 01741 $optgroup = $value; 01742 } elseif ( substr( $value, 0, 2) == '**' ) { 01743 # groupmember 01744 $opt = trim( substr( $value, 2 ) ); 01745 if( $optgroup === false ){ 01746 $options[$opt] = $opt; 01747 } else { 01748 $options[$optgroup][$opt] = $opt; 01749 } 01750 } else { 01751 # groupless reason list 01752 $optgroup = false; 01753 $options[$option] = $option; 01754 } 01755 } 01756 01757 return $options; 01758 } 01759 01760 function getInputHTML( $value ) { 01761 $select = parent::getInputHTML( $value[1] ); 01762 01763 $textAttribs = array( 01764 'id' => $this->mID . '-other', 01765 'size' => $this->getSize(), 01766 ); 01767 01768 if ( $this->mClass !== '' ) { 01769 $textAttribs['class'] = $this->mClass; 01770 } 01771 01772 foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) { 01773 if ( isset( $this->mParams[$param] ) ) { 01774 $textAttribs[$param] = ''; 01775 } 01776 } 01777 01778 $textbox = Html::input( 01779 $this->mName . '-other', 01780 $value[2], 01781 'text', 01782 $textAttribs 01783 ); 01784 01785 return "$select<br />\n$textbox"; 01786 } 01787 01792 function loadDataFromRequest( $request ) { 01793 if ( $request->getCheck( $this->mName ) ) { 01794 01795 $list = $request->getText( $this->mName ); 01796 $text = $request->getText( $this->mName . '-other' ); 01797 01798 if ( $list == 'other' ) { 01799 $final = $text; 01800 } elseif( !in_array( $list, $this->mFlatOptions ) ){ 01801 # User has spoofed the select form to give an option which wasn't 01802 # in the original offer. Sulk... 01803 $final = $text; 01804 } elseif( $text == '' ) { 01805 $final = $list; 01806 } else { 01807 $final = $list . wfMsgForContent( 'colon-separator' ) . $text; 01808 } 01809 01810 } else { 01811 $final = $this->getDefault(); 01812 01813 $list = 'other'; 01814 $text = $final; 01815 foreach ( $this->mFlatOptions as $option ) { 01816 $match = $option . wfMsgForContent( 'colon-separator' ); 01817 if( strpos( $text, $match ) === 0 ) { 01818 $list = $option; 01819 $text = substr( $text, strlen( $match ) ); 01820 break; 01821 } 01822 } 01823 } 01824 return array( $final, $list, $text ); 01825 } 01826 01827 function getSize() { 01828 return isset( $this->mParams['size'] ) 01829 ? $this->mParams['size'] 01830 : 45; 01831 } 01832 01833 function validate( $value, $alldata ) { 01834 # HTMLSelectField forces $value to be one of the options in the select 01835 # field, which is not useful here. But we do want the validation further up 01836 # the chain 01837 $p = parent::validate( $value[1], $alldata ); 01838 01839 if ( $p !== true ) { 01840 return $p; 01841 } 01842 01843 if( isset( $this->mParams['required'] ) && $value[1] === '' ){ 01844 return wfMsgExt( 'htmlform-required', 'parseinline' ); 01845 } 01846 01847 return true; 01848 } 01849 } 01850 01854 class HTMLRadioField extends HTMLFormField { 01855 01856 01857 function validate( $value, $alldata ) { 01858 $p = parent::validate( $value, $alldata ); 01859 01860 if ( $p !== true ) { 01861 return $p; 01862 } 01863 01864 if ( !is_string( $value ) && !is_int( $value ) ) { 01865 return false; 01866 } 01867 01868 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 01869 01870 if ( in_array( $value, $validOptions ) ) { 01871 return true; 01872 } else { 01873 return wfMsgExt( 'htmlform-select-badoption', 'parseinline' ); 01874 } 01875 } 01876 01883 function getInputHTML( $value ) { 01884 $html = $this->formatOptions( $this->mParams['options'], $value ); 01885 01886 return $html; 01887 } 01888 01889 function formatOptions( $options, $value ) { 01890 $html = ''; 01891 01892 $attribs = array(); 01893 if ( !empty( $this->mParams['disabled'] ) ) { 01894 $attribs['disabled'] = 'disabled'; 01895 } 01896 01897 # TODO: should this produce an unordered list perhaps? 01898 foreach ( $options as $label => $info ) { 01899 if ( is_array( $info ) ) { 01900 $html .= Html::rawElement( 'h1', array(), $label ) . "\n"; 01901 $html .= $this->formatOptions( $info, $value ); 01902 } else { 01903 $id = Sanitizer::escapeId( $this->mID . "-$info" ); 01904 $radio = Xml::radio( 01905 $this->mName, 01906 $info, 01907 $info == $value, 01908 $attribs + array( 'id' => $id ) 01909 ); 01910 $radio .= ' ' . 01911 Html::rawElement( 'label', array( 'for' => $id ), $label ); 01912 01913 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio ); 01914 } 01915 } 01916 01917 return $html; 01918 } 01919 01920 protected function needsLabel() { 01921 return false; 01922 } 01923 } 01924 01928 class HTMLInfoField extends HTMLFormField { 01929 function __construct( $info ) { 01930 $info['nodata'] = true; 01931 01932 parent::__construct( $info ); 01933 } 01934 01935 function getInputHTML( $value ) { 01936 return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value ); 01937 } 01938 01939 function getTableRow( $value ) { 01940 if ( !empty( $this->mParams['rawrow'] ) ) { 01941 return $value; 01942 } 01943 01944 return parent::getTableRow( $value ); 01945 } 01946 01947 protected function needsLabel() { 01948 return false; 01949 } 01950 } 01951 01952 class HTMLHiddenField extends HTMLFormField { 01953 public function __construct( $params ) { 01954 parent::__construct( $params ); 01955 01956 # Per HTML5 spec, hidden fields cannot be 'required' 01957 # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state 01958 unset( $this->mParams['required'] ); 01959 } 01960 01961 public function getTableRow( $value ) { 01962 $params = array(); 01963 if ( $this->mID ) { 01964 $params['id'] = $this->mID; 01965 } 01966 01967 $this->mParent->addHiddenField( 01968 $this->mName, 01969 $this->mDefault, 01970 $params 01971 ); 01972 01973 return ''; 01974 } 01975 01976 public function getInputHTML( $value ) { return ''; } 01977 } 01978 01983 class HTMLSubmitField extends HTMLFormField { 01984 01985 function __construct( $info ) { 01986 $info['nodata'] = true; 01987 parent::__construct( $info ); 01988 } 01989 01990 function getInputHTML( $value ) { 01991 return Xml::submitButton( 01992 $value, 01993 array( 01994 'class' => 'mw-htmlform-submit ' . $this->mClass, 01995 'name' => $this->mName, 01996 'id' => $this->mID, 01997 ) 01998 ); 01999 } 02000 02001 protected function needsLabel() { 02002 return false; 02003 } 02004 02011 public function validate( $value, $alldata ){ 02012 return true; 02013 } 02014 } 02015 02016 class HTMLEditTools extends HTMLFormField { 02017 public function getInputHTML( $value ) { 02018 return ''; 02019 } 02020 02021 public function getTableRow( $value ) { 02022 if ( empty( $this->mParams['message'] ) ) { 02023 $msg = wfMessage( 'edittools' ); 02024 } else { 02025 $msg = wfMessage( $this->mParams['message'] ); 02026 if ( $msg->isDisabled() ) { 02027 $msg = wfMessage( 'edittools' ); 02028 } 02029 } 02030 $msg->inContentLanguage(); 02031 02032 02033 return '<tr><td></td><td class="mw-input">' 02034 . '<div class="mw-editTools">' 02035 . $msg->parseAsBlock() 02036 . "</div></td></tr>\n"; 02037 } 02038 } 02039 02040 class HTMLApiField extends HTMLFormField { 02041 public function getTableRow( $value ) { 02042 return ''; 02043 } 02044 02045 public function getDiv( $value ) { 02046 return $this->getTableRow( $value ); 02047 } 02048 02049 public function getRaw( $value ) { 02050 return $this->getTableRow( $value ); 02051 } 02052 02053 public function getInputHTML( $value ) { 02054 return ''; 02055 } 02056 }