MediaWiki  REL1_22
HTMLForm.php
Go to the documentation of this file.
00001 <?php
00094 class HTMLForm extends ContextSource {
00095 
00096     // A mapping of 'type' inputs onto standard HTMLFormField subclasses
00097     public static $typeMappings = array(
00098         'api' => 'HTMLApiField',
00099         'text' => 'HTMLTextField',
00100         'textarea' => 'HTMLTextAreaField',
00101         'select' => 'HTMLSelectField',
00102         'radio' => 'HTMLRadioField',
00103         'multiselect' => 'HTMLMultiSelectField',
00104         'check' => 'HTMLCheckField',
00105         'toggle' => 'HTMLCheckField',
00106         'int' => 'HTMLIntField',
00107         'float' => 'HTMLFloatField',
00108         'info' => 'HTMLInfoField',
00109         'selectorother' => 'HTMLSelectOrOtherField',
00110         'selectandother' => 'HTMLSelectAndOtherField',
00111         'submit' => 'HTMLSubmitField',
00112         'hidden' => 'HTMLHiddenField',
00113         'edittools' => 'HTMLEditTools',
00114         'checkmatrix' => 'HTMLCheckMatrix',
00115 
00116         // HTMLTextField will output the correct type="" attribute automagically.
00117         // There are about four zillion other HTML5 input types, like url, but
00118         // we don't use those at the moment, so no point in adding all of them.
00119         'email' => 'HTMLTextField',
00120         'password' => 'HTMLTextField',
00121     );
00122 
00123     protected $mMessagePrefix;
00124 
00126     protected $mFlatFields;
00127 
00128     protected $mFieldTree;
00129     protected $mShowReset = false;
00130     protected $mShowSubmit = true;
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     protected $mTableId = '';
00144 
00145     protected $mSubmitID;
00146     protected $mSubmitName;
00147     protected $mSubmitText;
00148     protected $mSubmitTooltip;
00149 
00150     protected $mTitle;
00151     protected $mMethod = 'post';
00152 
00158     protected $mAction = false;
00159 
00160     protected $mUseMultipart = false;
00161     protected $mHiddenFields = array();
00162     protected $mButtons = array();
00163 
00164     protected $mWrapperLegend = false;
00165 
00173     protected $mSubSectionBeforeFields = true;
00174 
00180     protected $displayFormat = 'table';
00181 
00186     protected $availableDisplayFormats = array(
00187         'table',
00188         'div',
00189         'raw',
00190         'vform',
00191     );
00192 
00200     public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
00201         if ( $context instanceof IContextSource ) {
00202             $this->setContext( $context );
00203             $this->mTitle = false; // We don't need them to set a title
00204             $this->mMessagePrefix = $messagePrefix;
00205         } elseif ( is_null( $context ) && $messagePrefix !== '' ) {
00206             $this->mMessagePrefix = $messagePrefix;
00207         } elseif ( is_string( $context ) && $messagePrefix === '' ) {
00208             // B/C since 1.18
00209             // it's actually $messagePrefix
00210             $this->mMessagePrefix = $context;
00211         }
00212 
00213         // Expand out into a tree.
00214         $loadedDescriptor = array();
00215         $this->mFlatFields = array();
00216 
00217         foreach ( $descriptor as $fieldname => $info ) {
00218             $section = isset( $info['section'] )
00219                 ? $info['section']
00220                 : '';
00221 
00222             if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
00223                 $this->mUseMultipart = true;
00224             }
00225 
00226             $field = self::loadInputFromParameters( $fieldname, $info );
00227             // FIXME During field's construct, the parent form isn't available!
00228             // could add a 'parent' name-value to $info, could add a third parameter.
00229             $field->mParent = $this;
00230 
00231             // vform gets too much space if empty labels generate HTML.
00232             if ( $this->isVForm() ) {
00233                 $field->setShowEmptyLabel( false );
00234             }
00235 
00236             $setSection =& $loadedDescriptor;
00237             if ( $section ) {
00238                 $sectionParts = explode( '/', $section );
00239 
00240                 while ( count( $sectionParts ) ) {
00241                     $newName = array_shift( $sectionParts );
00242 
00243                     if ( !isset( $setSection[$newName] ) ) {
00244                         $setSection[$newName] = array();
00245                     }
00246 
00247                     $setSection =& $setSection[$newName];
00248                 }
00249             }
00250 
00251             $setSection[$fieldname] = $field;
00252             $this->mFlatFields[$fieldname] = $field;
00253         }
00254 
00255         $this->mFieldTree = $loadedDescriptor;
00256     }
00257 
00266     public function setDisplayFormat( $format ) {
00267         if ( !in_array( $format, $this->availableDisplayFormats ) ) {
00268             throw new MWException( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) );
00269         }
00270         $this->displayFormat = $format;
00271         return $this;
00272     }
00273 
00279     public function getDisplayFormat() {
00280         return $this->displayFormat;
00281     }
00282 
00288     public function isVForm() {
00289         return $this->displayFormat === 'vform';
00290     }
00291 
00297     static function addJS() {
00298         wfDeprecated( __METHOD__, '1.18' );
00299     }
00300 
00308     static function loadInputFromParameters( $fieldname, $descriptor ) {
00309         if ( isset( $descriptor['class'] ) ) {
00310             $class = $descriptor['class'];
00311         } elseif ( isset( $descriptor['type'] ) ) {
00312             $class = self::$typeMappings[$descriptor['type']];
00313             $descriptor['class'] = $class;
00314         } else {
00315             $class = null;
00316         }
00317 
00318         if ( !$class ) {
00319             throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
00320         }
00321 
00322         $descriptor['fieldname'] = $fieldname;
00323 
00324         # TODO
00325         # This will throw a fatal error whenever someone try to use
00326         # 'class' to feed a CSS class instead of 'cssclass'. Would be
00327         # great to avoid the fatal error and show a nice error.
00328         $obj = new $class( $descriptor );
00329 
00330         return $obj;
00331     }
00332 
00342     function prepareForm() {
00343         # Check if we have the info we need
00344         if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
00345             throw new MWException( "You must call setTitle() on an HTMLForm" );
00346         }
00347 
00348         # Load data from the request.
00349         $this->loadData();
00350         return $this;
00351     }
00352 
00357     function tryAuthorizedSubmit() {
00358         $result = false;
00359 
00360         $submit = false;
00361         if ( $this->getMethod() != 'post' ) {
00362             $submit = true; // no session check needed
00363         } elseif ( $this->getRequest()->wasPosted() ) {
00364             $editToken = $this->getRequest()->getVal( 'wpEditToken' );
00365             if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
00366                 // Session tokens for logged-out users have no security value.
00367                 // However, if the user gave one, check it in order to give a nice
00368                 // "session expired" error instead of "permission denied" or such.
00369                 $submit = $this->getUser()->matchEditToken( $editToken );
00370             } else {
00371                 $submit = true;
00372             }
00373         }
00374 
00375         if ( $submit ) {
00376             $result = $this->trySubmit();
00377         }
00378 
00379         return $result;
00380     }
00381 
00388     function show() {
00389         $this->prepareForm();
00390 
00391         $result = $this->tryAuthorizedSubmit();
00392         if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
00393             return $result;
00394         }
00395 
00396         $this->displayForm( $result );
00397         return false;
00398     }
00399 
00408     function trySubmit() {
00409         # Check for validation
00410         foreach ( $this->mFlatFields as $fieldname => $field ) {
00411             if ( !empty( $field->mParams['nodata'] ) ) {
00412                 continue;
00413             }
00414             if ( $field->validate(
00415                     $this->mFieldData[$fieldname],
00416                     $this->mFieldData )
00417                 !== true
00418             ) {
00419                 return isset( $this->mValidationErrorMessage )
00420                     ? $this->mValidationErrorMessage
00421                     : array( 'htmlform-invalid-input' );
00422             }
00423         }
00424 
00425         $callback = $this->mSubmitCallback;
00426         if ( !is_callable( $callback ) ) {
00427             throw new MWException( 'HTMLForm: no submit callback provided. Use setSubmitCallback() to set one.' );
00428         }
00429 
00430         $data = $this->filterDataForSubmit( $this->mFieldData );
00431 
00432         $res = call_user_func( $callback, $data, $this );
00433 
00434         return $res;
00435     }
00436 
00446     function setSubmitCallback( $cb ) {
00447         $this->mSubmitCallback = $cb;
00448         return $this;
00449     }
00450 
00457     function setValidationErrorMessage( $msg ) {
00458         $this->mValidationErrorMessage = $msg;
00459         return $this;
00460     }
00461 
00467     function setIntro( $msg ) {
00468         $this->setPreText( $msg );
00469         return $this;
00470     }
00471 
00478     function setPreText( $msg ) {
00479         $this->mPre = $msg;
00480         return $this;
00481     }
00482 
00488     function addPreText( $msg ) {
00489         $this->mPre .= $msg;
00490         return $this;
00491     }
00492 
00499     function addHeaderText( $msg, $section = null ) {
00500         if ( is_null( $section ) ) {
00501             $this->mHeader .= $msg;
00502         } else {
00503             if ( !isset( $this->mSectionHeaders[$section] ) ) {
00504                 $this->mSectionHeaders[$section] = '';
00505             }
00506             $this->mSectionHeaders[$section] .= $msg;
00507         }
00508         return $this;
00509     }
00510 
00518     function setHeaderText( $msg, $section = null ) {
00519         if ( is_null( $section ) ) {
00520             $this->mHeader = $msg;
00521         } else {
00522             $this->mSectionHeaders[$section] = $msg;
00523         }
00524         return $this;
00525     }
00526 
00533     function addFooterText( $msg, $section = null ) {
00534         if ( is_null( $section ) ) {
00535             $this->mFooter .= $msg;
00536         } else {
00537             if ( !isset( $this->mSectionFooters[$section] ) ) {
00538                 $this->mSectionFooters[$section] = '';
00539             }
00540             $this->mSectionFooters[$section] .= $msg;
00541         }
00542         return $this;
00543     }
00544 
00552     function setFooterText( $msg, $section = null ) {
00553         if ( is_null( $section ) ) {
00554             $this->mFooter = $msg;
00555         } else {
00556             $this->mSectionFooters[$section] = $msg;
00557         }
00558         return $this;
00559     }
00560 
00566     function addPostText( $msg ) {
00567         $this->mPost .= $msg;
00568         return $this;
00569     }
00570 
00576     function setPostText( $msg ) {
00577         $this->mPost = $msg;
00578         return $this;
00579     }
00580 
00588     public function addHiddenField( $name, $value, $attribs = array() ) {
00589         $attribs += array( 'name' => $name );
00590         $this->mHiddenFields[] = array( $value, $attribs );
00591         return $this;
00592     }
00593 
00602     public function addHiddenFields( array $fields ) {
00603         foreach ( $fields as $name => $value ) {
00604             $this->mHiddenFields[] = array( $value, array( 'name' => $name ) );
00605         }
00606         return $this;
00607     }
00608 
00617     public function addButton( $name, $value, $id = null, $attribs = null ) {
00618         $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
00619         return $this;
00620     }
00621 
00633     function displayForm( $submitResult ) {
00634         $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
00635     }
00636 
00642     function getHTML( $submitResult ) {
00643         # For good measure (it is the default)
00644         $this->getOutput()->preventClickjacking();
00645         $this->getOutput()->addModules( 'mediawiki.htmlform' );
00646         if ( $this->isVForm() ) {
00647             $this->getOutput()->addModuleStyles( 'mediawiki.ui' );
00648             // TODO should vertical form set setWrapperLegend( false )
00649             // to hide ugly fieldsets?
00650         }
00651 
00652         $html = ''
00653             . $this->getErrors( $submitResult )
00654             . $this->mHeader
00655             . $this->getBody()
00656             . $this->getHiddenFields()
00657             . $this->getButtons()
00658             . $this->mFooter
00659         ;
00660 
00661         $html = $this->wrapForm( $html );
00662 
00663         return '' . $this->mPre . $html . $this->mPost;
00664     }
00665 
00671     function wrapForm( $html ) {
00672 
00673         # Include a <fieldset> wrapper for style, if requested.
00674         if ( $this->mWrapperLegend !== false ) {
00675             $html = Xml::fieldset( $this->mWrapperLegend, $html );
00676         }
00677         # Use multipart/form-data
00678         $encType = $this->mUseMultipart
00679             ? 'multipart/form-data'
00680             : 'application/x-www-form-urlencoded';
00681         # Attributes
00682         $attribs = array(
00683             'action' => $this->getAction(),
00684             'method' => $this->getMethod(),
00685             'class' => array( 'visualClear' ),
00686             'enctype' => $encType,
00687         );
00688         if ( !empty( $this->mId ) ) {
00689             $attribs['id'] = $this->mId;
00690         }
00691 
00692         if ( $this->isVForm() ) {
00693             array_push( $attribs['class'], 'mw-ui-vform', 'mw-ui-container' );
00694         }
00695         return Html::rawElement( 'form', $attribs, $html );
00696     }
00697 
00702     function getHiddenFields() {
00703         global $wgArticlePath;
00704 
00705         $html = '';
00706         if ( $this->getMethod() == 'post' ) {
00707             $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
00708             $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00709         }
00710 
00711         if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
00712             $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00713         }
00714 
00715         foreach ( $this->mHiddenFields as $data ) {
00716             list( $value, $attribs ) = $data;
00717             $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
00718         }
00719 
00720         return $html;
00721     }
00722 
00727     function getButtons() {
00728         $html = '<span class="mw-htmlform-submit-buttons">';
00729 
00730         if ( $this->mShowSubmit ) {
00731             $attribs = array();
00732 
00733             if ( isset( $this->mSubmitID ) ) {
00734                 $attribs['id'] = $this->mSubmitID;
00735             }
00736 
00737             if ( isset( $this->mSubmitName ) ) {
00738                 $attribs['name'] = $this->mSubmitName;
00739             }
00740 
00741             if ( isset( $this->mSubmitTooltip ) ) {
00742                 $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
00743             }
00744 
00745             $attribs['class'] = array( 'mw-htmlform-submit' );
00746 
00747             if ( $this->isVForm() ) {
00748                 // mw-ui-block is necessary because the buttons aren't necessarily in an 
00749                 // immediate child div of the vform.
00750                 array_push( $attribs['class'], 'mw-ui-button', 'mw-ui-big', 'mw-ui-primary', 'mw-ui-block' );
00751             }
00752 
00753             $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
00754 
00755             // Buttons are top-level form elements in table and div layouts,
00756             // but vform wants all elements inside divs to get spaced-out block
00757             // styling.
00758             if ( $this->isVForm() ) {
00759                 $html = Html::rawElement( 'div', null, "\n$html\n" );
00760             }
00761         }
00762 
00763         if ( $this->mShowReset ) {
00764             $html .= Html::element(
00765                 'input',
00766                 array(
00767                     'type' => 'reset',
00768                     'value' => $this->msg( 'htmlform-reset' )->text()
00769                 )
00770             ) . "\n";
00771         }
00772 
00773         foreach ( $this->mButtons as $button ) {
00774             $attrs = array(
00775                 'type' => 'submit',
00776                 'name' => $button['name'],
00777                 'value' => $button['value']
00778             );
00779 
00780             if ( $button['attribs'] ) {
00781                 $attrs += $button['attribs'];
00782             }
00783 
00784             if ( isset( $button['id'] ) ) {
00785                 $attrs['id'] = $button['id'];
00786             }
00787 
00788             $html .= Html::element( 'input', $attrs );
00789         }
00790 
00791         $html .= '</span>';
00792 
00793         return $html;
00794     }
00795 
00800     function getBody() {
00801         return $this->displaySection( $this->mFieldTree, $this->mTableId );
00802     }
00803 
00809     function getErrors( $errors ) {
00810         if ( $errors instanceof Status ) {
00811             if ( $errors->isOK() ) {
00812                 $errorstr = '';
00813             } else {
00814                 $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
00815             }
00816         } elseif ( is_array( $errors ) ) {
00817             $errorstr = $this->formatErrors( $errors );
00818         } else {
00819             $errorstr = $errors;
00820         }
00821 
00822         return $errorstr
00823             ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
00824             : '';
00825     }
00826 
00832     public static function formatErrors( $errors ) {
00833         $errorstr = '';
00834 
00835         foreach ( $errors as $error ) {
00836             if ( is_array( $error ) ) {
00837                 $msg = array_shift( $error );
00838             } else {
00839                 $msg = $error;
00840                 $error = array();
00841             }
00842 
00843             $errorstr .= Html::rawElement(
00844                 'li',
00845                 array(),
00846                 wfMessage( $msg, $error )->parse()
00847             );
00848         }
00849 
00850         $errorstr = Html::rawElement( 'ul', array(), $errorstr );
00851 
00852         return $errorstr;
00853     }
00854 
00860     function setSubmitText( $t ) {
00861         $this->mSubmitText = $t;
00862         return $this;
00863     }
00864 
00871     public function setSubmitTextMsg( $msg ) {
00872         $this->setSubmitText( $this->msg( $msg )->text() );
00873         return $this;
00874     }
00875 
00880     function getSubmitText() {
00881         return $this->mSubmitText
00882             ? $this->mSubmitText
00883             : $this->msg( 'htmlform-submit' )->text();
00884     }
00885 
00890     public function setSubmitName( $name ) {
00891         $this->mSubmitName = $name;
00892         return $this;
00893     }
00894 
00899     public function setSubmitTooltip( $name ) {
00900         $this->mSubmitTooltip = $name;
00901         return $this;
00902     }
00903 
00910     function setSubmitID( $t ) {
00911         $this->mSubmitID = $t;
00912         return $this;
00913     }
00914 
00925     function suppressDefaultSubmit( $suppressSubmit = true ) {
00926         $this->mShowSubmit = !$suppressSubmit;
00927         return $this;
00928     }
00929 
00937     public function setTableId( $id ) {
00938         $this->mTableId = $id;
00939         return $this;
00940     }
00941 
00946     public function setId( $id ) {
00947         $this->mId = $id;
00948         return $this;
00949     }
00950 
00959     public function setWrapperLegend( $legend ) {
00960         $this->mWrapperLegend = $legend;
00961         return $this;
00962     }
00963 
00971     public function setWrapperLegendMsg( $msg ) {
00972         $this->setWrapperLegend( $this->msg( $msg )->text() );
00973         return $this;
00974     }
00975 
00983     function setMessagePrefix( $p ) {
00984         $this->mMessagePrefix = $p;
00985         return $this;
00986     }
00987 
00993     function setTitle( $t ) {
00994         $this->mTitle = $t;
00995         return $this;
00996     }
00997 
01002     function getTitle() {
01003         return $this->mTitle === false
01004             ? $this->getContext()->getTitle()
01005             : $this->mTitle;
01006     }
01007 
01013     public function setMethod( $method = 'post' ) {
01014         $this->mMethod = $method;
01015         return $this;
01016     }
01017 
01018     public function getMethod() {
01019         return $this->mMethod;
01020     }
01021 
01030     public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '', &$hasUserVisibleFields = false ) {
01031         $displayFormat = $this->getDisplayFormat();
01032 
01033         $html = '';
01034         $subsectionHtml = '';
01035         $hasLabel = false;
01036 
01037         switch( $displayFormat ) {
01038             case 'table':
01039                 $getFieldHtmlMethod = 'getTableRow';
01040                 break;
01041             case 'vform':
01042                 // Close enough to a div.
01043                 $getFieldHtmlMethod = 'getDiv';
01044                 break;
01045             default:
01046                 $getFieldHtmlMethod = 'get' . ucfirst( $displayFormat );
01047         }
01048 
01049         foreach ( $fields as $key => $value ) {
01050             if ( $value instanceof HTMLFormField ) {
01051                 $v = empty( $value->mParams['nodata'] )
01052                     ? $this->mFieldData[$key]
01053                     : $value->getDefault();
01054                 $html .= $value->$getFieldHtmlMethod( $v );
01055 
01056                 $labelValue = trim( $value->getLabel() );
01057                 if ( $labelValue != '&#160;' && $labelValue !== '' ) {
01058                     $hasLabel = true;
01059                 }
01060 
01061                 if ( get_class( $value ) !== 'HTMLHiddenField' &&
01062                         get_class( $value ) !== 'HTMLApiField' ) {
01063                     $hasUserVisibleFields = true;
01064                 }
01065             } elseif ( is_array( $value ) ) {
01066                 $subsectionHasVisibleFields = false;
01067                 $section = $this->displaySection( $value, "mw-htmlform-$key", "$fieldsetIDPrefix$key-", $subsectionHasVisibleFields );
01068                 $legend = null;
01069 
01070                 if ( $subsectionHasVisibleFields === true ) {
01071                     // Display the section with various niceties.
01072                     $hasUserVisibleFields = true;
01073 
01074                     $legend = $this->getLegend( $key );
01075 
01076                     if ( isset( $this->mSectionHeaders[$key] ) ) {
01077                         $section = $this->mSectionHeaders[$key] . $section;
01078                     }
01079                     if ( isset( $this->mSectionFooters[$key] ) ) {
01080                         $section .= $this->mSectionFooters[$key];
01081                     }
01082 
01083                     $attributes = array();
01084                     if ( $fieldsetIDPrefix ) {
01085                         $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
01086                     }
01087                     $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
01088                 } else {
01089                     // Just return the inputs, nothing fancy.
01090                     $subsectionHtml .= $section;
01091                 }
01092             }
01093         }
01094 
01095         if ( $displayFormat !== 'raw' ) {
01096             $classes = array();
01097 
01098             if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
01099                 $classes[] = 'mw-htmlform-nolabel';
01100             }
01101 
01102             $attribs = array(
01103                 'class' => implode( ' ', $classes ),
01104             );
01105 
01106             if ( $sectionName ) {
01107                 $attribs['id'] = Sanitizer::escapeId( $sectionName );
01108             }
01109 
01110             if ( $displayFormat === 'table' ) {
01111                 $html = Html::rawElement( 'table', $attribs,
01112                     Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
01113             } elseif ( $displayFormat === 'div' || $displayFormat === 'vform' ) {
01114                 $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
01115             }
01116         }
01117 
01118         if ( $this->mSubSectionBeforeFields ) {
01119             return $subsectionHtml . "\n" . $html;
01120         } else {
01121             return $html . "\n" . $subsectionHtml;
01122         }
01123     }
01124 
01128     function loadData() {
01129         $fieldData = array();
01130 
01131         foreach ( $this->mFlatFields as $fieldname => $field ) {
01132             if ( !empty( $field->mParams['nodata'] ) ) {
01133                 continue;
01134             } elseif ( !empty( $field->mParams['disabled'] ) ) {
01135                 $fieldData[$fieldname] = $field->getDefault();
01136             } else {
01137                 $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
01138             }
01139         }
01140 
01141         # Filter data.
01142         foreach ( $fieldData as $name => &$value ) {
01143             $field = $this->mFlatFields[$name];
01144             $value = $field->filter( $value, $this->mFlatFields );
01145         }
01146 
01147         $this->mFieldData = $fieldData;
01148     }
01149 
01156     function suppressReset( $suppressReset = true ) {
01157         $this->mShowReset = !$suppressReset;
01158         return $this;
01159     }
01160 
01168     function filterDataForSubmit( $data ) {
01169         return $data;
01170     }
01171 
01178     public function getLegend( $key ) {
01179         return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
01180     }
01181 
01191     public function setAction( $action ) {
01192         $this->mAction = $action;
01193         return $this;
01194     }
01195 
01203     public function getAction() {
01204         global $wgScript, $wgArticlePath;
01205 
01206         // If an action is alredy provided, return it
01207         if ( $this->mAction !== false ) {
01208             return $this->mAction;
01209         }
01210 
01211         // Check whether we are in GET mode and $wgArticlePath contains a "?"
01212         // meaning that getLocalURL() would return something like "index.php?title=...".
01213         // As browser remove the query string before submitting GET forms,
01214         // it means that the title would be lost. In such case use $wgScript instead
01215         // and put title in an hidden field (see getHiddenFields()).
01216         if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() === 'get' ) {
01217             return $wgScript;
01218         }
01219 
01220         return $this->getTitle()->getLocalURL();
01221     }
01222 }
01223 
01228 abstract class HTMLFormField {
01229 
01230     protected $mValidationCallback;
01231     protected $mFilterCallback;
01232     protected $mName;
01233     public $mParams;
01234     protected $mLabel;  # String label.  Set on construction
01235     protected $mID;
01236     protected $mClass = '';
01237     protected $mDefault;
01238 
01243     protected $mShowEmptyLabels = true;
01244 
01248     public $mParent;
01249 
01258     abstract function getInputHTML( $value );
01259 
01270     function msg() {
01271         $args = func_get_args();
01272 
01273         if ( $this->mParent ) {
01274             $callback = array( $this->mParent, 'msg' );
01275         } else {
01276             $callback = 'wfMessage';
01277         }
01278 
01279         return call_user_func_array( $callback, $args );
01280     }
01281 
01290     function validate( $value, $alldata ) {
01291         if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) {
01292             return $this->msg( 'htmlform-required' )->parse();
01293         }
01294 
01295         if ( isset( $this->mValidationCallback ) ) {
01296             return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
01297         }
01298 
01299         return true;
01300     }
01301 
01302     function filter( $value, $alldata ) {
01303         if ( isset( $this->mFilterCallback ) ) {
01304             $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
01305         }
01306 
01307         return $value;
01308     }
01309 
01316     protected function needsLabel() {
01317         return true;
01318     }
01319 
01328     public function setShowEmptyLabel( $show ) {
01329         $this->mShowEmptyLabels = $show;
01330     }
01331 
01338     function loadDataFromRequest( $request ) {
01339         if ( $request->getCheck( $this->mName ) ) {
01340             return $request->getText( $this->mName );
01341         } else {
01342             return $this->getDefault();
01343         }
01344     }
01345 
01353     function __construct( $params ) {
01354         $this->mParams = $params;
01355 
01356         # Generate the label from a message, if possible
01357         if ( isset( $params['label-message'] ) ) {
01358             $msgInfo = $params['label-message'];
01359 
01360             if ( is_array( $msgInfo ) ) {
01361                 $msg = array_shift( $msgInfo );
01362             } else {
01363                 $msg = $msgInfo;
01364                 $msgInfo = array();
01365             }
01366 
01367             $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
01368         } elseif ( isset( $params['label'] ) ) {
01369             if ( $params['label'] === '&#160;' ) {
01370                 // Apparently some things set &nbsp directly and in an odd format
01371                 $this->mLabel = '&#160;';
01372             } else {
01373                 $this->mLabel = htmlspecialchars( $params['label'] );
01374             }
01375         } elseif ( isset( $params['label-raw'] ) ) {
01376             $this->mLabel = $params['label-raw'];
01377         }
01378 
01379         $this->mName = "wp{$params['fieldname']}";
01380         if ( isset( $params['name'] ) ) {
01381             $this->mName = $params['name'];
01382         }
01383 
01384         $validName = Sanitizer::escapeId( $this->mName );
01385         if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
01386             throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
01387         }
01388 
01389         $this->mID = "mw-input-{$this->mName}";
01390 
01391         if ( isset( $params['default'] ) ) {
01392             $this->mDefault = $params['default'];
01393         }
01394 
01395         if ( isset( $params['id'] ) ) {
01396             $id = $params['id'];
01397             $validId = Sanitizer::escapeId( $id );
01398 
01399             if ( $id != $validId ) {
01400                 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
01401             }
01402 
01403             $this->mID = $id;
01404         }
01405 
01406         if ( isset( $params['cssclass'] ) ) {
01407             $this->mClass = $params['cssclass'];
01408         }
01409 
01410         if ( isset( $params['validation-callback'] ) ) {
01411             $this->mValidationCallback = $params['validation-callback'];
01412         }
01413 
01414         if ( isset( $params['filter-callback'] ) ) {
01415             $this->mFilterCallback = $params['filter-callback'];
01416         }
01417 
01418         if ( isset( $params['flatlist'] ) ) {
01419             $this->mClass .= ' mw-htmlform-flatlist';
01420         }
01421 
01422         if ( isset( $params['hidelabel'] ) ) {
01423             $this->mShowEmptyLabels = false;
01424         }
01425     }
01426 
01433     function getTableRow( $value ) {
01434         list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01435         $inputHtml = $this->getInputHTML( $value );
01436         $fieldType = get_class( $this );
01437         $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
01438         $cellAttributes = array();
01439 
01440         if ( !empty( $this->mParams['vertical-label'] ) ) {
01441             $cellAttributes['colspan'] = 2;
01442             $verticalLabel = true;
01443         } else {
01444             $verticalLabel = false;
01445         }
01446 
01447         $label = $this->getLabelHtml( $cellAttributes );
01448 
01449         $field = Html::rawElement(
01450             'td',
01451             array( 'class' => 'mw-input' ) + $cellAttributes,
01452             $inputHtml . "\n$errors"
01453         );
01454 
01455         if ( $verticalLabel ) {
01456             $html = Html::rawElement( 'tr',
01457                 array( 'class' => 'mw-htmlform-vertical-label' ), $label );
01458             $html .= Html::rawElement( 'tr',
01459                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01460                 $field );
01461         } else {
01462             $html = Html::rawElement( 'tr',
01463                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01464                 $label . $field );
01465         }
01466 
01467         return $html . $helptext;
01468     }
01469 
01477     public function getDiv( $value ) {
01478         list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01479         $inputHtml = $this->getInputHTML( $value );
01480         $fieldType = get_class( $this );
01481         $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
01482         $cellAttributes = array();
01483         $label = $this->getLabelHtml( $cellAttributes );
01484 
01485         $outerDivClass = array(
01486             'mw-input',
01487             'mw-htmlform-nolabel' => ( $label === '' )
01488         );
01489 
01490         $field = Html::rawElement(
01491             'div',
01492             array( 'class' => $outerDivClass ) + $cellAttributes,
01493             $inputHtml . "\n$errors"
01494         );
01495         $divCssClasses = array( "mw-htmlform-field-$fieldType", $this->mClass, $errorClass );
01496         if ( $this->mParent->isVForm() ) {
01497             $divCssClasses[] = 'mw-ui-vform-div';
01498         }
01499         $html = Html::rawElement( 'div',
01500             array( 'class' => $divCssClasses ),
01501             $label . $field );
01502         $html .= $helptext;
01503         return $html;
01504     }
01505 
01513     public function getRaw( $value ) {
01514         list( $errors, ) = $this->getErrorsAndErrorClass( $value );
01515         $inputHtml = $this->getInputHTML( $value );
01516         $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
01517         $cellAttributes = array();
01518         $label = $this->getLabelHtml( $cellAttributes );
01519 
01520         $html = "\n$errors";
01521         $html .= $label;
01522         $html .= $inputHtml;
01523         $html .= $helptext;
01524         return $html;
01525     }
01526 
01533     public function getHelpTextHtmlTable( $helptext ) {
01534         if ( is_null( $helptext ) ) {
01535             return '';
01536         }
01537 
01538         $row = Html::rawElement(
01539             'td',
01540             array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
01541             $helptext
01542         );
01543         $row = Html::rawElement( 'tr', array(), $row );
01544         return $row;
01545     }
01546 
01553     public function getHelpTextHtmlDiv( $helptext ) {
01554         if ( is_null( $helptext ) ) {
01555             return '';
01556         }
01557 
01558         $div = Html::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
01559         return $div;
01560     }
01561 
01568     public function getHelpTextHtmlRaw( $helptext ) {
01569         return $this->getHelpTextHtmlDiv( $helptext );
01570     }
01571 
01577     public function getHelpText() {
01578         $helptext = null;
01579 
01580         if ( isset( $this->mParams['help-message'] ) ) {
01581             $this->mParams['help-messages'] = array( $this->mParams['help-message'] );
01582         }
01583 
01584         if ( isset( $this->mParams['help-messages'] ) ) {
01585             foreach ( $this->mParams['help-messages'] as $name ) {
01586                 $helpMessage = (array)$name;
01587                 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
01588 
01589                 if ( $msg->exists() ) {
01590                     if ( is_null( $helptext ) ) {
01591                         $helptext = '';
01592                     } else {
01593                         $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
01594                     }
01595                     $helptext .= $msg->parse(); // Append message
01596                 }
01597             }
01598         }
01599         elseif ( isset( $this->mParams['help'] ) ) {
01600             $helptext = $this->mParams['help'];
01601         }
01602         return $helptext;
01603     }
01604 
01611     public function getErrorsAndErrorClass( $value ) {
01612         $errors = $this->validate( $value, $this->mParent->mFieldData );
01613 
01614         if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
01615             $errors = '';
01616             $errorClass = '';
01617         } else {
01618             $errors = self::formatErrors( $errors );
01619             $errorClass = 'mw-htmlform-invalid-input';
01620         }
01621         return array( $errors, $errorClass );
01622     }
01623 
01624     function getLabel() {
01625         return is_null( $this->mLabel ) ? '' : $this->mLabel;
01626     }
01627 
01628     function getLabelHtml( $cellAttributes = array() ) {
01629         # Don't output a for= attribute for labels with no associated input.
01630         # Kind of hacky here, possibly we don't want these to be <label>s at all.
01631         $for = array();
01632 
01633         if ( $this->needsLabel() ) {
01634             $for['for'] = $this->mID;
01635         }
01636 
01637         $labelValue = trim( $this->getLabel() );
01638         $hasLabel = false;
01639         if ( $labelValue !== '&#160;' && $labelValue !== '' ) {
01640             $hasLabel = true;
01641         }
01642 
01643         $displayFormat = $this->mParent->getDisplayFormat();
01644         $html = '';
01645 
01646         if ( $displayFormat === 'table' ) {
01647             $html = Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
01648                 Html::rawElement( 'label', $for, $labelValue )
01649             );
01650         } elseif ( $hasLabel || $this->mShowEmptyLabels ) {
01651             if ( $displayFormat === 'div' ) {
01652                 $html = Html::rawElement(
01653                     'div',
01654                     array( 'class' => 'mw-label' ) + $cellAttributes,
01655                     Html::rawElement( 'label', $for, $labelValue )
01656                 );
01657             } else {
01658                 $html = Html::rawElement( 'label', $for, $labelValue );
01659             }
01660         }
01661 
01662         return $html;
01663     }
01664 
01665     function getDefault() {
01666         if ( isset( $this->mDefault ) ) {
01667             return $this->mDefault;
01668         } else {
01669             return null;
01670         }
01671     }
01672 
01678     public function getTooltipAndAccessKey() {
01679         if ( empty( $this->mParams['tooltip'] ) ) {
01680             return array();
01681         }
01682         return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
01683     }
01684 
01692     public static function flattenOptions( $options ) {
01693         $flatOpts = array();
01694 
01695         foreach ( $options as $value ) {
01696             if ( is_array( $value ) ) {
01697                 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
01698             } else {
01699                 $flatOpts[] = $value;
01700             }
01701         }
01702 
01703         return $flatOpts;
01704     }
01705 
01712     protected static function formatErrors( $errors ) {
01713         if ( is_array( $errors ) && count( $errors ) === 1 ) {
01714             $errors = array_shift( $errors );
01715         }
01716 
01717         if ( is_array( $errors ) ) {
01718             $lines = array();
01719             foreach ( $errors as $error ) {
01720                 if ( $error instanceof Message ) {
01721                     $lines[] = Html::rawElement( 'li', array(), $error->parse() );
01722                 } else {
01723                     $lines[] = Html::rawElement( 'li', array(), $error );
01724                 }
01725             }
01726             return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
01727         } else {
01728             if ( $errors instanceof Message ) {
01729                 $errors = $errors->parse();
01730             }
01731             return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
01732         }
01733     }
01734 }
01735 
01736 class HTMLTextField extends HTMLFormField {
01737     function getSize() {
01738         return isset( $this->mParams['size'] )
01739             ? $this->mParams['size']
01740             : 45;
01741     }
01742 
01743     function getInputHTML( $value ) {
01744         $attribs = array(
01745             'id' => $this->mID,
01746             'name' => $this->mName,
01747             'size' => $this->getSize(),
01748             'value' => $value,
01749         ) + $this->getTooltipAndAccessKey();
01750 
01751         if ( $this->mClass !== '' ) {
01752             $attribs['class'] = $this->mClass;
01753         }
01754 
01755         if ( !empty( $this->mParams['disabled'] ) ) {
01756             $attribs['disabled'] = 'disabled';
01757         }
01758 
01759         # TODO: Enforce pattern, step, required, readonly on the server side as
01760         # well
01761         $allowedParams = array( 'min', 'max', 'pattern', 'title', 'step',
01762             'placeholder', 'list', 'maxlength' );
01763         foreach ( $allowedParams as $param ) {
01764             if ( isset( $this->mParams[$param] ) ) {
01765                 $attribs[$param] = $this->mParams[$param];
01766             }
01767         }
01768 
01769         foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
01770             if ( isset( $this->mParams[$param] ) ) {
01771                 $attribs[$param] = '';
01772             }
01773         }
01774 
01775         # Implement tiny differences between some field variants
01776         # here, rather than creating a new class for each one which
01777         # is essentially just a clone of this one.
01778         if ( isset( $this->mParams['type'] ) ) {
01779             switch ( $this->mParams['type'] ) {
01780                 case 'email':
01781                     $attribs['type'] = 'email';
01782                     break;
01783                 case 'int':
01784                     $attribs['type'] = 'number';
01785                     break;
01786                 case 'float':
01787                     $attribs['type'] = 'number';
01788                     $attribs['step'] = 'any';
01789                     break;
01790                 # Pass through
01791                 case 'password':
01792                 case 'file':
01793                     $attribs['type'] = $this->mParams['type'];
01794                     break;
01795             }
01796         }
01797 
01798         return Html::element( 'input', $attribs );
01799     }
01800 }
01801 class HTMLTextAreaField extends HTMLFormField {
01802     const DEFAULT_COLS = 80;
01803     const DEFAULT_ROWS = 25;
01804 
01805     function getCols() {
01806         return isset( $this->mParams['cols'] )
01807             ? $this->mParams['cols']
01808             : static::DEFAULT_COLS;
01809     }
01810 
01811     function getRows() {
01812         return isset( $this->mParams['rows'] )
01813             ? $this->mParams['rows']
01814             : static::DEFAULT_ROWS;
01815     }
01816 
01817     function getInputHTML( $value ) {
01818         $attribs = array(
01819             'id' => $this->mID,
01820             'name' => $this->mName,
01821             'cols' => $this->getCols(),
01822             'rows' => $this->getRows(),
01823         ) + $this->getTooltipAndAccessKey();
01824 
01825         if ( $this->mClass !== '' ) {
01826             $attribs['class'] = $this->mClass;
01827         }
01828 
01829         if ( !empty( $this->mParams['disabled'] ) ) {
01830             $attribs['disabled'] = 'disabled';
01831         }
01832 
01833         if ( !empty( $this->mParams['readonly'] ) ) {
01834             $attribs['readonly'] = 'readonly';
01835         }
01836 
01837         if ( isset( $this->mParams['placeholder'] ) ) {
01838             $attribs['placeholder'] = $this->mParams['placeholder'];
01839         }
01840 
01841         foreach ( array( 'required', 'autofocus' ) as $param ) {
01842             if ( isset( $this->mParams[$param] ) ) {
01843                 $attribs[$param] = '';
01844             }
01845         }
01846 
01847         return Html::element( 'textarea', $attribs, $value );
01848     }
01849 }
01850 
01854 class HTMLFloatField extends HTMLTextField {
01855     function getSize() {
01856         return isset( $this->mParams['size'] )
01857             ? $this->mParams['size']
01858             : 20;
01859     }
01860 
01861     function validate( $value, $alldata ) {
01862         $p = parent::validate( $value, $alldata );
01863 
01864         if ( $p !== true ) {
01865             return $p;
01866         }
01867 
01868         $value = trim( $value );
01869 
01870         # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
01871         # with the addition that a leading '+' sign is ok.
01872         if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
01873             return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
01874         }
01875 
01876         # The "int" part of these message names is rather confusing.
01877         # They make equal sense for all numbers.
01878         if ( isset( $this->mParams['min'] ) ) {
01879             $min = $this->mParams['min'];
01880 
01881             if ( $min > $value ) {
01882                 return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
01883             }
01884         }
01885 
01886         if ( isset( $this->mParams['max'] ) ) {
01887             $max = $this->mParams['max'];
01888 
01889             if ( $max < $value ) {
01890                 return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
01891             }
01892         }
01893 
01894         return true;
01895     }
01896 }
01897 
01901 class HTMLIntField extends HTMLFloatField {
01902     function validate( $value, $alldata ) {
01903         $p = parent::validate( $value, $alldata );
01904 
01905         if ( $p !== true ) {
01906             return $p;
01907         }
01908 
01909         # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
01910         # with the addition that a leading '+' sign is ok. Note that leading zeros
01911         # are fine, and will be left in the input, which is useful for things like
01912         # phone numbers when you know that they are integers (the HTML5 type=tel
01913         # input does not require its value to be numeric).  If you want a tidier
01914         # value to, eg, save in the DB, clean it up with intval().
01915         if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
01916         ) {
01917             return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
01918         }
01919 
01920         return true;
01921     }
01922 }
01923 
01927 class HTMLCheckField extends HTMLFormField {
01928     function getInputHTML( $value ) {
01929         if ( !empty( $this->mParams['invert'] ) ) {
01930             $value = !$value;
01931         }
01932 
01933         $attr = $this->getTooltipAndAccessKey();
01934         $attr['id'] = $this->mID;
01935 
01936         if ( !empty( $this->mParams['disabled'] ) ) {
01937             $attr['disabled'] = 'disabled';
01938         }
01939 
01940         if ( $this->mClass !== '' ) {
01941             $attr['class'] = $this->mClass;
01942         }
01943 
01944         if ( $this->mParent->isVForm() ) {
01945             // Nest checkbox inside label.
01946             return Html::rawElement(
01947                     'label',
01948                     array(
01949                         'class' => 'mw-ui-checkbox-label'
01950                     ),
01951                     Xml::check(
01952                         $this->mName,
01953                         $value,
01954                         $attr
01955                     ) .
01956                     // Html:rawElement doesn't escape contents.
01957                     htmlspecialchars( $this->mLabel )
01958                 );
01959         } else {
01960             return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
01961                 Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
01962         }
01963     }
01964 
01970     function getLabel() {
01971         return '&#160;';
01972     }
01973 
01977     protected function needsLabel() {
01978         return false;
01979     }
01980 
01985     function loadDataFromRequest( $request ) {
01986         $invert = false;
01987         if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
01988             $invert = true;
01989         }
01990 
01991         // GetCheck won't work like we want for checks.
01992         // Fetch the value in either one of the two following case:
01993         // - we have a valid token (form got posted or GET forged by the user)
01994         // - checkbox name has a value (false or true), ie is not null
01995         if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
01996             // XOR has the following truth table, which is what we want
01997             // INVERT VALUE | OUTPUT
01998             // true   true  | false
01999             // false  true  | true
02000             // false  false | false
02001             // true   false | true
02002             return $request->getBool( $this->mName ) xor $invert;
02003         } else {
02004             return $this->getDefault();
02005         }
02006     }
02007 }
02008 
02030 class HTMLCheckMatrix extends HTMLFormField implements HTMLNestedFilterable {
02031 
02032     static private $requiredParams = array(
02033         // Required by underlying HTMLFormField
02034         'fieldname',
02035         // Required by HTMLCheckMatrix
02036         'rows', 'columns'
02037     );
02038 
02039     public function __construct( $params ) {
02040         $missing = array_diff( self::$requiredParams, array_keys( $params ) );
02041         if ( $missing ) {
02042             throw new HTMLFormFieldRequiredOptionsException( $this, $missing );
02043         }
02044         parent::__construct( $params );
02045     }
02046 
02047     function validate( $value, $alldata ) {
02048         $rows = $this->mParams['rows'];
02049         $columns = $this->mParams['columns'];
02050 
02051         // Make sure user-defined validation callback is run
02052         $p = parent::validate( $value, $alldata );
02053         if ( $p !== true ) {
02054             return $p;
02055         }
02056 
02057         // Make sure submitted value is an array
02058         if ( !is_array( $value ) ) {
02059             return false;
02060         }
02061 
02062         // If all options are valid, array_intersect of the valid options
02063         // and the provided options will return the provided options.
02064         $validOptions = array();
02065         foreach ( $rows as $rowTag ) {
02066             foreach ( $columns as $columnTag ) {
02067                 $validOptions[] = $columnTag . '-' . $rowTag;
02068             }
02069         }
02070         $validValues = array_intersect( $value, $validOptions );
02071         if ( count( $validValues ) == count( $value ) ) {
02072             return true;
02073         } else {
02074             return $this->msg( 'htmlform-select-badoption' )->parse();
02075         }
02076     }
02077 
02086     function getInputHTML( $value ) {
02087         $html = '';
02088         $tableContents = '';
02089         $attribs = array();
02090         $rows = $this->mParams['rows'];
02091         $columns = $this->mParams['columns'];
02092 
02093         // If the disabled param is set, disable all the options
02094         if ( !empty( $this->mParams['disabled'] ) ) {
02095             $attribs['disabled'] = 'disabled';
02096         }
02097 
02098         // Build the column headers
02099         $headerContents = Html::rawElement( 'td', array(), '&#160;' );
02100         foreach ( $columns as $columnLabel => $columnTag ) {
02101             $headerContents .= Html::rawElement( 'td', array(), $columnLabel );
02102         }
02103         $tableContents .= Html::rawElement( 'tr', array(), "\n$headerContents\n" );
02104 
02105         $tooltipClass = 'mw-icon-question';
02106         if ( isset( $this->mParams['tooltip-class'] ) ) {
02107             $tooltipClass = $this->mParams['tooltip-class'];
02108         }
02109 
02110         // Build the options matrix
02111         foreach ( $rows as $rowLabel => $rowTag ) {
02112             // Append tooltip if configured
02113             if ( isset( $this->mParams['tooltips'][$rowLabel] ) ) {
02114                 $tooltipAttribs = array(
02115                     'class' => "mw-htmlform-tooltip $tooltipClass",
02116                     'title' =>  $this->mParams['tooltips'][$rowLabel],
02117                 );
02118                 $rowLabel .= ' ' . Html::element( 'span', $tooltipAttribs, '' );
02119             }
02120             $rowContents = Html::rawElement( 'td', array(), $rowLabel );
02121             foreach ( $columns as $columnTag ) {
02122                 $thisTag = "$columnTag-$rowTag";
02123                 // Construct the checkbox
02124                 $thisAttribs = array(
02125                     'id' => "{$this->mID}-$thisTag",
02126                     'value' => $thisTag,
02127                 );
02128                 $checked = in_array( $thisTag, (array)$value, true );
02129                 if ( $this->isTagForcedOff( $thisTag ) ) {
02130                     $checked = false;
02131                     $thisAttribs['disabled'] = 1;
02132                 } elseif ( $this->isTagForcedOn( $thisTag ) ) {
02133                     $checked = true;
02134                     $thisAttribs['disabled'] = 1;
02135                 }
02136                 $rowContents .= Html::rawElement(
02137                     'td',
02138                     array(),
02139                     Xml::check( "{$this->mName}[]", $checked, $attribs + $thisAttribs )
02140                 );
02141             }
02142             $tableContents .= Html::rawElement( 'tr', array(), "\n$rowContents\n" );
02143         }
02144 
02145         // Put it all in a table
02146         $html .= Html::rawElement( 'table', array( 'class' => 'mw-htmlform-matrix' ),
02147             Html::rawElement( 'tbody', array(), "\n$tableContents\n" ) ) . "\n";
02148 
02149         return $html;
02150     }
02151 
02152     protected function isTagForcedOff( $tag ) {
02153         return isset( $this->mParams['force-options-off'] )
02154             && in_array( $tag, $this->mParams['force-options-off'] );
02155     }
02156 
02157     protected function isTagForcedOn( $tag ) {
02158         return isset( $this->mParams['force-options-on'] )
02159             && in_array( $tag, $this->mParams['force-options-on'] );
02160     }
02161 
02171     function getTableRow( $value ) {
02172         list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
02173         $inputHtml = $this->getInputHTML( $value );
02174         $fieldType = get_class( $this );
02175         $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
02176         $cellAttributes = array( 'colspan' => 2 );
02177 
02178         $label = $this->getLabelHtml( $cellAttributes );
02179 
02180         $field = Html::rawElement(
02181             'td',
02182             array( 'class' => 'mw-input' ) + $cellAttributes,
02183             $inputHtml . "\n$errors"
02184         );
02185 
02186         $html = Html::rawElement( 'tr',
02187             array( 'class' => 'mw-htmlform-vertical-label' ), $label );
02188         $html .= Html::rawElement( 'tr',
02189             array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
02190             $field );
02191 
02192         return $html . $helptext;
02193     }
02194 
02199     function loadDataFromRequest( $request ) {
02200         if ( $this->mParent->getMethod() == 'post' ) {
02201             if ( $request->wasPosted() ) {
02202                 // Checkboxes are not added to the request arrays if they're not checked,
02203                 // so it's perfectly possible for there not to be an entry at all
02204                 return $request->getArray( $this->mName, array() );
02205             } else {
02206                 // That's ok, the user has not yet submitted the form, so show the defaults
02207                 return $this->getDefault();
02208             }
02209         } else {
02210             // This is the impossible case: if we look at $_GET and see no data for our
02211             // field, is it because the user has not yet submitted the form, or that they
02212             // have submitted it with all the options unchecked. We will have to assume the
02213             // latter, which basically means that you can't specify 'positive' defaults
02214             // for GET forms.
02215             return $request->getArray( $this->mName, array() );
02216         }
02217     }
02218 
02219     function getDefault() {
02220         if ( isset( $this->mDefault ) ) {
02221             return $this->mDefault;
02222         } else {
02223             return array();
02224         }
02225     }
02226 
02227     function filterDataForSubmit( $data ) {
02228         $columns = HTMLFormField::flattenOptions( $this->mParams['columns'] );
02229         $rows = HTMLFormField::flattenOptions( $this->mParams['rows'] );
02230         $res = array();
02231         foreach ( $columns as $column ) {
02232             foreach ( $rows as $row ) {
02233                 // Make sure option hasn't been forced
02234                 $thisTag = "$column-$row";
02235                 if ( $this->isTagForcedOff( $thisTag ) ) {
02236                     $res[$thisTag] = false;
02237                 } elseif ( $this->isTagForcedOn( $thisTag ) ) {
02238                     $res[$thisTag] = true;
02239                 } else {
02240                     $res[$thisTag] = in_array( $thisTag, $data );
02241                 }
02242             }
02243         }
02244 
02245         return $res;
02246     }
02247 }
02248 
02252 class HTMLSelectField extends HTMLFormField {
02253     function validate( $value, $alldata ) {
02254         $p = parent::validate( $value, $alldata );
02255 
02256         if ( $p !== true ) {
02257             return $p;
02258         }
02259 
02260         $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
02261 
02262         if ( in_array( $value, $validOptions ) ) {
02263             return true;
02264         } else {
02265             return $this->msg( 'htmlform-select-badoption' )->parse();
02266         }
02267     }
02268 
02269     function getInputHTML( $value ) {
02270         $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
02271 
02272         # If one of the options' 'name' is int(0), it is automatically selected.
02273         # because PHP sucks and thinks int(0) == 'some string'.
02274         # Working around this by forcing all of them to strings.
02275         foreach ( $this->mParams['options'] as &$opt ) {
02276             if ( is_int( $opt ) ) {
02277                 $opt = strval( $opt );
02278             }
02279         }
02280         unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
02281 
02282         if ( !empty( $this->mParams['disabled'] ) ) {
02283             $select->setAttribute( 'disabled', 'disabled' );
02284         }
02285 
02286         if ( $this->mClass !== '' ) {
02287             $select->setAttribute( 'class', $this->mClass );
02288         }
02289 
02290         $select->addOptions( $this->mParams['options'] );
02291 
02292         return $select->getHTML();
02293     }
02294 }
02295 
02299 class HTMLSelectOrOtherField extends HTMLTextField {
02300 
02301     function __construct( $params ) {
02302         if ( !in_array( 'other', $params['options'], true ) ) {
02303             $msg = isset( $params['other'] ) ?
02304                 $params['other'] :
02305                 wfMessage( 'htmlform-selectorother-other' )->text();
02306             $params['options'][$msg] = 'other';
02307         }
02308 
02309         parent::__construct( $params );
02310     }
02311 
02312     static function forceToStringRecursive( $array ) {
02313         if ( is_array( $array ) ) {
02314             return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
02315         } else {
02316             return strval( $array );
02317         }
02318     }
02319 
02320     function getInputHTML( $value ) {
02321         $valInSelect = false;
02322 
02323         if ( $value !== false ) {
02324             $valInSelect = in_array(
02325                 $value,
02326                 HTMLFormField::flattenOptions( $this->mParams['options'] )
02327             );
02328         }
02329 
02330         $selected = $valInSelect ? $value : 'other';
02331 
02332         $opts = self::forceToStringRecursive( $this->mParams['options'] );
02333 
02334         $select = new XmlSelect( $this->mName, $this->mID, $selected );
02335         $select->addOptions( $opts );
02336 
02337         $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
02338 
02339         $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
02340 
02341         if ( !empty( $this->mParams['disabled'] ) ) {
02342             $select->setAttribute( 'disabled', 'disabled' );
02343             $tbAttribs['disabled'] = 'disabled';
02344         }
02345 
02346         $select = $select->getHTML();
02347 
02348         if ( isset( $this->mParams['maxlength'] ) ) {
02349             $tbAttribs['maxlength'] = $this->mParams['maxlength'];
02350         }
02351 
02352         if ( $this->mClass !== '' ) {
02353             $tbAttribs['class'] = $this->mClass;
02354         }
02355 
02356         $textbox = Html::input(
02357             $this->mName . '-other',
02358             $valInSelect ? '' : $value,
02359             'text',
02360             $tbAttribs
02361         );
02362 
02363         return "$select<br />\n$textbox";
02364     }
02365 
02370     function loadDataFromRequest( $request ) {
02371         if ( $request->getCheck( $this->mName ) ) {
02372             $val = $request->getText( $this->mName );
02373 
02374             if ( $val == 'other' ) {
02375                 $val = $request->getText( $this->mName . '-other' );
02376             }
02377 
02378             return $val;
02379         } else {
02380             return $this->getDefault();
02381         }
02382     }
02383 }
02384 
02388 class HTMLMultiSelectField extends HTMLFormField implements HTMLNestedFilterable {
02389 
02390     function validate( $value, $alldata ) {
02391         $p = parent::validate( $value, $alldata );
02392 
02393         if ( $p !== true ) {
02394             return $p;
02395         }
02396 
02397         if ( !is_array( $value ) ) {
02398             return false;
02399         }
02400 
02401         # If all options are valid, array_intersect of the valid options
02402         # and the provided options will return the provided options.
02403         $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
02404 
02405         $validValues = array_intersect( $value, $validOptions );
02406         if ( count( $validValues ) == count( $value ) ) {
02407             return true;
02408         } else {
02409             return $this->msg( 'htmlform-select-badoption' )->parse();
02410         }
02411     }
02412 
02413     function getInputHTML( $value ) {
02414         $html = $this->formatOptions( $this->mParams['options'], $value );
02415 
02416         return $html;
02417     }
02418 
02419     function formatOptions( $options, $value ) {
02420         $html = '';
02421 
02422         $attribs = array();
02423 
02424         if ( !empty( $this->mParams['disabled'] ) ) {
02425             $attribs['disabled'] = 'disabled';
02426         }
02427 
02428         foreach ( $options as $label => $info ) {
02429             if ( is_array( $info ) ) {
02430                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
02431                 $html .= $this->formatOptions( $info, $value );
02432             } else {
02433                 $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
02434 
02435                 $checkbox = Xml::check(
02436                     $this->mName . '[]',
02437                     in_array( $info, $value, true ),
02438                     $attribs + $thisAttribs );
02439                 $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
02440 
02441                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
02442             }
02443         }
02444 
02445         return $html;
02446     }
02447 
02452     function loadDataFromRequest( $request ) {
02453         if ( $this->mParent->getMethod() == 'post' ) {
02454             if ( $request->wasPosted() ) {
02455                 # Checkboxes are just not added to the request arrays if they're not checked,
02456                 # so it's perfectly possible for there not to be an entry at all
02457                 return $request->getArray( $this->mName, array() );
02458             } else {
02459                 # That's ok, the user has not yet submitted the form, so show the defaults
02460                 return $this->getDefault();
02461             }
02462         } else {
02463             # This is the impossible case: if we look at $_GET and see no data for our
02464             # field, is it because the user has not yet submitted the form, or that they
02465             # have submitted it with all the options unchecked? We will have to assume the
02466             # latter, which basically means that you can't specify 'positive' defaults
02467             # for GET forms.
02468             # @todo FIXME...
02469             return $request->getArray( $this->mName, array() );
02470         }
02471     }
02472 
02473     function getDefault() {
02474         if ( isset( $this->mDefault ) ) {
02475             return $this->mDefault;
02476         } else {
02477             return array();
02478         }
02479     }
02480 
02481     function filterDataForSubmit( $data ) {
02482         $options = HTMLFormField::flattenOptions( $this->mParams['options'] );
02483 
02484         $res = array();
02485         foreach ( $options as $opt ) {
02486             $res["$opt"] = in_array( $opt, $data );
02487         }
02488 
02489         return $res;
02490     }
02491 
02492     protected function needsLabel() {
02493         return false;
02494     }
02495 }
02496 
02507 class HTMLSelectAndOtherField extends HTMLSelectField {
02508 
02509     function __construct( $params ) {
02510         if ( array_key_exists( 'other', $params ) ) {
02511         } elseif ( array_key_exists( 'other-message', $params ) ) {
02512             $params['other'] = wfMessage( $params['other-message'] )->plain();
02513         } else {
02514             $params['other'] = null;
02515         }
02516 
02517         if ( array_key_exists( 'options', $params ) ) {
02518             # Options array already specified
02519         } elseif ( array_key_exists( 'options-message', $params ) ) {
02520             # Generate options array from a system message
02521             $params['options'] = self::parseMessage(
02522                 wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
02523                 $params['other']
02524             );
02525         } else {
02526             # Sulk
02527             throw new MWException( 'HTMLSelectAndOtherField called without any options' );
02528         }
02529         $this->mFlatOptions = self::flattenOptions( $params['options'] );
02530 
02531         parent::__construct( $params );
02532     }
02533 
02541     public static function parseMessage( $string, $otherName = null ) {
02542         if ( $otherName === null ) {
02543             $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
02544         }
02545 
02546         $optgroup = false;
02547         $options = array( $otherName => 'other' );
02548 
02549         foreach ( explode( "\n", $string ) as $option ) {
02550             $value = trim( $option );
02551             if ( $value == '' ) {
02552                 continue;
02553             } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
02554                 # A new group is starting...
02555                 $value = trim( substr( $value, 1 ) );
02556                 $optgroup = $value;
02557             } elseif ( substr( $value, 0, 2 ) == '**' ) {
02558                 # groupmember
02559                 $opt = trim( substr( $value, 2 ) );
02560                 if ( $optgroup === false ) {
02561                     $options[$opt] = $opt;
02562                 } else {
02563                     $options[$optgroup][$opt] = $opt;
02564                 }
02565             } else {
02566                 # groupless reason list
02567                 $optgroup = false;
02568                 $options[$option] = $option;
02569             }
02570         }
02571 
02572         return $options;
02573     }
02574 
02575     function getInputHTML( $value ) {
02576         $select = parent::getInputHTML( $value[1] );
02577 
02578         $textAttribs = array(
02579             'id' => $this->mID . '-other',
02580             'size' => $this->getSize(),
02581         );
02582 
02583         if ( $this->mClass !== '' ) {
02584             $textAttribs['class'] = $this->mClass;
02585         }
02586 
02587         foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
02588             if ( isset( $this->mParams[$param] ) ) {
02589                 $textAttribs[$param] = '';
02590             }
02591         }
02592 
02593         $textbox = Html::input(
02594             $this->mName . '-other',
02595             $value[2],
02596             'text',
02597             $textAttribs
02598         );
02599 
02600         return "$select<br />\n$textbox";
02601     }
02602 
02607     function loadDataFromRequest( $request ) {
02608         if ( $request->getCheck( $this->mName ) ) {
02609 
02610             $list = $request->getText( $this->mName );
02611             $text = $request->getText( $this->mName . '-other' );
02612 
02613             if ( $list == 'other' ) {
02614                 $final = $text;
02615             } elseif ( !in_array( $list, $this->mFlatOptions ) ) {
02616                 # User has spoofed the select form to give an option which wasn't
02617                 # in the original offer.  Sulk...
02618                 $final = $text;
02619             } elseif ( $text == '' ) {
02620                 $final = $list;
02621             } else {
02622                 $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
02623             }
02624 
02625         } else {
02626             $final = $this->getDefault();
02627 
02628             $list = 'other';
02629             $text = $final;
02630             foreach ( $this->mFlatOptions as $option ) {
02631                 $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
02632                 if ( strpos( $text, $match ) === 0 ) {
02633                     $list = $option;
02634                     $text = substr( $text, strlen( $match ) );
02635                     break;
02636                 }
02637             }
02638         }
02639         return array( $final, $list, $text );
02640     }
02641 
02642     function getSize() {
02643         return isset( $this->mParams['size'] )
02644             ? $this->mParams['size']
02645             : 45;
02646     }
02647 
02648     function validate( $value, $alldata ) {
02649         # HTMLSelectField forces $value to be one of the options in the select
02650         # field, which is not useful here.  But we do want the validation further up
02651         # the chain
02652         $p = parent::validate( $value[1], $alldata );
02653 
02654         if ( $p !== true ) {
02655             return $p;
02656         }
02657 
02658         if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) {
02659             return $this->msg( 'htmlform-required' )->parse();
02660         }
02661 
02662         return true;
02663     }
02664 }
02665 
02669 class HTMLRadioField extends HTMLFormField {
02670 
02671     function validate( $value, $alldata ) {
02672         $p = parent::validate( $value, $alldata );
02673 
02674         if ( $p !== true ) {
02675             return $p;
02676         }
02677 
02678         if ( !is_string( $value ) && !is_int( $value ) ) {
02679             return false;
02680         }
02681 
02682         $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
02683 
02684         if ( in_array( $value, $validOptions ) ) {
02685             return true;
02686         } else {
02687             return $this->msg( 'htmlform-select-badoption' )->parse();
02688         }
02689     }
02690 
02697     function getInputHTML( $value ) {
02698         $html = $this->formatOptions( $this->mParams['options'], $value );
02699 
02700         return $html;
02701     }
02702 
02703     function formatOptions( $options, $value ) {
02704         $html = '';
02705 
02706         $attribs = array();
02707         if ( !empty( $this->mParams['disabled'] ) ) {
02708             $attribs['disabled'] = 'disabled';
02709         }
02710 
02711         # TODO: should this produce an unordered list perhaps?
02712         foreach ( $options as $label => $info ) {
02713             if ( is_array( $info ) ) {
02714                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
02715                 $html .= $this->formatOptions( $info, $value );
02716             } else {
02717                 $id = Sanitizer::escapeId( $this->mID . "-$info" );
02718                 $radio = Xml::radio(
02719                     $this->mName,
02720                     $info,
02721                     $info == $value,
02722                     $attribs + array( 'id' => $id )
02723                 );
02724                 $radio .= '&#160;' .
02725                         Html::rawElement( 'label', array( 'for' => $id ), $label );
02726 
02727                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
02728             }
02729         }
02730 
02731         return $html;
02732     }
02733 
02734     protected function needsLabel() {
02735         return false;
02736     }
02737 }
02738 
02742 class HTMLInfoField extends HTMLFormField {
02743     public function __construct( $info ) {
02744         $info['nodata'] = true;
02745 
02746         parent::__construct( $info );
02747     }
02748 
02749     public function getInputHTML( $value ) {
02750         return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
02751     }
02752 
02753     public function getTableRow( $value ) {
02754         if ( !empty( $this->mParams['rawrow'] ) ) {
02755             return $value;
02756         }
02757 
02758         return parent::getTableRow( $value );
02759     }
02760 
02764     public function getDiv( $value ) {
02765         if ( !empty( $this->mParams['rawrow'] ) ) {
02766             return $value;
02767         }
02768 
02769         return parent::getDiv( $value );
02770     }
02771 
02775     public function getRaw( $value ) {
02776         if ( !empty( $this->mParams['rawrow'] ) ) {
02777             return $value;
02778         }
02779 
02780         return parent::getRaw( $value );
02781     }
02782 
02783     protected function needsLabel() {
02784         return false;
02785     }
02786 }
02787 
02788 class HTMLHiddenField extends HTMLFormField {
02789     public function __construct( $params ) {
02790         parent::__construct( $params );
02791 
02792         # Per HTML5 spec, hidden fields cannot be 'required'
02793         # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
02794         unset( $this->mParams['required'] );
02795     }
02796 
02797     public function getTableRow( $value ) {
02798         $params = array();
02799         if ( $this->mID ) {
02800             $params['id'] = $this->mID;
02801         }
02802 
02803         $this->mParent->addHiddenField(
02804             $this->mName,
02805             $this->mDefault,
02806             $params
02807         );
02808 
02809         return '';
02810     }
02811 
02815     public function getDiv( $value ) {
02816         return $this->getTableRow( $value );
02817     }
02818 
02822     public function getRaw( $value ) {
02823         return $this->getTableRow( $value );
02824     }
02825 
02826     public function getInputHTML( $value ) {
02827         return '';
02828     }
02829 }
02830 
02835 class HTMLSubmitField extends HTMLButtonField {
02836     protected $buttonType = 'submit';
02837 }
02838 
02846 class HTMLButtonField extends HTMLFormField {
02847     protected $buttonType = 'button';
02848 
02849     public function __construct( $info ) {
02850         $info['nodata'] = true;
02851         parent::__construct( $info );
02852     }
02853 
02854     public function getInputHTML( $value ) {
02855         $attr = array(
02856             'class' => 'mw-htmlform-submit ' . $this->mClass,
02857             'id' => $this->mID,
02858         );
02859 
02860         if ( !empty( $this->mParams['disabled'] ) ) {
02861             $attr['disabled'] = 'disabled';
02862         }
02863 
02864         return Html::input(
02865             $this->mName,
02866             $value,
02867             $this->buttonType,
02868             $attr
02869         );
02870     }
02871 
02872     protected function needsLabel() {
02873         return false;
02874     }
02875 
02882     public function validate( $value, $alldata ) {
02883         return true;
02884     }
02885 }
02886 
02887 class HTMLEditTools extends HTMLFormField {
02888     public function getInputHTML( $value ) {
02889         return '';
02890     }
02891 
02892     public function getTableRow( $value ) {
02893         $msg = $this->formatMsg();
02894 
02895         return '<tr><td></td><td class="mw-input">'
02896             . '<div class="mw-editTools">'
02897             . $msg->parseAsBlock()
02898             . "</div></td></tr>\n";
02899     }
02900 
02904     public function getDiv( $value ) {
02905         $msg = $this->formatMsg();
02906         return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
02907     }
02908 
02912     public function getRaw( $value ) {
02913         return $this->getDiv( $value );
02914     }
02915 
02916     protected function formatMsg() {
02917         if ( empty( $this->mParams['message'] ) ) {
02918             $msg = $this->msg( 'edittools' );
02919         } else {
02920             $msg = $this->msg( $this->mParams['message'] );
02921             if ( $msg->isDisabled() ) {
02922                 $msg = $this->msg( 'edittools' );
02923             }
02924         }
02925         $msg->inContentLanguage();
02926         return $msg;
02927     }
02928 }
02929 
02930 class HTMLApiField extends HTMLFormField {
02931     public function getTableRow( $value ) {
02932         return '';
02933     }
02934 
02935     public function getDiv( $value ) {
02936         return $this->getTableRow( $value );
02937     }
02938 
02939     public function getRaw( $value ) {
02940         return $this->getTableRow( $value );
02941     }
02942 
02943     public function getInputHTML( $value ) {
02944         return '';
02945     }
02946 }
02947 
02948 interface HTMLNestedFilterable {
02954     function filterDataForSubmit( $data );
02955 }
02956 
02957 class HTMLFormFieldRequiredOptionsException extends MWException {
02958     public function __construct( HTMLFormField $field, array $missing ) {
02959         parent::__construct( sprintf(
02960             "Form type `%s` expected the following parameters to be set: %s",
02961             get_class( $field ),
02962             implode( ', ', $missing )
02963         ) );
02964     }
02965 }