MediaWiki
REL1_21
|
00001 <?php 00095 class HTMLForm extends ContextSource { 00096 00097 // A mapping of 'type' inputs onto standard HTMLFormField subclasses 00098 static $typeMappings = array( 00099 'api' => 'HTMLApiField', 00100 'text' => 'HTMLTextField', 00101 'textarea' => 'HTMLTextAreaField', 00102 'select' => 'HTMLSelectField', 00103 'radio' => 'HTMLRadioField', 00104 'multiselect' => 'HTMLMultiSelectField', 00105 'check' => 'HTMLCheckField', 00106 'toggle' => 'HTMLCheckField', 00107 'int' => 'HTMLIntField', 00108 'float' => 'HTMLFloatField', 00109 'info' => 'HTMLInfoField', 00110 'selectorother' => 'HTMLSelectOrOtherField', 00111 'selectandother' => 'HTMLSelectAndOtherField', 00112 'submit' => 'HTMLSubmitField', 00113 'hidden' => 'HTMLHiddenField', 00114 'edittools' => 'HTMLEditTools', 00115 'checkmatrix' => 'HTMLCheckMatrix', 00116 00117 // HTMLTextField will output the correct type="" attribute automagically. 00118 // There are about four zillion other HTML5 input types, like url, but 00119 // we don't use those at the moment, so no point in adding all of them. 00120 'email' => 'HTMLTextField', 00121 'password' => 'HTMLTextField', 00122 ); 00123 00124 protected $mMessagePrefix; 00125 00127 protected $mFlatFields; 00128 00129 protected $mFieldTree; 00130 protected $mShowReset = false; 00131 public $mFieldData; 00132 00133 protected $mSubmitCallback; 00134 protected $mValidationErrorMessage; 00135 00136 protected $mPre = ''; 00137 protected $mHeader = ''; 00138 protected $mFooter = ''; 00139 protected $mSectionHeaders = array(); 00140 protected $mSectionFooters = array(); 00141 protected $mPost = ''; 00142 protected $mId; 00143 00144 protected $mSubmitID; 00145 protected $mSubmitName; 00146 protected $mSubmitText; 00147 protected $mSubmitTooltip; 00148 00149 protected $mTitle; 00150 protected $mMethod = 'post'; 00151 00157 protected $mAction = false; 00158 00159 protected $mUseMultipart = false; 00160 protected $mHiddenFields = array(); 00161 protected $mButtons = array(); 00162 00163 protected $mWrapperLegend = false; 00164 00172 protected $mSubSectionBeforeFields = true; 00173 00179 protected $displayFormat = 'table'; 00180 00185 protected $availableDisplayFormats = array( 00186 'table', 00187 'div', 00188 'raw', 00189 ); 00190 00198 public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) { 00199 if ( $context instanceof IContextSource ) { 00200 $this->setContext( $context ); 00201 $this->mTitle = false; // We don't need them to set a title 00202 $this->mMessagePrefix = $messagePrefix; 00203 } else { 00204 // B/C since 1.18 00205 if ( is_string( $context ) && $messagePrefix === '' ) { 00206 // it's actually $messagePrefix 00207 $this->mMessagePrefix = $context; 00208 } 00209 } 00210 00211 // Expand out into a tree. 00212 $loadedDescriptor = array(); 00213 $this->mFlatFields = array(); 00214 00215 foreach ( $descriptor as $fieldname => $info ) { 00216 $section = isset( $info['section'] ) 00217 ? $info['section'] 00218 : ''; 00219 00220 if ( isset( $info['type'] ) && $info['type'] == 'file' ) { 00221 $this->mUseMultipart = true; 00222 } 00223 00224 $field = self::loadInputFromParameters( $fieldname, $info ); 00225 $field->mParent = $this; 00226 00227 $setSection =& $loadedDescriptor; 00228 if ( $section ) { 00229 $sectionParts = explode( '/', $section ); 00230 00231 while ( count( $sectionParts ) ) { 00232 $newName = array_shift( $sectionParts ); 00233 00234 if ( !isset( $setSection[$newName] ) ) { 00235 $setSection[$newName] = array(); 00236 } 00237 00238 $setSection =& $setSection[$newName]; 00239 } 00240 } 00241 00242 $setSection[$fieldname] = $field; 00243 $this->mFlatFields[$fieldname] = $field; 00244 } 00245 00246 $this->mFieldTree = $loadedDescriptor; 00247 } 00248 00257 public function setDisplayFormat( $format ) { 00258 if ( !in_array( $format, $this->availableDisplayFormats ) ) { 00259 throw new MWException ( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) ); 00260 } 00261 $this->displayFormat = $format; 00262 return $this; 00263 } 00264 00270 public function getDisplayFormat() { 00271 return $this->displayFormat; 00272 } 00273 00279 static function addJS() { wfDeprecated( __METHOD__, '1.18' ); } 00280 00288 static function loadInputFromParameters( $fieldname, $descriptor ) { 00289 if ( isset( $descriptor['class'] ) ) { 00290 $class = $descriptor['class']; 00291 } elseif ( isset( $descriptor['type'] ) ) { 00292 $class = self::$typeMappings[$descriptor['type']]; 00293 $descriptor['class'] = $class; 00294 } else { 00295 $class = null; 00296 } 00297 00298 if ( !$class ) { 00299 throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) ); 00300 } 00301 00302 $descriptor['fieldname'] = $fieldname; 00303 00304 # TODO 00305 # This will throw a fatal error whenever someone try to use 00306 # 'class' to feed a CSS class instead of 'cssclass'. Would be 00307 # great to avoid the fatal error and show a nice error. 00308 $obj = new $class( $descriptor ); 00309 00310 return $obj; 00311 } 00312 00322 function prepareForm() { 00323 # Check if we have the info we need 00324 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) { 00325 throw new MWException( "You must call setTitle() on an HTMLForm" ); 00326 } 00327 00328 # Load data from the request. 00329 $this->loadData(); 00330 return $this; 00331 } 00332 00337 function tryAuthorizedSubmit() { 00338 $result = false; 00339 00340 $submit = false; 00341 if ( $this->getMethod() != 'post' ) { 00342 $submit = true; // no session check needed 00343 } elseif ( $this->getRequest()->wasPosted() ) { 00344 $editToken = $this->getRequest()->getVal( 'wpEditToken' ); 00345 if ( $this->getUser()->isLoggedIn() || $editToken != null ) { 00346 // Session tokens for logged-out users have no security value. 00347 // However, if the user gave one, check it in order to give a nice 00348 // "session expired" error instead of "permission denied" or such. 00349 $submit = $this->getUser()->matchEditToken( $editToken ); 00350 } else { 00351 $submit = true; 00352 } 00353 } 00354 00355 if ( $submit ) { 00356 $result = $this->trySubmit(); 00357 } 00358 00359 return $result; 00360 } 00361 00368 function show() { 00369 $this->prepareForm(); 00370 00371 $result = $this->tryAuthorizedSubmit(); 00372 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) { 00373 return $result; 00374 } 00375 00376 $this->displayForm( $result ); 00377 return false; 00378 } 00379 00388 function trySubmit() { 00389 # Check for validation 00390 foreach ( $this->mFlatFields as $fieldname => $field ) { 00391 if ( !empty( $field->mParams['nodata'] ) ) { 00392 continue; 00393 } 00394 if ( $field->validate( 00395 $this->mFieldData[$fieldname], 00396 $this->mFieldData ) 00397 !== true 00398 ) { 00399 return isset( $this->mValidationErrorMessage ) 00400 ? $this->mValidationErrorMessage 00401 : array( 'htmlform-invalid-input' ); 00402 } 00403 } 00404 00405 $callback = $this->mSubmitCallback; 00406 if ( !is_callable( $callback ) ) { 00407 throw new MWException( 'HTMLForm: no submit callback provided. Use setSubmitCallback() to set one.' ); 00408 } 00409 00410 $data = $this->filterDataForSubmit( $this->mFieldData ); 00411 00412 $res = call_user_func( $callback, $data, $this ); 00413 00414 return $res; 00415 } 00416 00426 function setSubmitCallback( $cb ) { 00427 $this->mSubmitCallback = $cb; 00428 return $this; 00429 } 00430 00437 function setValidationErrorMessage( $msg ) { 00438 $this->mValidationErrorMessage = $msg; 00439 return $this; 00440 } 00441 00447 function setIntro( $msg ) { 00448 $this->setPreText( $msg ); 00449 return $this; 00450 } 00451 00458 function setPreText( $msg ) { 00459 $this->mPre = $msg; 00460 return $this; 00461 } 00462 00468 function addPreText( $msg ) { 00469 $this->mPre .= $msg; 00470 return $this; 00471 } 00472 00479 function addHeaderText( $msg, $section = null ) { 00480 if ( is_null( $section ) ) { 00481 $this->mHeader .= $msg; 00482 } else { 00483 if ( !isset( $this->mSectionHeaders[$section] ) ) { 00484 $this->mSectionHeaders[$section] = ''; 00485 } 00486 $this->mSectionHeaders[$section] .= $msg; 00487 } 00488 return $this; 00489 } 00490 00498 function setHeaderText( $msg, $section = null ) { 00499 if ( is_null( $section ) ) { 00500 $this->mHeader = $msg; 00501 } else { 00502 $this->mSectionHeaders[$section] = $msg; 00503 } 00504 return $this; 00505 } 00506 00513 function addFooterText( $msg, $section = null ) { 00514 if ( is_null( $section ) ) { 00515 $this->mFooter .= $msg; 00516 } else { 00517 if ( !isset( $this->mSectionFooters[$section] ) ) { 00518 $this->mSectionFooters[$section] = ''; 00519 } 00520 $this->mSectionFooters[$section] .= $msg; 00521 } 00522 return $this; 00523 } 00524 00532 function setFooterText( $msg, $section = null ) { 00533 if ( is_null( $section ) ) { 00534 $this->mFooter = $msg; 00535 } else { 00536 $this->mSectionFooters[$section] = $msg; 00537 } 00538 return $this; 00539 } 00540 00546 function addPostText( $msg ) { 00547 $this->mPost .= $msg; 00548 return $this; 00549 } 00550 00556 function setPostText( $msg ) { 00557 $this->mPost = $msg; 00558 return $this; 00559 } 00560 00568 public function addHiddenField( $name, $value, $attribs = array() ) { 00569 $attribs += array( 'name' => $name ); 00570 $this->mHiddenFields[] = array( $value, $attribs ); 00571 return $this; 00572 } 00573 00582 public function addButton( $name, $value, $id = null, $attribs = null ) { 00583 $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' ); 00584 return $this; 00585 } 00586 00598 function displayForm( $submitResult ) { 00599 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) ); 00600 } 00601 00607 function getHTML( $submitResult ) { 00608 # For good measure (it is the default) 00609 $this->getOutput()->preventClickjacking(); 00610 $this->getOutput()->addModules( 'mediawiki.htmlform' ); 00611 00612 $html = '' 00613 . $this->getErrors( $submitResult ) 00614 . $this->mHeader 00615 . $this->getBody() 00616 . $this->getHiddenFields() 00617 . $this->getButtons() 00618 . $this->mFooter 00619 ; 00620 00621 $html = $this->wrapForm( $html ); 00622 00623 return '' . $this->mPre . $html . $this->mPost; 00624 } 00625 00631 function wrapForm( $html ) { 00632 00633 # Include a <fieldset> wrapper for style, if requested. 00634 if ( $this->mWrapperLegend !== false ) { 00635 $html = Xml::fieldset( $this->mWrapperLegend, $html ); 00636 } 00637 # Use multipart/form-data 00638 $encType = $this->mUseMultipart 00639 ? 'multipart/form-data' 00640 : 'application/x-www-form-urlencoded'; 00641 # Attributes 00642 $attribs = array( 00643 'action' => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction, 00644 'method' => $this->mMethod, 00645 'class' => 'visualClear', 00646 'enctype' => $encType, 00647 ); 00648 if ( !empty( $this->mId ) ) { 00649 $attribs['id'] = $this->mId; 00650 } 00651 00652 return Html::rawElement( 'form', $attribs, $html ); 00653 } 00654 00659 function getHiddenFields() { 00660 global $wgArticlePath; 00661 00662 $html = ''; 00663 if ( $this->getMethod() == 'post' ) { 00664 $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n"; 00665 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; 00666 } 00667 00668 if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) { 00669 $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n"; 00670 } 00671 00672 foreach ( $this->mHiddenFields as $data ) { 00673 list( $value, $attribs ) = $data; 00674 $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n"; 00675 } 00676 00677 return $html; 00678 } 00679 00684 function getButtons() { 00685 $html = ''; 00686 $attribs = array(); 00687 00688 if ( isset( $this->mSubmitID ) ) { 00689 $attribs['id'] = $this->mSubmitID; 00690 } 00691 00692 if ( isset( $this->mSubmitName ) ) { 00693 $attribs['name'] = $this->mSubmitName; 00694 } 00695 00696 if ( isset( $this->mSubmitTooltip ) ) { 00697 $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip ); 00698 } 00699 00700 $attribs['class'] = 'mw-htmlform-submit'; 00701 00702 $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n"; 00703 00704 if ( $this->mShowReset ) { 00705 $html .= Html::element( 00706 'input', 00707 array( 00708 'type' => 'reset', 00709 'value' => $this->msg( 'htmlform-reset' )->text() 00710 ) 00711 ) . "\n"; 00712 } 00713 00714 foreach ( $this->mButtons as $button ) { 00715 $attrs = array( 00716 'type' => 'submit', 00717 'name' => $button['name'], 00718 'value' => $button['value'] 00719 ); 00720 00721 if ( $button['attribs'] ) { 00722 $attrs += $button['attribs']; 00723 } 00724 00725 if ( isset( $button['id'] ) ) { 00726 $attrs['id'] = $button['id']; 00727 } 00728 00729 $html .= Html::element( 'input', $attrs ); 00730 } 00731 00732 return $html; 00733 } 00734 00739 function getBody() { 00740 return $this->displaySection( $this->mFieldTree ); 00741 } 00742 00748 function getErrors( $errors ) { 00749 if ( $errors instanceof Status ) { 00750 if ( $errors->isOK() ) { 00751 $errorstr = ''; 00752 } else { 00753 $errorstr = $this->getOutput()->parse( $errors->getWikiText() ); 00754 } 00755 } elseif ( is_array( $errors ) ) { 00756 $errorstr = $this->formatErrors( $errors ); 00757 } else { 00758 $errorstr = $errors; 00759 } 00760 00761 return $errorstr 00762 ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr ) 00763 : ''; 00764 } 00765 00771 public static function formatErrors( $errors ) { 00772 $errorstr = ''; 00773 00774 foreach ( $errors as $error ) { 00775 if ( is_array( $error ) ) { 00776 $msg = array_shift( $error ); 00777 } else { 00778 $msg = $error; 00779 $error = array(); 00780 } 00781 00782 $errorstr .= Html::rawElement( 00783 'li', 00784 array(), 00785 wfMessage( $msg, $error )->parse() 00786 ); 00787 } 00788 00789 $errorstr = Html::rawElement( 'ul', array(), $errorstr ); 00790 00791 return $errorstr; 00792 } 00793 00799 function setSubmitText( $t ) { 00800 $this->mSubmitText = $t; 00801 return $this; 00802 } 00803 00810 public function setSubmitTextMsg( $msg ) { 00811 $this->setSubmitText( $this->msg( $msg )->text() ); 00812 return $this; 00813 } 00814 00819 function getSubmitText() { 00820 return $this->mSubmitText 00821 ? $this->mSubmitText 00822 : $this->msg( 'htmlform-submit' )->text(); 00823 } 00824 00829 public function setSubmitName( $name ) { 00830 $this->mSubmitName = $name; 00831 return $this; 00832 } 00833 00838 public function setSubmitTooltip( $name ) { 00839 $this->mSubmitTooltip = $name; 00840 return $this; 00841 } 00842 00849 function setSubmitID( $t ) { 00850 $this->mSubmitID = $t; 00851 return $this; 00852 } 00853 00858 public function setId( $id ) { 00859 $this->mId = $id; 00860 return $this; 00861 } 00869 public function setWrapperLegend( $legend ) { 00870 $this->mWrapperLegend = $legend; 00871 return $this; 00872 } 00873 00881 public function setWrapperLegendMsg( $msg ) { 00882 $this->setWrapperLegend( $this->msg( $msg )->text() ); 00883 return $this; 00884 } 00885 00893 function setMessagePrefix( $p ) { 00894 $this->mMessagePrefix = $p; 00895 return $this; 00896 } 00897 00903 function setTitle( $t ) { 00904 $this->mTitle = $t; 00905 return $this; 00906 } 00907 00912 function getTitle() { 00913 return $this->mTitle === false 00914 ? $this->getContext()->getTitle() 00915 : $this->mTitle; 00916 } 00917 00923 public function setMethod( $method = 'post' ) { 00924 $this->mMethod = $method; 00925 return $this; 00926 } 00927 00928 public function getMethod() { 00929 return $this->mMethod; 00930 } 00931 00939 public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) { 00940 $displayFormat = $this->getDisplayFormat(); 00941 00942 $html = ''; 00943 $subsectionHtml = ''; 00944 $hasLabel = false; 00945 00946 $getFieldHtmlMethod = ( $displayFormat == 'table' ) ? 'getTableRow' : 'get' . ucfirst( $displayFormat ); 00947 00948 foreach ( $fields as $key => $value ) { 00949 if ( $value instanceof HTMLFormField ) { 00950 $v = empty( $value->mParams['nodata'] ) 00951 ? $this->mFieldData[$key] 00952 : $value->getDefault(); 00953 $html .= $value->$getFieldHtmlMethod( $v ); 00954 00955 $labelValue = trim( $value->getLabel() ); 00956 if ( $labelValue != ' ' && $labelValue !== '' ) { 00957 $hasLabel = true; 00958 } 00959 } elseif ( is_array( $value ) ) { 00960 $section = $this->displaySection( $value, $key, "$fieldsetIDPrefix$key-" ); 00961 $legend = $this->getLegend( $key ); 00962 if ( isset( $this->mSectionHeaders[$key] ) ) { 00963 $section = $this->mSectionHeaders[$key] . $section; 00964 } 00965 if ( isset( $this->mSectionFooters[$key] ) ) { 00966 $section .= $this->mSectionFooters[$key]; 00967 } 00968 $attributes = array(); 00969 if ( $fieldsetIDPrefix ) { 00970 $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" ); 00971 } 00972 $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n"; 00973 } 00974 } 00975 00976 if ( $displayFormat !== 'raw' ) { 00977 $classes = array(); 00978 00979 if ( !$hasLabel ) { // Avoid strange spacing when no labels exist 00980 $classes[] = 'mw-htmlform-nolabel'; 00981 } 00982 00983 $attribs = array( 00984 'class' => implode( ' ', $classes ), 00985 ); 00986 00987 if ( $sectionName ) { 00988 $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" ); 00989 } 00990 00991 if ( $displayFormat === 'table' ) { 00992 $html = Html::rawElement( 'table', $attribs, 00993 Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n"; 00994 } elseif ( $displayFormat === 'div' ) { 00995 $html = Html::rawElement( 'div', $attribs, "\n$html\n" ); 00996 } 00997 } 00998 00999 if ( $this->mSubSectionBeforeFields ) { 01000 return $subsectionHtml . "\n" . $html; 01001 } else { 01002 return $html . "\n" . $subsectionHtml; 01003 } 01004 } 01005 01009 function loadData() { 01010 $fieldData = array(); 01011 01012 foreach ( $this->mFlatFields as $fieldname => $field ) { 01013 if ( !empty( $field->mParams['nodata'] ) ) { 01014 continue; 01015 } elseif ( !empty( $field->mParams['disabled'] ) ) { 01016 $fieldData[$fieldname] = $field->getDefault(); 01017 } else { 01018 $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() ); 01019 } 01020 } 01021 01022 # Filter data. 01023 foreach ( $fieldData as $name => &$value ) { 01024 $field = $this->mFlatFields[$name]; 01025 $value = $field->filter( $value, $this->mFlatFields ); 01026 } 01027 01028 $this->mFieldData = $fieldData; 01029 } 01030 01037 function suppressReset( $suppressReset = true ) { 01038 $this->mShowReset = !$suppressReset; 01039 return $this; 01040 } 01041 01049 function filterDataForSubmit( $data ) { 01050 return $data; 01051 } 01052 01059 public function getLegend( $key ) { 01060 return $this->msg( "{$this->mMessagePrefix}-$key" )->text(); 01061 } 01062 01072 public function setAction( $action ) { 01073 $this->mAction = $action; 01074 return $this; 01075 } 01076 01077 } 01078 01083 abstract class HTMLFormField { 01084 01085 protected $mValidationCallback; 01086 protected $mFilterCallback; 01087 protected $mName; 01088 public $mParams; 01089 protected $mLabel; # String label. Set on construction 01090 protected $mID; 01091 protected $mClass = ''; 01092 protected $mDefault; 01093 01097 public $mParent; 01098 01107 abstract function getInputHTML( $value ); 01108 01119 function msg() { 01120 $args = func_get_args(); 01121 01122 if ( $this->mParent ) { 01123 $callback = array( $this->mParent, 'msg' ); 01124 } else { 01125 $callback = 'wfMessage'; 01126 } 01127 01128 return call_user_func_array( $callback, $args ); 01129 } 01130 01139 function validate( $value, $alldata ) { 01140 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) { 01141 return $this->msg( 'htmlform-required' )->parse(); 01142 } 01143 01144 if ( isset( $this->mValidationCallback ) ) { 01145 return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent ); 01146 } 01147 01148 return true; 01149 } 01150 01151 function filter( $value, $alldata ) { 01152 if ( isset( $this->mFilterCallback ) ) { 01153 $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent ); 01154 } 01155 01156 return $value; 01157 } 01158 01165 protected function needsLabel() { 01166 return true; 01167 } 01168 01175 function loadDataFromRequest( $request ) { 01176 if ( $request->getCheck( $this->mName ) ) { 01177 return $request->getText( $this->mName ); 01178 } else { 01179 return $this->getDefault(); 01180 } 01181 } 01182 01188 function __construct( $params ) { 01189 $this->mParams = $params; 01190 01191 # Generate the label from a message, if possible 01192 if ( isset( $params['label-message'] ) ) { 01193 $msgInfo = $params['label-message']; 01194 01195 if ( is_array( $msgInfo ) ) { 01196 $msg = array_shift( $msgInfo ); 01197 } else { 01198 $msg = $msgInfo; 01199 $msgInfo = array(); 01200 } 01201 01202 $this->mLabel = wfMessage( $msg, $msgInfo )->parse(); 01203 } elseif ( isset( $params['label'] ) ) { 01204 $this->mLabel = $params['label']; 01205 } 01206 01207 $this->mName = "wp{$params['fieldname']}"; 01208 if ( isset( $params['name'] ) ) { 01209 $this->mName = $params['name']; 01210 } 01211 01212 $validName = Sanitizer::escapeId( $this->mName ); 01213 if ( $this->mName != $validName && !isset( $params['nodata'] ) ) { 01214 throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ ); 01215 } 01216 01217 $this->mID = "mw-input-{$this->mName}"; 01218 01219 if ( isset( $params['default'] ) ) { 01220 $this->mDefault = $params['default']; 01221 } 01222 01223 if ( isset( $params['id'] ) ) { 01224 $id = $params['id']; 01225 $validId = Sanitizer::escapeId( $id ); 01226 01227 if ( $id != $validId ) { 01228 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ ); 01229 } 01230 01231 $this->mID = $id; 01232 } 01233 01234 if ( isset( $params['cssclass'] ) ) { 01235 $this->mClass = $params['cssclass']; 01236 } 01237 01238 if ( isset( $params['validation-callback'] ) ) { 01239 $this->mValidationCallback = $params['validation-callback']; 01240 } 01241 01242 if ( isset( $params['filter-callback'] ) ) { 01243 $this->mFilterCallback = $params['filter-callback']; 01244 } 01245 01246 if ( isset( $params['flatlist'] ) ) { 01247 $this->mClass .= ' mw-htmlform-flatlist'; 01248 } 01249 } 01250 01257 function getTableRow( $value ) { 01258 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); 01259 $inputHtml = $this->getInputHTML( $value ); 01260 $fieldType = get_class( $this ); 01261 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() ); 01262 $cellAttributes = array(); 01263 01264 if ( !empty( $this->mParams['vertical-label'] ) ) { 01265 $cellAttributes['colspan'] = 2; 01266 $verticalLabel = true; 01267 } else { 01268 $verticalLabel = false; 01269 } 01270 01271 $label = $this->getLabelHtml( $cellAttributes ); 01272 01273 $field = Html::rawElement( 01274 'td', 01275 array( 'class' => 'mw-input' ) + $cellAttributes, 01276 $inputHtml . "\n$errors" 01277 ); 01278 01279 if ( $verticalLabel ) { 01280 $html = Html::rawElement( 'tr', 01281 array( 'class' => 'mw-htmlform-vertical-label' ), $label ); 01282 $html .= Html::rawElement( 'tr', 01283 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01284 $field ); 01285 } else { 01286 $html = Html::rawElement( 'tr', 01287 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01288 $label . $field ); 01289 } 01290 01291 return $html . $helptext; 01292 } 01293 01301 public function getDiv( $value ) { 01302 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); 01303 $inputHtml = $this->getInputHTML( $value ); 01304 $fieldType = get_class( $this ); 01305 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() ); 01306 $cellAttributes = array(); 01307 $label = $this->getLabelHtml( $cellAttributes ); 01308 01309 $field = Html::rawElement( 01310 'div', 01311 array( 'class' => 'mw-input' ) + $cellAttributes, 01312 $inputHtml . "\n$errors" 01313 ); 01314 $html = Html::rawElement( 'div', 01315 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01316 $label . $field ); 01317 $html .= $helptext; 01318 return $html; 01319 } 01320 01328 public function getRaw( $value ) { 01329 list( $errors, ) = $this->getErrorsAndErrorClass( $value ); 01330 $inputHtml = $this->getInputHTML( $value ); 01331 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() ); 01332 $cellAttributes = array(); 01333 $label = $this->getLabelHtml( $cellAttributes ); 01334 01335 $html = "\n$errors"; 01336 $html .= $label; 01337 $html .= $inputHtml; 01338 $html .= $helptext; 01339 return $html; 01340 } 01341 01348 public function getHelpTextHtmlTable( $helptext ) { 01349 if ( is_null( $helptext ) ) { 01350 return ''; 01351 } 01352 01353 $row = Html::rawElement( 01354 'td', 01355 array( 'colspan' => 2, 'class' => 'htmlform-tip' ), 01356 $helptext 01357 ); 01358 $row = Html::rawElement( 'tr', array(), $row ); 01359 return $row; 01360 } 01361 01368 public function getHelpTextHtmlDiv( $helptext ) { 01369 if ( is_null( $helptext ) ) { 01370 return ''; 01371 } 01372 01373 $div = Html::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext ); 01374 return $div; 01375 } 01376 01383 public function getHelpTextHtmlRaw( $helptext ) { 01384 return $this->getHelpTextHtmlDiv( $helptext ); 01385 } 01386 01392 public function getHelpText() { 01393 $helptext = null; 01394 01395 if ( isset( $this->mParams['help-message'] ) ) { 01396 $this->mParams['help-messages'] = array( $this->mParams['help-message'] ); 01397 } 01398 01399 if ( isset( $this->mParams['help-messages'] ) ) { 01400 foreach ( $this->mParams['help-messages'] as $name ) { 01401 $helpMessage = (array)$name; 01402 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage ); 01403 01404 if ( $msg->exists() ) { 01405 if ( is_null( $helptext ) ) { 01406 $helptext = ''; 01407 } else { 01408 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space 01409 } 01410 $helptext .= $msg->parse(); // Append message 01411 } 01412 } 01413 } 01414 elseif ( isset( $this->mParams['help'] ) ) { 01415 $helptext = $this->mParams['help']; 01416 } 01417 return $helptext; 01418 } 01419 01426 public function getErrorsAndErrorClass( $value ) { 01427 $errors = $this->validate( $value, $this->mParent->mFieldData ); 01428 01429 if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) { 01430 $errors = ''; 01431 $errorClass = ''; 01432 } else { 01433 $errors = self::formatErrors( $errors ); 01434 $errorClass = 'mw-htmlform-invalid-input'; 01435 } 01436 return array( $errors, $errorClass ); 01437 } 01438 01439 function getLabel() { 01440 return $this->mLabel; 01441 } 01442 01443 function getLabelHtml( $cellAttributes = array() ) { 01444 # Don't output a for= attribute for labels with no associated input. 01445 # Kind of hacky here, possibly we don't want these to be <label>s at all. 01446 $for = array(); 01447 01448 if ( $this->needsLabel() ) { 01449 $for['for'] = $this->mID; 01450 } 01451 01452 $displayFormat = $this->mParent->getDisplayFormat(); 01453 $labelElement = Html::rawElement( 'label', $for, $this->getLabel() ); 01454 01455 if ( $displayFormat == 'table' ) { 01456 return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, 01457 Html::rawElement( 'label', $for, $this->getLabel() ) 01458 ); 01459 } elseif ( $displayFormat == 'div' ) { 01460 return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes, 01461 Html::rawElement( 'label', $for, $this->getLabel() ) 01462 ); 01463 } else { 01464 return $labelElement; 01465 } 01466 } 01467 01468 function getDefault() { 01469 if ( isset( $this->mDefault ) ) { 01470 return $this->mDefault; 01471 } else { 01472 return null; 01473 } 01474 } 01475 01481 public function getTooltipAndAccessKey() { 01482 if ( empty( $this->mParams['tooltip'] ) ) { 01483 return array(); 01484 } 01485 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] ); 01486 } 01487 01495 public static function flattenOptions( $options ) { 01496 $flatOpts = array(); 01497 01498 foreach ( $options as $value ) { 01499 if ( is_array( $value ) ) { 01500 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) ); 01501 } else { 01502 $flatOpts[] = $value; 01503 } 01504 } 01505 01506 return $flatOpts; 01507 } 01508 01515 protected static function formatErrors( $errors ) { 01516 if ( is_array( $errors ) && count( $errors ) === 1 ) { 01517 $errors = array_shift( $errors ); 01518 } 01519 01520 if ( is_array( $errors ) ) { 01521 $lines = array(); 01522 foreach ( $errors as $error ) { 01523 if ( $error instanceof Message ) { 01524 $lines[] = Html::rawElement( 'li', array(), $error->parse() ); 01525 } else { 01526 $lines[] = Html::rawElement( 'li', array(), $error ); 01527 } 01528 } 01529 return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) ); 01530 } else { 01531 if ( $errors instanceof Message ) { 01532 $errors = $errors->parse(); 01533 } 01534 return Html::rawElement( 'span', array( 'class' => 'error' ), $errors ); 01535 } 01536 } 01537 } 01538 01539 class HTMLTextField extends HTMLFormField { 01540 function getSize() { 01541 return isset( $this->mParams['size'] ) 01542 ? $this->mParams['size'] 01543 : 45; 01544 } 01545 01546 function getInputHTML( $value ) { 01547 $attribs = array( 01548 'id' => $this->mID, 01549 'name' => $this->mName, 01550 'size' => $this->getSize(), 01551 'value' => $value, 01552 ) + $this->getTooltipAndAccessKey(); 01553 01554 if ( $this->mClass !== '' ) { 01555 $attribs['class'] = $this->mClass; 01556 } 01557 01558 if ( !empty( $this->mParams['disabled'] ) ) { 01559 $attribs['disabled'] = 'disabled'; 01560 } 01561 01562 # TODO: Enforce pattern, step, required, readonly on the server side as 01563 # well 01564 $allowedParams = array( 'min', 'max', 'pattern', 'title', 'step', 01565 'placeholder', 'list', 'maxlength' ); 01566 foreach ( $allowedParams as $param ) { 01567 if ( isset( $this->mParams[$param] ) ) { 01568 $attribs[$param] = $this->mParams[$param]; 01569 } 01570 } 01571 01572 foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) { 01573 if ( isset( $this->mParams[$param] ) ) { 01574 $attribs[$param] = ''; 01575 } 01576 } 01577 01578 # Implement tiny differences between some field variants 01579 # here, rather than creating a new class for each one which 01580 # is essentially just a clone of this one. 01581 if ( isset( $this->mParams['type'] ) ) { 01582 switch ( $this->mParams['type'] ) { 01583 case 'email': 01584 $attribs['type'] = 'email'; 01585 break; 01586 case 'int': 01587 $attribs['type'] = 'number'; 01588 break; 01589 case 'float': 01590 $attribs['type'] = 'number'; 01591 $attribs['step'] = 'any'; 01592 break; 01593 # Pass through 01594 case 'password': 01595 case 'file': 01596 $attribs['type'] = $this->mParams['type']; 01597 break; 01598 } 01599 } 01600 01601 return Html::element( 'input', $attribs ); 01602 } 01603 } 01604 class HTMLTextAreaField extends HTMLFormField { 01605 function getCols() { 01606 return isset( $this->mParams['cols'] ) 01607 ? $this->mParams['cols'] 01608 : 80; 01609 } 01610 01611 function getRows() { 01612 return isset( $this->mParams['rows'] ) 01613 ? $this->mParams['rows'] 01614 : 25; 01615 } 01616 01617 function getInputHTML( $value ) { 01618 $attribs = array( 01619 'id' => $this->mID, 01620 'name' => $this->mName, 01621 'cols' => $this->getCols(), 01622 'rows' => $this->getRows(), 01623 ) + $this->getTooltipAndAccessKey(); 01624 01625 if ( $this->mClass !== '' ) { 01626 $attribs['class'] = $this->mClass; 01627 } 01628 01629 if ( !empty( $this->mParams['disabled'] ) ) { 01630 $attribs['disabled'] = 'disabled'; 01631 } 01632 01633 if ( !empty( $this->mParams['readonly'] ) ) { 01634 $attribs['readonly'] = 'readonly'; 01635 } 01636 01637 if ( isset( $this->mParams['placeholder'] ) ) { 01638 $attribs['placeholder'] = $this->mParams['placeholder']; 01639 } 01640 01641 foreach ( array( 'required', 'autofocus' ) as $param ) { 01642 if ( isset( $this->mParams[$param] ) ) { 01643 $attribs[$param] = ''; 01644 } 01645 } 01646 01647 return Html::element( 'textarea', $attribs, $value ); 01648 } 01649 } 01650 01654 class HTMLFloatField extends HTMLTextField { 01655 function getSize() { 01656 return isset( $this->mParams['size'] ) 01657 ? $this->mParams['size'] 01658 : 20; 01659 } 01660 01661 function validate( $value, $alldata ) { 01662 $p = parent::validate( $value, $alldata ); 01663 01664 if ( $p !== true ) { 01665 return $p; 01666 } 01667 01668 $value = trim( $value ); 01669 01670 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers 01671 # with the addition that a leading '+' sign is ok. 01672 if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) { 01673 return $this->msg( 'htmlform-float-invalid' )->parseAsBlock(); 01674 } 01675 01676 # The "int" part of these message names is rather confusing. 01677 # They make equal sense for all numbers. 01678 if ( isset( $this->mParams['min'] ) ) { 01679 $min = $this->mParams['min']; 01680 01681 if ( $min > $value ) { 01682 return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock(); 01683 } 01684 } 01685 01686 if ( isset( $this->mParams['max'] ) ) { 01687 $max = $this->mParams['max']; 01688 01689 if ( $max < $value ) { 01690 return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock(); 01691 } 01692 } 01693 01694 return true; 01695 } 01696 } 01697 01701 class HTMLIntField extends HTMLFloatField { 01702 function validate( $value, $alldata ) { 01703 $p = parent::validate( $value, $alldata ); 01704 01705 if ( $p !== true ) { 01706 return $p; 01707 } 01708 01709 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers 01710 # with the addition that a leading '+' sign is ok. Note that leading zeros 01711 # are fine, and will be left in the input, which is useful for things like 01712 # phone numbers when you know that they are integers (the HTML5 type=tel 01713 # input does not require its value to be numeric). If you want a tidier 01714 # value to, eg, save in the DB, clean it up with intval(). 01715 if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) ) 01716 ) { 01717 return $this->msg( 'htmlform-int-invalid' )->parseAsBlock(); 01718 } 01719 01720 return true; 01721 } 01722 } 01723 01727 class HTMLCheckField extends HTMLFormField { 01728 function getInputHTML( $value ) { 01729 if ( !empty( $this->mParams['invert'] ) ) { 01730 $value = !$value; 01731 } 01732 01733 $attr = $this->getTooltipAndAccessKey(); 01734 $attr['id'] = $this->mID; 01735 01736 if ( !empty( $this->mParams['disabled'] ) ) { 01737 $attr['disabled'] = 'disabled'; 01738 } 01739 01740 if ( $this->mClass !== '' ) { 01741 $attr['class'] = $this->mClass; 01742 } 01743 01744 return Xml::check( $this->mName, $value, $attr ) . ' ' . 01745 Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel ); 01746 } 01747 01753 function getLabel() { 01754 return ' '; 01755 } 01756 01761 function loadDataFromRequest( $request ) { 01762 $invert = false; 01763 if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) { 01764 $invert = true; 01765 } 01766 01767 // GetCheck won't work like we want for checks. 01768 // Fetch the value in either one of the two following case: 01769 // - we have a valid token (form got posted or GET forged by the user) 01770 // - checkbox name has a value (false or true), ie is not null 01771 if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) { 01772 // XOR has the following truth table, which is what we want 01773 // INVERT VALUE | OUTPUT 01774 // true true | false 01775 // false true | true 01776 // false false | false 01777 // true false | true 01778 return $request->getBool( $this->mName ) xor $invert; 01779 } else { 01780 return $this->getDefault(); 01781 } 01782 } 01783 } 01784 01791 class HTMLCheckMatrix extends HTMLFormField { 01792 01793 function validate( $value, $alldata ) { 01794 $rows = $this->mParams['rows']; 01795 $columns = $this->mParams['columns']; 01796 01797 // Make sure user-defined validation callback is run 01798 $p = parent::validate( $value, $alldata ); 01799 if ( $p !== true ) { 01800 return $p; 01801 } 01802 01803 // Make sure submitted value is an array 01804 if ( !is_array( $value ) ) { 01805 return false; 01806 } 01807 01808 // If all options are valid, array_intersect of the valid options 01809 // and the provided options will return the provided options. 01810 $validOptions = array(); 01811 foreach ( $rows as $rowTag ) { 01812 foreach ( $columns as $columnTag ) { 01813 $validOptions[] = $columnTag . '-' . $rowTag; 01814 } 01815 } 01816 $validValues = array_intersect( $value, $validOptions ); 01817 if ( count( $validValues ) == count( $value ) ) { 01818 return true; 01819 } else { 01820 return $this->msg( 'htmlform-select-badoption' )->parse(); 01821 } 01822 } 01823 01832 function getInputHTML( $value ) { 01833 $html = ''; 01834 $tableContents = ''; 01835 $attribs = array(); 01836 $rows = $this->mParams['rows']; 01837 $columns = $this->mParams['columns']; 01838 01839 // If the disabled param is set, disable all the options 01840 if ( !empty( $this->mParams['disabled'] ) ) { 01841 $attribs['disabled'] = 'disabled'; 01842 } 01843 01844 // Build the column headers 01845 $headerContents = Html::rawElement( 'td', array(), ' ' ); 01846 foreach ( $columns as $columnLabel => $columnTag ) { 01847 $headerContents .= Html::rawElement( 'td', array(), $columnLabel ); 01848 } 01849 $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" ); 01850 01851 // Build the options matrix 01852 foreach ( $rows as $rowLabel => $rowTag ) { 01853 $rowContents = Html::rawElement( 'td', array(), $rowLabel ); 01854 foreach ( $columns as $columnTag ) { 01855 // Knock out any options that are not wanted 01856 if ( isset( $this->mParams['remove-options'] ) 01857 && in_array( "$columnTag-$rowTag", $this->mParams['remove-options'] ) ) 01858 { 01859 $rowContents .= Html::rawElement( 'td', array(), ' ' ); 01860 } else { 01861 // Construct the checkbox 01862 $thisAttribs = array( 01863 'id' => "{$this->mID}-$columnTag-$rowTag", 01864 'value' => $columnTag . '-' . $rowTag 01865 ); 01866 $checkbox = Xml::check( 01867 $this->mName . '[]', 01868 in_array( $columnTag . '-' . $rowTag, (array)$value, true ), 01869 $attribs + $thisAttribs ); 01870 $rowContents .= Html::rawElement( 'td', array(), $checkbox ); 01871 } 01872 } 01873 $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" ); 01874 } 01875 01876 // Put it all in a table 01877 $html .= Html::rawElement( 'table', array( 'class' => 'mw-htmlform-matrix' ), 01878 Html::rawElement( 'tbody', array(), "\n$tableContents\n" ) ) . "\n"; 01879 01880 return $html; 01881 } 01882 01892 function getTableRow( $value ) { 01893 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value ); 01894 $inputHtml = $this->getInputHTML( $value ); 01895 $fieldType = get_class( $this ); 01896 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() ); 01897 $cellAttributes = array( 'colspan' => 2 ); 01898 01899 $label = $this->getLabelHtml( $cellAttributes ); 01900 01901 $field = Html::rawElement( 01902 'td', 01903 array( 'class' => 'mw-input' ) + $cellAttributes, 01904 $inputHtml . "\n$errors" 01905 ); 01906 01907 $html = Html::rawElement( 'tr', 01908 array( 'class' => 'mw-htmlform-vertical-label' ), $label ); 01909 $html .= Html::rawElement( 'tr', 01910 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ), 01911 $field ); 01912 01913 return $html . $helptext; 01914 } 01915 01920 function loadDataFromRequest( $request ) { 01921 if ( $this->mParent->getMethod() == 'post' ) { 01922 if ( $request->wasPosted() ) { 01923 // Checkboxes are not added to the request arrays if they're not checked, 01924 // so it's perfectly possible for there not to be an entry at all 01925 return $request->getArray( $this->mName, array() ); 01926 } else { 01927 // That's ok, the user has not yet submitted the form, so show the defaults 01928 return $this->getDefault(); 01929 } 01930 } else { 01931 // This is the impossible case: if we look at $_GET and see no data for our 01932 // field, is it because the user has not yet submitted the form, or that they 01933 // have submitted it with all the options unchecked. We will have to assume the 01934 // latter, which basically means that you can't specify 'positive' defaults 01935 // for GET forms. 01936 return $request->getArray( $this->mName, array() ); 01937 } 01938 } 01939 01940 function getDefault() { 01941 if ( isset( $this->mDefault ) ) { 01942 return $this->mDefault; 01943 } else { 01944 return array(); 01945 } 01946 } 01947 } 01948 01952 class HTMLSelectField extends HTMLFormField { 01953 function validate( $value, $alldata ) { 01954 $p = parent::validate( $value, $alldata ); 01955 01956 if ( $p !== true ) { 01957 return $p; 01958 } 01959 01960 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 01961 01962 if ( in_array( $value, $validOptions ) ) 01963 return true; 01964 else 01965 return $this->msg( 'htmlform-select-badoption' )->parse(); 01966 } 01967 01968 function getInputHTML( $value ) { 01969 $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) ); 01970 01971 # If one of the options' 'name' is int(0), it is automatically selected. 01972 # because PHP sucks and thinks int(0) == 'some string'. 01973 # Working around this by forcing all of them to strings. 01974 foreach ( $this->mParams['options'] as &$opt ) { 01975 if ( is_int( $opt ) ) { 01976 $opt = strval( $opt ); 01977 } 01978 } 01979 unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary 01980 01981 if ( !empty( $this->mParams['disabled'] ) ) { 01982 $select->setAttribute( 'disabled', 'disabled' ); 01983 } 01984 01985 if ( $this->mClass !== '' ) { 01986 $select->setAttribute( 'class', $this->mClass ); 01987 } 01988 01989 $select->addOptions( $this->mParams['options'] ); 01990 01991 return $select->getHTML(); 01992 } 01993 } 01994 01998 class HTMLSelectOrOtherField extends HTMLTextField { 01999 static $jsAdded = false; 02000 02001 function __construct( $params ) { 02002 if ( !in_array( 'other', $params['options'], true ) ) { 02003 $msg = isset( $params['other'] ) ? 02004 $params['other'] : 02005 wfMessage( 'htmlform-selectorother-other' )->text(); 02006 $params['options'][$msg] = 'other'; 02007 } 02008 02009 parent::__construct( $params ); 02010 } 02011 02012 static function forceToStringRecursive( $array ) { 02013 if ( is_array( $array ) ) { 02014 return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array ); 02015 } else { 02016 return strval( $array ); 02017 } 02018 } 02019 02020 function getInputHTML( $value ) { 02021 $valInSelect = false; 02022 02023 if ( $value !== false ) { 02024 $valInSelect = in_array( 02025 $value, 02026 HTMLFormField::flattenOptions( $this->mParams['options'] ) 02027 ); 02028 } 02029 02030 $selected = $valInSelect ? $value : 'other'; 02031 02032 $opts = self::forceToStringRecursive( $this->mParams['options'] ); 02033 02034 $select = new XmlSelect( $this->mName, $this->mID, $selected ); 02035 $select->addOptions( $opts ); 02036 02037 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' ); 02038 02039 $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() ); 02040 02041 if ( !empty( $this->mParams['disabled'] ) ) { 02042 $select->setAttribute( 'disabled', 'disabled' ); 02043 $tbAttribs['disabled'] = 'disabled'; 02044 } 02045 02046 $select = $select->getHTML(); 02047 02048 if ( isset( $this->mParams['maxlength'] ) ) { 02049 $tbAttribs['maxlength'] = $this->mParams['maxlength']; 02050 } 02051 02052 if ( $this->mClass !== '' ) { 02053 $tbAttribs['class'] = $this->mClass; 02054 } 02055 02056 $textbox = Html::input( 02057 $this->mName . '-other', 02058 $valInSelect ? '' : $value, 02059 'text', 02060 $tbAttribs 02061 ); 02062 02063 return "$select<br />\n$textbox"; 02064 } 02065 02070 function loadDataFromRequest( $request ) { 02071 if ( $request->getCheck( $this->mName ) ) { 02072 $val = $request->getText( $this->mName ); 02073 02074 if ( $val == 'other' ) { 02075 $val = $request->getText( $this->mName . '-other' ); 02076 } 02077 02078 return $val; 02079 } else { 02080 return $this->getDefault(); 02081 } 02082 } 02083 } 02084 02088 class HTMLMultiSelectField extends HTMLFormField { 02089 02090 function validate( $value, $alldata ) { 02091 $p = parent::validate( $value, $alldata ); 02092 02093 if ( $p !== true ) { 02094 return $p; 02095 } 02096 02097 if ( !is_array( $value ) ) { 02098 return false; 02099 } 02100 02101 # If all options are valid, array_intersect of the valid options 02102 # and the provided options will return the provided options. 02103 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 02104 02105 $validValues = array_intersect( $value, $validOptions ); 02106 if ( count( $validValues ) == count( $value ) ) { 02107 return true; 02108 } else { 02109 return $this->msg( 'htmlform-select-badoption' )->parse(); 02110 } 02111 } 02112 02113 function getInputHTML( $value ) { 02114 $html = $this->formatOptions( $this->mParams['options'], $value ); 02115 02116 return $html; 02117 } 02118 02119 function formatOptions( $options, $value ) { 02120 $html = ''; 02121 02122 $attribs = array(); 02123 02124 if ( !empty( $this->mParams['disabled'] ) ) { 02125 $attribs['disabled'] = 'disabled'; 02126 } 02127 02128 foreach ( $options as $label => $info ) { 02129 if ( is_array( $info ) ) { 02130 $html .= Html::rawElement( 'h1', array(), $label ) . "\n"; 02131 $html .= $this->formatOptions( $info, $value ); 02132 } else { 02133 $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info ); 02134 02135 $checkbox = Xml::check( 02136 $this->mName . '[]', 02137 in_array( $info, $value, true ), 02138 $attribs + $thisAttribs ); 02139 $checkbox .= ' ' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label ); 02140 02141 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox ); 02142 } 02143 } 02144 02145 return $html; 02146 } 02147 02152 function loadDataFromRequest( $request ) { 02153 if ( $this->mParent->getMethod() == 'post' ) { 02154 if ( $request->wasPosted() ) { 02155 # Checkboxes are just not added to the request arrays if they're not checked, 02156 # so it's perfectly possible for there not to be an entry at all 02157 return $request->getArray( $this->mName, array() ); 02158 } else { 02159 # That's ok, the user has not yet submitted the form, so show the defaults 02160 return $this->getDefault(); 02161 } 02162 } else { 02163 # This is the impossible case: if we look at $_GET and see no data for our 02164 # field, is it because the user has not yet submitted the form, or that they 02165 # have submitted it with all the options unchecked? We will have to assume the 02166 # latter, which basically means that you can't specify 'positive' defaults 02167 # for GET forms. 02168 # @todo FIXME... 02169 return $request->getArray( $this->mName, array() ); 02170 } 02171 } 02172 02173 function getDefault() { 02174 if ( isset( $this->mDefault ) ) { 02175 return $this->mDefault; 02176 } else { 02177 return array(); 02178 } 02179 } 02180 02181 protected function needsLabel() { 02182 return false; 02183 } 02184 } 02185 02196 class HTMLSelectAndOtherField extends HTMLSelectField { 02197 02198 function __construct( $params ) { 02199 if ( array_key_exists( 'other', $params ) ) { 02200 } elseif ( array_key_exists( 'other-message', $params ) ) { 02201 $params['other'] = wfMessage( $params['other-message'] )->plain(); 02202 } else { 02203 $params['other'] = null; 02204 } 02205 02206 if ( array_key_exists( 'options', $params ) ) { 02207 # Options array already specified 02208 } elseif ( array_key_exists( 'options-message', $params ) ) { 02209 # Generate options array from a system message 02210 $params['options'] = self::parseMessage( 02211 wfMessage( $params['options-message'] )->inContentLanguage()->plain(), 02212 $params['other'] 02213 ); 02214 } else { 02215 # Sulk 02216 throw new MWException( 'HTMLSelectAndOtherField called without any options' ); 02217 } 02218 $this->mFlatOptions = self::flattenOptions( $params['options'] ); 02219 02220 parent::__construct( $params ); 02221 } 02222 02230 public static function parseMessage( $string, $otherName = null ) { 02231 if ( $otherName === null ) { 02232 $otherName = wfMessage( 'htmlform-selectorother-other' )->plain(); 02233 } 02234 02235 $optgroup = false; 02236 $options = array( $otherName => 'other' ); 02237 02238 foreach ( explode( "\n", $string ) as $option ) { 02239 $value = trim( $option ); 02240 if ( $value == '' ) { 02241 continue; 02242 } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) { 02243 # A new group is starting... 02244 $value = trim( substr( $value, 1 ) ); 02245 $optgroup = $value; 02246 } elseif ( substr( $value, 0, 2 ) == '**' ) { 02247 # groupmember 02248 $opt = trim( substr( $value, 2 ) ); 02249 if ( $optgroup === false ) { 02250 $options[$opt] = $opt; 02251 } else { 02252 $options[$optgroup][$opt] = $opt; 02253 } 02254 } else { 02255 # groupless reason list 02256 $optgroup = false; 02257 $options[$option] = $option; 02258 } 02259 } 02260 02261 return $options; 02262 } 02263 02264 function getInputHTML( $value ) { 02265 $select = parent::getInputHTML( $value[1] ); 02266 02267 $textAttribs = array( 02268 'id' => $this->mID . '-other', 02269 'size' => $this->getSize(), 02270 ); 02271 02272 if ( $this->mClass !== '' ) { 02273 $textAttribs['class'] = $this->mClass; 02274 } 02275 02276 foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) { 02277 if ( isset( $this->mParams[$param] ) ) { 02278 $textAttribs[$param] = ''; 02279 } 02280 } 02281 02282 $textbox = Html::input( 02283 $this->mName . '-other', 02284 $value[2], 02285 'text', 02286 $textAttribs 02287 ); 02288 02289 return "$select<br />\n$textbox"; 02290 } 02291 02296 function loadDataFromRequest( $request ) { 02297 if ( $request->getCheck( $this->mName ) ) { 02298 02299 $list = $request->getText( $this->mName ); 02300 $text = $request->getText( $this->mName . '-other' ); 02301 02302 if ( $list == 'other' ) { 02303 $final = $text; 02304 } elseif ( !in_array( $list, $this->mFlatOptions ) ) { 02305 # User has spoofed the select form to give an option which wasn't 02306 # in the original offer. Sulk... 02307 $final = $text; 02308 } elseif ( $text == '' ) { 02309 $final = $list; 02310 } else { 02311 $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text; 02312 } 02313 02314 } else { 02315 $final = $this->getDefault(); 02316 02317 $list = 'other'; 02318 $text = $final; 02319 foreach ( $this->mFlatOptions as $option ) { 02320 $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text(); 02321 if ( strpos( $text, $match ) === 0 ) { 02322 $list = $option; 02323 $text = substr( $text, strlen( $match ) ); 02324 break; 02325 } 02326 } 02327 } 02328 return array( $final, $list, $text ); 02329 } 02330 02331 function getSize() { 02332 return isset( $this->mParams['size'] ) 02333 ? $this->mParams['size'] 02334 : 45; 02335 } 02336 02337 function validate( $value, $alldata ) { 02338 # HTMLSelectField forces $value to be one of the options in the select 02339 # field, which is not useful here. But we do want the validation further up 02340 # the chain 02341 $p = parent::validate( $value[1], $alldata ); 02342 02343 if ( $p !== true ) { 02344 return $p; 02345 } 02346 02347 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) { 02348 return $this->msg( 'htmlform-required' )->parse(); 02349 } 02350 02351 return true; 02352 } 02353 } 02354 02358 class HTMLRadioField extends HTMLFormField { 02359 02360 function validate( $value, $alldata ) { 02361 $p = parent::validate( $value, $alldata ); 02362 02363 if ( $p !== true ) { 02364 return $p; 02365 } 02366 02367 if ( !is_string( $value ) && !is_int( $value ) ) { 02368 return false; 02369 } 02370 02371 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] ); 02372 02373 if ( in_array( $value, $validOptions ) ) { 02374 return true; 02375 } else { 02376 return $this->msg( 'htmlform-select-badoption' )->parse(); 02377 } 02378 } 02379 02386 function getInputHTML( $value ) { 02387 $html = $this->formatOptions( $this->mParams['options'], $value ); 02388 02389 return $html; 02390 } 02391 02392 function formatOptions( $options, $value ) { 02393 $html = ''; 02394 02395 $attribs = array(); 02396 if ( !empty( $this->mParams['disabled'] ) ) { 02397 $attribs['disabled'] = 'disabled'; 02398 } 02399 02400 # TODO: should this produce an unordered list perhaps? 02401 foreach ( $options as $label => $info ) { 02402 if ( is_array( $info ) ) { 02403 $html .= Html::rawElement( 'h1', array(), $label ) . "\n"; 02404 $html .= $this->formatOptions( $info, $value ); 02405 } else { 02406 $id = Sanitizer::escapeId( $this->mID . "-$info" ); 02407 $radio = Xml::radio( 02408 $this->mName, 02409 $info, 02410 $info == $value, 02411 $attribs + array( 'id' => $id ) 02412 ); 02413 $radio .= ' ' . 02414 Html::rawElement( 'label', array( 'for' => $id ), $label ); 02415 02416 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio ); 02417 } 02418 } 02419 02420 return $html; 02421 } 02422 02423 protected function needsLabel() { 02424 return false; 02425 } 02426 } 02427 02431 class HTMLInfoField extends HTMLFormField { 02432 public function __construct( $info ) { 02433 $info['nodata'] = true; 02434 02435 parent::__construct( $info ); 02436 } 02437 02438 public function getInputHTML( $value ) { 02439 return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value ); 02440 } 02441 02442 public function getTableRow( $value ) { 02443 if ( !empty( $this->mParams['rawrow'] ) ) { 02444 return $value; 02445 } 02446 02447 return parent::getTableRow( $value ); 02448 } 02449 02453 public function getDiv( $value ) { 02454 if ( !empty( $this->mParams['rawrow'] ) ) { 02455 return $value; 02456 } 02457 02458 return parent::getDiv( $value ); 02459 } 02460 02464 public function getRaw( $value ) { 02465 if ( !empty( $this->mParams['rawrow'] ) ) { 02466 return $value; 02467 } 02468 02469 return parent::getRaw( $value ); 02470 } 02471 02472 protected function needsLabel() { 02473 return false; 02474 } 02475 } 02476 02477 class HTMLHiddenField extends HTMLFormField { 02478 public function __construct( $params ) { 02479 parent::__construct( $params ); 02480 02481 # Per HTML5 spec, hidden fields cannot be 'required' 02482 # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state 02483 unset( $this->mParams['required'] ); 02484 } 02485 02486 public function getTableRow( $value ) { 02487 $params = array(); 02488 if ( $this->mID ) { 02489 $params['id'] = $this->mID; 02490 } 02491 02492 $this->mParent->addHiddenField( 02493 $this->mName, 02494 $this->mDefault, 02495 $params 02496 ); 02497 02498 return ''; 02499 } 02500 02504 public function getDiv( $value ) { 02505 return $this->getTableRow( $value ); 02506 } 02507 02511 public function getRaw( $value ) { 02512 return $this->getTableRow( $value ); 02513 } 02514 02515 public function getInputHTML( $value ) { return ''; } 02516 } 02517 02522 class HTMLSubmitField extends HTMLFormField { 02523 02524 public function __construct( $info ) { 02525 $info['nodata'] = true; 02526 parent::__construct( $info ); 02527 } 02528 02529 public function getInputHTML( $value ) { 02530 return Xml::submitButton( 02531 $value, 02532 array( 02533 'class' => 'mw-htmlform-submit ' . $this->mClass, 02534 'name' => $this->mName, 02535 'id' => $this->mID, 02536 ) 02537 ); 02538 } 02539 02540 protected function needsLabel() { 02541 return false; 02542 } 02543 02550 public function validate( $value, $alldata ) { 02551 return true; 02552 } 02553 } 02554 02555 class HTMLEditTools extends HTMLFormField { 02556 public function getInputHTML( $value ) { 02557 return ''; 02558 } 02559 02560 public function getTableRow( $value ) { 02561 $msg = $this->formatMsg(); 02562 02563 return '<tr><td></td><td class="mw-input">' 02564 . '<div class="mw-editTools">' 02565 . $msg->parseAsBlock() 02566 . "</div></td></tr>\n"; 02567 } 02568 02572 public function getDiv( $value ) { 02573 $msg = $this->formatMsg(); 02574 return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>'; 02575 } 02576 02580 public function getRaw( $value ) { 02581 return $this->getDiv( $value ); 02582 } 02583 02584 protected function formatMsg() { 02585 if ( empty( $this->mParams['message'] ) ) { 02586 $msg = $this->msg( 'edittools' ); 02587 } else { 02588 $msg = $this->msg( $this->mParams['message'] ); 02589 if ( $msg->isDisabled() ) { 02590 $msg = $this->msg( 'edittools' ); 02591 } 02592 } 02593 $msg->inContentLanguage(); 02594 return $msg; 02595 } 02596 } 02597 02598 class HTMLApiField extends HTMLFormField { 02599 public function getTableRow( $value ) { 02600 return ''; 02601 } 02602 02603 public function getDiv( $value ) { 02604 return $this->getTableRow( $value ); 02605 } 02606 02607 public function getRaw( $value ) { 02608 return $this->getTableRow( $value ); 02609 } 02610 02611 public function getInputHTML( $value ) { 02612 return ''; 02613 } 02614 }