MediaWiki  REL1_21
HTMLForm.php
Go to the documentation of this file.
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 != '&#160;' && $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 ) . '&#160;' .
01745                         Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
01746         }
01747 
01753         function getLabel() {
01754                 return '&#160;';
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(), '&#160;' );
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(), '&#160;' );
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 .= '&#160;' . 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 .= '&#160;' .
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 }