MediaWiki  REL1_20
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 
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         public $mFieldData;
00131 
00132         protected $mSubmitCallback;
00133         protected $mValidationErrorMessage;
00134 
00135         protected $mPre = '';
00136         protected $mHeader = '';
00137         protected $mFooter = '';
00138         protected $mSectionHeaders = array();
00139         protected $mSectionFooters = array();
00140         protected $mPost = '';
00141         protected $mId;
00142 
00143         protected $mSubmitID;
00144         protected $mSubmitName;
00145         protected $mSubmitText;
00146         protected $mSubmitTooltip;
00147 
00148         protected $mTitle;
00149         protected $mMethod = 'post';
00150 
00156         protected $mAction = false;
00157 
00158         protected $mUseMultipart = false;
00159         protected $mHiddenFields = array();
00160         protected $mButtons = array();
00161 
00162         protected $mWrapperLegend = false;
00163 
00171         protected $mSubSectionBeforeFields = true;
00172 
00178         protected $displayFormat = 'table';
00179 
00184         protected $availableDisplayFormats = array(
00185                 'table',
00186                 'div',
00187                 'raw',
00188         );
00189 
00197         public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
00198                 if ( $context instanceof IContextSource ) {
00199                         $this->setContext( $context );
00200                         $this->mTitle = false; // We don't need them to set a title
00201                         $this->mMessagePrefix = $messagePrefix;
00202                 } else {
00203                         // B/C since 1.18
00204                         if ( is_string( $context ) && $messagePrefix === '' ) {
00205                                 // it's actually $messagePrefix
00206                                 $this->mMessagePrefix = $context;
00207                         }
00208                 }
00209 
00210                 // Expand out into a tree.
00211                 $loadedDescriptor = array();
00212                 $this->mFlatFields = array();
00213 
00214                 foreach ( $descriptor as $fieldname => $info ) {
00215                         $section = isset( $info['section'] )
00216                                 ? $info['section']
00217                                 : '';
00218 
00219                         if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
00220                                 $this->mUseMultipart = true;
00221                         }
00222 
00223                         $field = self::loadInputFromParameters( $fieldname, $info );
00224                         $field->mParent = $this;
00225 
00226                         $setSection =& $loadedDescriptor;
00227                         if ( $section ) {
00228                                 $sectionParts = explode( '/', $section );
00229 
00230                                 while ( count( $sectionParts ) ) {
00231                                         $newName = array_shift( $sectionParts );
00232 
00233                                         if ( !isset( $setSection[$newName] ) ) {
00234                                                 $setSection[$newName] = array();
00235                                         }
00236 
00237                                         $setSection =& $setSection[$newName];
00238                                 }
00239                         }
00240 
00241                         $setSection[$fieldname] = $field;
00242                         $this->mFlatFields[$fieldname] = $field;
00243                 }
00244 
00245                 $this->mFieldTree = $loadedDescriptor;
00246         }
00247 
00255         public function setDisplayFormat( $format ) {
00256                 if ( !in_array( $format, $this->availableDisplayFormats ) ) {
00257                         throw new MWException ( 'Display format must be one of ' . print_r( $this->availableDisplayFormats, true ) );
00258                 }
00259                 $this->displayFormat = $format;
00260                 return $this;
00261         }
00262 
00268         public function getDisplayFormat() {
00269                 return $this->displayFormat;
00270         }
00271 
00277         static function addJS() { wfDeprecated( __METHOD__, '1.18' ); }
00278 
00285         static function loadInputFromParameters( $fieldname, $descriptor ) {
00286                 if ( isset( $descriptor['class'] ) ) {
00287                         $class = $descriptor['class'];
00288                 } elseif ( isset( $descriptor['type'] ) ) {
00289                         $class = self::$typeMappings[$descriptor['type']];
00290                         $descriptor['class'] = $class;
00291                 } else {
00292                         $class = null;
00293                 }
00294 
00295                 if ( !$class ) {
00296                         throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
00297                 }
00298 
00299                 $descriptor['fieldname'] = $fieldname;
00300 
00301                 # TODO
00302                 # This will throw a fatal error whenever someone try to use
00303                 # 'class' to feed a CSS class instead of 'cssclass'. Would be
00304                 # great to avoid the fatal error and show a nice error.
00305                 $obj = new $class( $descriptor );
00306 
00307                 return $obj;
00308         }
00309 
00318         function prepareForm() {
00319                 # Check if we have the info we need
00320                 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
00321                         throw new MWException( "You must call setTitle() on an HTMLForm" );
00322                 }
00323 
00324                 # Load data from the request.
00325                 $this->loadData();
00326                 return $this;
00327         }
00328 
00333         function tryAuthorizedSubmit() {
00334                 $result = false;
00335 
00336                 $submit = false;
00337                 if ( $this->getMethod() != 'post' ) {
00338                         $submit = true; // no session check needed
00339                 } elseif ( $this->getRequest()->wasPosted() ) {
00340                         $editToken = $this->getRequest()->getVal( 'wpEditToken' );
00341                         if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
00342                                 // Session tokens for logged-out users have no security value.
00343                                 // However, if the user gave one, check it in order to give a nice
00344                                 // "session expired" error instead of "permission denied" or such.
00345                                 $submit = $this->getUser()->matchEditToken( $editToken );
00346                         } else {
00347                                 $submit = true;
00348                         }
00349                 }
00350 
00351                 if ( $submit ) {
00352                         $result = $this->trySubmit();
00353                 }
00354 
00355                 return $result;
00356         }
00357 
00364         function show() {
00365                 $this->prepareForm();
00366 
00367                 $result = $this->tryAuthorizedSubmit();
00368                 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
00369                         return $result;
00370                 }
00371 
00372                 $this->displayForm( $result );
00373                 return false;
00374         }
00375 
00383         function trySubmit() {
00384                 # Check for validation
00385                 foreach ( $this->mFlatFields as $fieldname => $field ) {
00386                         if ( !empty( $field->mParams['nodata'] ) ) {
00387                                 continue;
00388                         }
00389                         if ( $field->validate(
00390                                         $this->mFieldData[$fieldname],
00391                                         $this->mFieldData )
00392                                 !== true
00393                         ) {
00394                                 return isset( $this->mValidationErrorMessage )
00395                                         ? $this->mValidationErrorMessage
00396                                         : array( 'htmlform-invalid-input' );
00397                         }
00398                 }
00399 
00400                 $callback = $this->mSubmitCallback;
00401                 if ( !is_callable( $callback ) ) {
00402                         throw new MWException( 'HTMLForm: no submit callback provided. Use setSubmitCallback() to set one.' );
00403                 }
00404 
00405                 $data = $this->filterDataForSubmit( $this->mFieldData );
00406 
00407                 $res = call_user_func( $callback, $data, $this );
00408 
00409                 return $res;
00410         }
00411 
00421         function setSubmitCallback( $cb ) {
00422                 $this->mSubmitCallback = $cb;
00423                 return $this;
00424         }
00425 
00432         function setValidationErrorMessage( $msg ) {
00433                 $this->mValidationErrorMessage = $msg;
00434                 return $this;
00435         }
00436 
00442         function setIntro( $msg ) {
00443                 $this->setPreText( $msg );
00444                 return $this;
00445         }
00446 
00453         function setPreText( $msg ) {
00454                 $this->mPre = $msg;
00455                 return $this;
00456         }
00457 
00463         function addPreText( $msg ) {
00464                 $this->mPre .= $msg;
00465                 return $this;
00466         }
00467 
00474         function addHeaderText( $msg, $section = null ) {
00475                 if ( is_null( $section ) ) {
00476                         $this->mHeader .= $msg;
00477                 } else {
00478                         if ( !isset( $this->mSectionHeaders[$section] ) ) {
00479                                 $this->mSectionHeaders[$section] = '';
00480                         }
00481                         $this->mSectionHeaders[$section] .= $msg;
00482                 }
00483                 return $this;
00484         }
00485 
00493         function setHeaderText( $msg, $section = null ) {
00494                 if ( is_null( $section ) ) {
00495                         $this->mHeader = $msg;
00496                 } else {
00497                         $this->mSectionHeaders[$section] = $msg;
00498                 }
00499                 return $this;
00500         }
00501 
00508         function addFooterText( $msg, $section = null ) {
00509                 if ( is_null( $section ) ) {
00510                         $this->mFooter .= $msg;
00511                 } else {
00512                         if ( !isset( $this->mSectionFooters[$section] ) ) {
00513                                 $this->mSectionFooters[$section] = '';
00514                         }
00515                         $this->mSectionFooters[$section] .= $msg;
00516                 }
00517                 return $this;
00518         }
00519 
00527         function setFooterText( $msg, $section = null ) {
00528                 if ( is_null( $section ) ) {
00529                         $this->mFooter = $msg;
00530                 } else {
00531                         $this->mSectionFooters[$section] = $msg;
00532                 }
00533                 return $this;
00534         }
00535 
00541         function addPostText( $msg ) {
00542                 $this->mPost .= $msg;
00543                 return $this;
00544         }
00545 
00551         function setPostText( $msg ) {
00552                 $this->mPost = $msg;
00553                 return $this;
00554         }
00555 
00563         public function addHiddenField( $name, $value, $attribs = array() ) {
00564                 $attribs += array( 'name' => $name );
00565                 $this->mHiddenFields[] = array( $value, $attribs );
00566                 return $this;
00567         }
00568 
00577         public function addButton( $name, $value, $id = null, $attribs = null ) {
00578                 $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
00579                 return $this;
00580         }
00581 
00593         function displayForm( $submitResult ) {
00594                 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
00595         }
00596 
00602         function getHTML( $submitResult ) {
00603                 # For good measure (it is the default)
00604                 $this->getOutput()->preventClickjacking();
00605                 $this->getOutput()->addModules( 'mediawiki.htmlform' );
00606 
00607                 $html = ''
00608                         . $this->getErrors( $submitResult )
00609                         . $this->mHeader
00610                         . $this->getBody()
00611                         . $this->getHiddenFields()
00612                         . $this->getButtons()
00613                         . $this->mFooter
00614                 ;
00615 
00616                 $html = $this->wrapForm( $html );
00617 
00618                 return '' . $this->mPre . $html . $this->mPost;
00619         }
00620 
00626         function wrapForm( $html ) {
00627 
00628                 # Include a <fieldset> wrapper for style, if requested.
00629                 if ( $this->mWrapperLegend !== false ) {
00630                         $html = Xml::fieldset( $this->mWrapperLegend, $html );
00631                 }
00632                 # Use multipart/form-data
00633                 $encType = $this->mUseMultipart
00634                         ? 'multipart/form-data'
00635                         : 'application/x-www-form-urlencoded';
00636                 # Attributes
00637                 $attribs = array(
00638                         'action'  => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
00639                         'method'  => $this->mMethod,
00640                         'class'   => 'visualClear',
00641                         'enctype' => $encType,
00642                 );
00643                 if ( !empty( $this->mId ) ) {
00644                         $attribs['id'] = $this->mId;
00645                 }
00646 
00647                 return Html::rawElement( 'form', $attribs, $html );
00648         }
00649 
00654         function getHiddenFields() {
00655                 global $wgArticlePath;
00656 
00657                 $html = '';
00658                 if ( $this->getMethod() == 'post' ) {
00659                         $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
00660                         $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00661                 }
00662 
00663                 if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
00664                         $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00665                 }
00666 
00667                 foreach ( $this->mHiddenFields as $data ) {
00668                         list( $value, $attribs ) = $data;
00669                         $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
00670                 }
00671 
00672                 return $html;
00673         }
00674 
00679         function getButtons() {
00680                 $html = '';
00681                 $attribs = array();
00682 
00683                 if ( isset( $this->mSubmitID ) ) {
00684                         $attribs['id'] = $this->mSubmitID;
00685                 }
00686 
00687                 if ( isset( $this->mSubmitName ) ) {
00688                         $attribs['name'] = $this->mSubmitName;
00689                 }
00690 
00691                 if ( isset( $this->mSubmitTooltip ) ) {
00692                         $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
00693                 }
00694 
00695                 $attribs['class'] = 'mw-htmlform-submit';
00696 
00697                 $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
00698 
00699                 if ( $this->mShowReset ) {
00700                         $html .= Html::element(
00701                                 'input',
00702                                 array(
00703                                         'type' => 'reset',
00704                                         'value' => $this->msg( 'htmlform-reset' )->text()
00705                                 )
00706                         ) . "\n";
00707                 }
00708 
00709                 foreach ( $this->mButtons as $button ) {
00710                         $attrs = array(
00711                                 'type'  => 'submit',
00712                                 'name'  => $button['name'],
00713                                 'value' => $button['value']
00714                         );
00715 
00716                         if ( $button['attribs'] ) {
00717                                 $attrs += $button['attribs'];
00718                         }
00719 
00720                         if ( isset( $button['id'] ) ) {
00721                                 $attrs['id'] = $button['id'];
00722                         }
00723 
00724                         $html .= Html::element( 'input', $attrs );
00725                 }
00726 
00727                 return $html;
00728         }
00729 
00734         function getBody() {
00735                 return $this->displaySection( $this->mFieldTree );
00736         }
00737 
00743         function getErrors( $errors ) {
00744                 if ( $errors instanceof Status ) {
00745                         if ( $errors->isOK() ) {
00746                                 $errorstr = '';
00747                         } else {
00748                                 $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
00749                         }
00750                 } elseif ( is_array( $errors ) ) {
00751                         $errorstr = $this->formatErrors( $errors );
00752                 } else {
00753                         $errorstr = $errors;
00754                 }
00755 
00756                 return $errorstr
00757                         ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
00758                         : '';
00759         }
00760 
00766         public static function formatErrors( $errors ) {
00767                 $errorstr = '';
00768 
00769                 foreach ( $errors as $error ) {
00770                         if ( is_array( $error ) ) {
00771                                 $msg = array_shift( $error );
00772                         } else {
00773                                 $msg = $error;
00774                                 $error = array();
00775                         }
00776 
00777                         $errorstr .= Html::rawElement(
00778                                 'li',
00779                                 array(),
00780                                 wfMessage( $msg, $error )->parse()
00781                         );
00782                 }
00783 
00784                 $errorstr = Html::rawElement( 'ul', array(), $errorstr );
00785 
00786                 return $errorstr;
00787         }
00788 
00794         function setSubmitText( $t ) {
00795                 $this->mSubmitText = $t;
00796                 return $this;
00797         }
00798 
00805         public function setSubmitTextMsg( $msg ) {
00806                 $this->setSubmitText( $this->msg( $msg )->text() );
00807                 return $this;
00808         }
00809 
00814         function getSubmitText() {
00815                 return $this->mSubmitText
00816                         ? $this->mSubmitText
00817                         : $this->msg( 'htmlform-submit' )->text();
00818         }
00819 
00824         public function setSubmitName( $name ) {
00825                 $this->mSubmitName = $name;
00826                 return $this;
00827         }
00828 
00833         public function setSubmitTooltip( $name ) {
00834                 $this->mSubmitTooltip = $name;
00835                 return $this;
00836         }
00837 
00844         function setSubmitID( $t ) {
00845                 $this->mSubmitID = $t;
00846                 return $this;
00847         }
00848 
00853         public function setId( $id ) {
00854                 $this->mId = $id;
00855                 return $this;
00856         }
00864         public function setWrapperLegend( $legend ) {
00865                 $this->mWrapperLegend = $legend;
00866                 return $this;
00867         }
00868 
00876         public function setWrapperLegendMsg( $msg ) {
00877                 $this->setWrapperLegend( $this->msg( $msg )->text() );
00878                 return $this;
00879         }
00880 
00888         function setMessagePrefix( $p ) {
00889                 $this->mMessagePrefix = $p;
00890                 return $this;
00891         }
00892 
00898         function setTitle( $t ) {
00899                 $this->mTitle = $t;
00900                 return $this;
00901         }
00902 
00907         function getTitle() {
00908                 return $this->mTitle === false
00909                         ? $this->getContext()->getTitle()
00910                         : $this->mTitle;
00911         }
00912 
00918         public function setMethod( $method = 'post' ) {
00919                 $this->mMethod = $method;
00920                 return $this;
00921         }
00922 
00923         public function getMethod() {
00924                 return $this->mMethod;
00925         }
00926 
00934         public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
00935                 $displayFormat = $this->getDisplayFormat();
00936 
00937                 $html = '';
00938                 $subsectionHtml = '';
00939                 $hasLabel = false;
00940 
00941                 $getFieldHtmlMethod = ( $displayFormat == 'table' ) ? 'getTableRow' : 'get' . ucfirst( $displayFormat );
00942 
00943                 foreach ( $fields as $key => $value ) {
00944                         if ( $value instanceof HTMLFormField ) {
00945                                 $v = empty( $value->mParams['nodata'] )
00946                                         ? $this->mFieldData[$key]
00947                                         : $value->getDefault();
00948                                 $html .= $value->$getFieldHtmlMethod( $v );
00949 
00950                                 $labelValue = trim( $value->getLabel() );
00951                                 if ( $labelValue != '&#160;' && $labelValue !== '' ) {
00952                                         $hasLabel = true;
00953                                 }
00954                         } elseif ( is_array( $value ) ) {
00955                                 $section = $this->displaySection( $value, $key );
00956                                 $legend = $this->getLegend( $key );
00957                                 if ( isset( $this->mSectionHeaders[$key] ) ) {
00958                                         $section = $this->mSectionHeaders[$key] . $section;
00959                                 }
00960                                 if ( isset( $this->mSectionFooters[$key] ) ) {
00961                                         $section .= $this->mSectionFooters[$key];
00962                                 }
00963                                 $attributes = array();
00964                                 if ( $fieldsetIDPrefix ) {
00965                                         $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
00966                                 }
00967                                 $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
00968                         }
00969                 }
00970 
00971                 if ( $displayFormat !== 'raw' ) {
00972                         $classes = array();
00973 
00974                         if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
00975                                 $classes[] = 'mw-htmlform-nolabel';
00976                         }
00977 
00978                         $attribs = array(
00979                                 'class' => implode( ' ', $classes ),
00980                         );
00981 
00982                         if ( $sectionName ) {
00983                                 $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
00984                         }
00985 
00986                         if ( $displayFormat === 'table' ) {
00987                                 $html = Html::rawElement( 'table', $attribs,
00988                                         Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
00989                         } elseif ( $displayFormat === 'div' ) {
00990                                 $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
00991                         }
00992                 }
00993 
00994                 if ( $this->mSubSectionBeforeFields ) {
00995                         return $subsectionHtml . "\n" . $html;
00996                 } else {
00997                         return $html . "\n" . $subsectionHtml;
00998                 }
00999         }
01000 
01004         function loadData() {
01005                 $fieldData = array();
01006 
01007                 foreach ( $this->mFlatFields as $fieldname => $field ) {
01008                         if ( !empty( $field->mParams['nodata'] ) ) {
01009                                 continue;
01010                         } elseif ( !empty( $field->mParams['disabled'] ) ) {
01011                                 $fieldData[$fieldname] = $field->getDefault();
01012                         } else {
01013                                 $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
01014                         }
01015                 }
01016 
01017                 # Filter data.
01018                 foreach ( $fieldData as $name => &$value ) {
01019                         $field = $this->mFlatFields[$name];
01020                         $value = $field->filter( $value, $this->mFlatFields );
01021                 }
01022 
01023                 $this->mFieldData = $fieldData;
01024         }
01025 
01032         function suppressReset( $suppressReset = true ) {
01033                 $this->mShowReset = !$suppressReset;
01034                 return $this;
01035         }
01036 
01044         function filterDataForSubmit( $data ) {
01045                 return $data;
01046         }
01047 
01054         public function getLegend( $key ) {
01055                 return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
01056         }
01057 
01067         public function setAction( $action ) {
01068                 $this->mAction = $action;
01069                 return $this;
01070         }
01071 
01072 }
01073 
01078 abstract class HTMLFormField {
01079 
01080         protected $mValidationCallback;
01081         protected $mFilterCallback;
01082         protected $mName;
01083         public $mParams;
01084         protected $mLabel;      # String label.  Set on construction
01085         protected $mID;
01086         protected $mClass = '';
01087         protected $mDefault;
01088 
01092         public $mParent;
01093 
01102         abstract function getInputHTML( $value );
01103 
01114         function msg() {
01115                 $args = func_get_args();
01116 
01117                 if ( $this->mParent ) {
01118                         $callback = array( $this->mParent, 'msg' );
01119                 } else {
01120                         $callback = 'wfMessage';
01121                 }
01122 
01123                 return call_user_func_array( $callback, $args );
01124         }
01125 
01134         function validate( $value, $alldata ) {
01135                 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) {
01136                         return $this->msg( 'htmlform-required' )->parse();
01137                 }
01138 
01139                 if ( isset( $this->mValidationCallback ) ) {
01140                         return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
01141                 }
01142 
01143                 return true;
01144         }
01145 
01146         function filter( $value, $alldata ) {
01147                 if ( isset( $this->mFilterCallback ) ) {
01148                         $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
01149                 }
01150 
01151                 return $value;
01152         }
01153 
01160         protected function needsLabel() {
01161                 return true;
01162         }
01163 
01170         function loadDataFromRequest( $request ) {
01171                 if ( $request->getCheck( $this->mName ) ) {
01172                         return $request->getText( $this->mName );
01173                 } else {
01174                         return $this->getDefault();
01175                 }
01176         }
01177 
01182         function __construct( $params ) {
01183                 $this->mParams = $params;
01184 
01185                 # Generate the label from a message, if possible
01186                 if ( isset( $params['label-message'] ) ) {
01187                         $msgInfo = $params['label-message'];
01188 
01189                         if ( is_array( $msgInfo ) ) {
01190                                 $msg = array_shift( $msgInfo );
01191                         } else {
01192                                 $msg = $msgInfo;
01193                                 $msgInfo = array();
01194                         }
01195 
01196                         $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
01197                 } elseif ( isset( $params['label'] ) ) {
01198                         $this->mLabel = $params['label'];
01199                 }
01200 
01201                 $this->mName = "wp{$params['fieldname']}";
01202                 if ( isset( $params['name'] ) ) {
01203                         $this->mName = $params['name'];
01204                 }
01205 
01206                 $validName = Sanitizer::escapeId( $this->mName );
01207                 if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
01208                         throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
01209                 }
01210 
01211                 $this->mID = "mw-input-{$this->mName}";
01212 
01213                 if ( isset( $params['default'] ) ) {
01214                         $this->mDefault = $params['default'];
01215                 }
01216 
01217                 if ( isset( $params['id'] ) ) {
01218                         $id = $params['id'];
01219                         $validId = Sanitizer::escapeId( $id );
01220 
01221                         if ( $id != $validId ) {
01222                                 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
01223                         }
01224 
01225                         $this->mID = $id;
01226                 }
01227 
01228                 if ( isset( $params['cssclass'] ) ) {
01229                         $this->mClass = $params['cssclass'];
01230                 }
01231 
01232                 if ( isset( $params['validation-callback'] ) ) {
01233                         $this->mValidationCallback = $params['validation-callback'];
01234                 }
01235 
01236                 if ( isset( $params['filter-callback'] ) ) {
01237                         $this->mFilterCallback = $params['filter-callback'];
01238                 }
01239 
01240                 if ( isset( $params['flatlist'] ) ) {
01241                         $this->mClass .= ' mw-htmlform-flatlist';
01242                 }
01243         }
01244 
01251         function getTableRow( $value ) {
01252                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01253                 $inputHtml = $this->getInputHTML( $value );
01254                 $fieldType = get_class( $this );
01255                 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
01256                 $cellAttributes = array();
01257 
01258                 if ( !empty( $this->mParams['vertical-label'] ) ) {
01259                         $cellAttributes['colspan'] = 2;
01260                         $verticalLabel = true;
01261                 } else {
01262                         $verticalLabel = false;
01263                 }
01264 
01265                 $label = $this->getLabelHtml( $cellAttributes );
01266 
01267                 $field = Html::rawElement(
01268                         'td',
01269                         array( 'class' => 'mw-input' ) + $cellAttributes,
01270                         $inputHtml . "\n$errors"
01271                 );
01272 
01273                 if ( $verticalLabel ) {
01274                         $html = Html::rawElement( 'tr',
01275                                 array( 'class' => 'mw-htmlform-vertical-label' ), $label );
01276                         $html .= Html::rawElement( 'tr',
01277                                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01278                                 $field );
01279                 } else {
01280                         $html = Html::rawElement( 'tr',
01281                                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01282                                 $label . $field );
01283                 }
01284 
01285                 return $html . $helptext;
01286         }
01287 
01295         public function getDiv( $value ) {
01296                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01297                 $inputHtml = $this->getInputHTML( $value );
01298                 $fieldType = get_class( $this );
01299                 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
01300                 $cellAttributes = array();
01301                 $label = $this->getLabelHtml( $cellAttributes );
01302 
01303                 $field = Html::rawElement(
01304                         'div',
01305                         array( 'class' => 'mw-input' ) + $cellAttributes,
01306                         $inputHtml . "\n$errors"
01307                 );
01308                 $html = Html::rawElement( 'div',
01309                         array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01310                         $label . $field );
01311                 $html .= $helptext;
01312                 return $html;
01313         }
01314 
01322         public function getRaw( $value ) {
01323                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01324                 $inputHtml = $this->getInputHTML( $value );
01325                 $fieldType = get_class( $this );
01326                 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
01327                 $cellAttributes = array();
01328                 $label = $this->getLabelHtml( $cellAttributes );
01329 
01330                 $html = "\n$errors";
01331                 $html .= $label;
01332                 $html .= $inputHtml;
01333                 $html .= $helptext;
01334                 return $html;
01335         }
01336 
01343         public function getHelpTextHtmlTable( $helptext ) {
01344                 if ( is_null( $helptext ) ) {
01345                         return '';
01346                 }
01347 
01348                 $row = Html::rawElement(
01349                         'td',
01350                         array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
01351                         $helptext
01352                 );
01353                 $row = Html::rawElement( 'tr', array(), $row );
01354                 return $row;
01355         }
01356 
01363         public function getHelpTextHtmlDiv( $helptext ) {
01364                 if ( is_null( $helptext ) ) {
01365                         return '';
01366                 }
01367 
01368                 $div = Html::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
01369                 return $div;
01370         }
01371 
01378         public function getHelpTextHtmlRaw( $helptext ) {
01379                 return $this->getHelpTextHtmlDiv( $helptext );
01380         }
01381 
01387         public function getHelpText() {
01388                 $helptext = null;
01389 
01390                 if ( isset( $this->mParams['help-message'] ) ) {
01391                         $this->mParams['help-messages'] = array( $this->mParams['help-message'] );
01392                 }
01393 
01394                 if ( isset( $this->mParams['help-messages'] ) ) {
01395                         foreach ( $this->mParams['help-messages'] as $name ) {
01396                                 $helpMessage = (array)$name;
01397                                 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
01398 
01399                                 if ( $msg->exists() ) {
01400                                         if ( is_null( $helptext ) ) {
01401                                                 $helptext = '';
01402                                         } else {
01403                                                 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
01404                                         }
01405                                         $helptext .= $msg->parse(); // Append message
01406                                 }
01407                         }
01408                 }
01409                 elseif ( isset( $this->mParams['help'] ) ) {
01410                         $helptext = $this->mParams['help'];
01411                 }
01412                 return $helptext;
01413         }
01414 
01421         public function getErrorsAndErrorClass( $value ) {
01422                 $errors = $this->validate( $value, $this->mParent->mFieldData );
01423 
01424                 if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
01425                         $errors = '';
01426                         $errorClass = '';
01427                 } else {
01428                         $errors = self::formatErrors( $errors );
01429                         $errorClass = 'mw-htmlform-invalid-input';
01430                 }
01431                 return array( $errors, $errorClass );
01432         }
01433 
01434         function getLabel() {
01435                 return $this->mLabel;
01436         }
01437 
01438         function getLabelHtml( $cellAttributes = array() ) {
01439                 # Don't output a for= attribute for labels with no associated input.
01440                 # Kind of hacky here, possibly we don't want these to be <label>s at all.
01441                 $for = array();
01442 
01443                 if ( $this->needsLabel() ) {
01444                         $for['for'] = $this->mID;
01445                 }
01446 
01447                 $displayFormat = $this->mParent->getDisplayFormat();
01448                 $labelElement = Html::rawElement( 'label', $for, $this->getLabel() );
01449 
01450                 if ( $displayFormat == 'table' ) {
01451                         return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
01452                                 Html::rawElement( 'label', $for, $this->getLabel() )
01453                         );
01454                 } elseif ( $displayFormat == 'div' ) {
01455                         return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes,
01456                                 Html::rawElement( 'label', $for, $this->getLabel() )
01457                         );
01458                 } else {
01459                         return $labelElement;
01460                 }
01461         }
01462 
01463         function getDefault() {
01464                 if ( isset( $this->mDefault ) ) {
01465                         return $this->mDefault;
01466                 } else {
01467                         return null;
01468                 }
01469         }
01470 
01476         public function getTooltipAndAccessKey() {
01477                 if ( empty( $this->mParams['tooltip'] ) ) {
01478                         return array();
01479                 }
01480                 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
01481         }
01482 
01490         public static function flattenOptions( $options ) {
01491                 $flatOpts = array();
01492 
01493                 foreach ( $options as $value ) {
01494                         if ( is_array( $value ) ) {
01495                                 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
01496                         } else {
01497                                 $flatOpts[] = $value;
01498                         }
01499                 }
01500 
01501                 return $flatOpts;
01502         }
01503 
01510         protected static function formatErrors( $errors ) {
01511                 if ( is_array( $errors ) && count( $errors ) === 1 ) {
01512                         $errors = array_shift( $errors );
01513                 }
01514 
01515                 if ( is_array( $errors ) ) {
01516                         $lines = array();
01517                         foreach ( $errors as $error ) {
01518                                 if ( $error instanceof Message ) {
01519                                         $lines[] = Html::rawElement( 'li', array(), $error->parse() );
01520                                 } else {
01521                                         $lines[] = Html::rawElement( 'li', array(), $error );
01522                                 }
01523                         }
01524                         return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
01525                 } else {
01526                         if ( $errors instanceof Message ) {
01527                                 $errors = $errors->parse();
01528                         }
01529                         return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
01530                 }
01531         }
01532 }
01533 
01534 class HTMLTextField extends HTMLFormField {
01535         function getSize() {
01536                 return isset( $this->mParams['size'] )
01537                         ? $this->mParams['size']
01538                         : 45;
01539         }
01540 
01541         function getInputHTML( $value ) {
01542                 $attribs = array(
01543                         'id' => $this->mID,
01544                         'name' => $this->mName,
01545                         'size' => $this->getSize(),
01546                         'value' => $value,
01547                 ) + $this->getTooltipAndAccessKey();
01548 
01549                 if ( $this->mClass !== '' ) {
01550                         $attribs['class'] = $this->mClass;
01551                 }
01552 
01553                 if ( !empty( $this->mParams['disabled'] ) ) {
01554                         $attribs['disabled'] = 'disabled';
01555                 }
01556 
01557                 # TODO: Enforce pattern, step, required, readonly on the server side as
01558                 # well
01559                 $allowedParams = array( 'min', 'max', 'pattern', 'title', 'step',
01560                         'placeholder', 'list', 'maxlength' );
01561                 foreach ( $allowedParams as $param ) {
01562                         if ( isset( $this->mParams[$param] ) ) {
01563                                 $attribs[$param] = $this->mParams[$param];
01564                         }
01565                 }
01566 
01567                 foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
01568                         if ( isset( $this->mParams[$param] ) ) {
01569                                 $attribs[$param] = '';
01570                         }
01571                 }
01572 
01573                 # Implement tiny differences between some field variants
01574                 # here, rather than creating a new class for each one which
01575                 # is essentially just a clone of this one.
01576                 if ( isset( $this->mParams['type'] ) ) {
01577                         switch ( $this->mParams['type'] ) {
01578                                 case 'email':
01579                                         $attribs['type'] = 'email';
01580                                         break;
01581                                 case 'int':
01582                                         $attribs['type'] = 'number';
01583                                         break;
01584                                 case 'float':
01585                                         $attribs['type'] = 'number';
01586                                         $attribs['step'] = 'any';
01587                                         break;
01588                                 # Pass through
01589                                 case 'password':
01590                                 case 'file':
01591                                         $attribs['type'] = $this->mParams['type'];
01592                                         break;
01593                         }
01594                 }
01595 
01596                 return Html::element( 'input', $attribs );
01597         }
01598 }
01599 class HTMLTextAreaField extends HTMLFormField {
01600         function getCols() {
01601                 return isset( $this->mParams['cols'] )
01602                         ? $this->mParams['cols']
01603                         : 80;
01604         }
01605 
01606         function getRows() {
01607                 return isset( $this->mParams['rows'] )
01608                         ? $this->mParams['rows']
01609                         : 25;
01610         }
01611 
01612         function getInputHTML( $value ) {
01613                 $attribs = array(
01614                         'id' => $this->mID,
01615                         'name' => $this->mName,
01616                         'cols' => $this->getCols(),
01617                         'rows' => $this->getRows(),
01618                 ) + $this->getTooltipAndAccessKey();
01619 
01620                 if ( $this->mClass !== '' ) {
01621                         $attribs['class'] = $this->mClass;
01622                 }
01623 
01624                 if ( !empty( $this->mParams['disabled'] ) ) {
01625                         $attribs['disabled'] = 'disabled';
01626                 }
01627 
01628                 if ( !empty( $this->mParams['readonly'] ) ) {
01629                         $attribs['readonly'] = 'readonly';
01630                 }
01631 
01632                 if ( isset( $this->mParams['placeholder'] ) ) {
01633                         $attribs['placeholder'] = $this->mParams['placeholder'];
01634                 }
01635 
01636                 foreach ( array( 'required', 'autofocus' ) as $param ) {
01637                         if ( isset( $this->mParams[$param] ) ) {
01638                                 $attribs[$param] = '';
01639                         }
01640                 }
01641 
01642                 return Html::element( 'textarea', $attribs, $value );
01643         }
01644 }
01645 
01649 class HTMLFloatField extends HTMLTextField {
01650         function getSize() {
01651                 return isset( $this->mParams['size'] )
01652                         ? $this->mParams['size']
01653                         : 20;
01654         }
01655 
01656         function validate( $value, $alldata ) {
01657                 $p = parent::validate( $value, $alldata );
01658 
01659                 if ( $p !== true ) {
01660                         return $p;
01661                 }
01662 
01663                 $value = trim( $value );
01664 
01665                 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
01666                 # with the addition that a leading '+' sign is ok.
01667                 if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
01668                         return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
01669                 }
01670 
01671                 # The "int" part of these message names is rather confusing.
01672                 # They make equal sense for all numbers.
01673                 if ( isset( $this->mParams['min'] ) ) {
01674                         $min = $this->mParams['min'];
01675 
01676                         if ( $min > $value ) {
01677                                 return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
01678                         }
01679                 }
01680 
01681                 if ( isset( $this->mParams['max'] ) ) {
01682                         $max = $this->mParams['max'];
01683 
01684                         if ( $max < $value ) {
01685                                 return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
01686                         }
01687                 }
01688 
01689                 return true;
01690         }
01691 }
01692 
01696 class HTMLIntField extends HTMLFloatField {
01697         function validate( $value, $alldata ) {
01698                 $p = parent::validate( $value, $alldata );
01699 
01700                 if ( $p !== true ) {
01701                         return $p;
01702                 }
01703 
01704                 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
01705                 # with the addition that a leading '+' sign is ok. Note that leading zeros
01706                 # are fine, and will be left in the input, which is useful for things like
01707                 # phone numbers when you know that they are integers (the HTML5 type=tel
01708                 # input does not require its value to be numeric).  If you want a tidier
01709                 # value to, eg, save in the DB, clean it up with intval().
01710                 if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
01711                 ) {
01712                         return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
01713                 }
01714 
01715                 return true;
01716         }
01717 }
01718 
01722 class HTMLCheckField extends HTMLFormField {
01723         function getInputHTML( $value ) {
01724                 if ( !empty( $this->mParams['invert'] ) ) {
01725                         $value = !$value;
01726                 }
01727 
01728                 $attr = $this->getTooltipAndAccessKey();
01729                 $attr['id'] = $this->mID;
01730 
01731                 if ( !empty( $this->mParams['disabled'] ) ) {
01732                         $attr['disabled'] = 'disabled';
01733                 }
01734 
01735                 if ( $this->mClass !== '' ) {
01736                         $attr['class'] = $this->mClass;
01737                 }
01738 
01739                 return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
01740                         Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
01741         }
01742 
01748         function getLabel() {
01749                 return '&#160;';
01750         }
01751 
01756         function loadDataFromRequest( $request ) {
01757                 $invert = false;
01758                 if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
01759                         $invert = true;
01760                 }
01761 
01762                 // GetCheck won't work like we want for checks.
01763                 // Fetch the value in either one of the two following case:
01764                 // - we have a valid token (form got posted or GET forged by the user)
01765                 // - checkbox name has a value (false or true), ie is not null
01766                 if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
01767                         // XOR has the following truth table, which is what we want
01768                         // INVERT VALUE | OUTPUT
01769                         // true   true  | false
01770                         // false  true  | true
01771                         // false  false | false
01772                         // true   false | true
01773                         return $request->getBool( $this->mName ) xor $invert;
01774                 } else {
01775                         return $this->getDefault();
01776                 }
01777         }
01778 }
01779 
01783 class HTMLSelectField extends HTMLFormField {
01784         function validate( $value, $alldata ) {
01785                 $p = parent::validate( $value, $alldata );
01786 
01787                 if ( $p !== true ) {
01788                         return $p;
01789                 }
01790 
01791                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01792 
01793                 if ( in_array( $value, $validOptions ) )
01794                         return true;
01795                 else
01796                         return $this->msg( 'htmlform-select-badoption' )->parse();
01797         }
01798 
01799         function getInputHTML( $value ) {
01800                 $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
01801 
01802                 # If one of the options' 'name' is int(0), it is automatically selected.
01803                 # because PHP sucks and thinks int(0) == 'some string'.
01804                 # Working around this by forcing all of them to strings.
01805                 foreach ( $this->mParams['options'] as &$opt ) {
01806                         if ( is_int( $opt ) ) {
01807                                 $opt = strval( $opt );
01808                         }
01809                 }
01810                 unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
01811 
01812                 if ( !empty( $this->mParams['disabled'] ) ) {
01813                         $select->setAttribute( 'disabled', 'disabled' );
01814                 }
01815 
01816                 if ( $this->mClass !== '' ) {
01817                         $select->setAttribute( 'class', $this->mClass );
01818                 }
01819 
01820                 $select->addOptions( $this->mParams['options'] );
01821 
01822                 return $select->getHTML();
01823         }
01824 }
01825 
01829 class HTMLSelectOrOtherField extends HTMLTextField {
01830         static $jsAdded = false;
01831 
01832         function __construct( $params ) {
01833                 if ( !in_array( 'other', $params['options'], true ) ) {
01834                         $msg = isset( $params['other'] ) ?
01835                                 $params['other'] :
01836                                 wfMessage( 'htmlform-selectorother-other' )->text();
01837                         $params['options'][$msg] = 'other';
01838                 }
01839 
01840                 parent::__construct( $params );
01841         }
01842 
01843         static function forceToStringRecursive( $array ) {
01844                 if ( is_array( $array ) ) {
01845                         return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
01846                 } else {
01847                         return strval( $array );
01848                 }
01849         }
01850 
01851         function getInputHTML( $value ) {
01852                 $valInSelect = false;
01853 
01854                 if ( $value !== false ) {
01855                         $valInSelect = in_array(
01856                                 $value,
01857                                 HTMLFormField::flattenOptions( $this->mParams['options'] )
01858                         );
01859                 }
01860 
01861                 $selected = $valInSelect ? $value : 'other';
01862 
01863                 $opts = self::forceToStringRecursive( $this->mParams['options'] );
01864 
01865                 $select = new XmlSelect( $this->mName, $this->mID, $selected );
01866                 $select->addOptions( $opts );
01867 
01868                 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
01869 
01870                 $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
01871 
01872                 if ( !empty( $this->mParams['disabled'] ) ) {
01873                         $select->setAttribute( 'disabled', 'disabled' );
01874                         $tbAttribs['disabled'] = 'disabled';
01875                 }
01876 
01877                 $select = $select->getHTML();
01878 
01879                 if ( isset( $this->mParams['maxlength'] ) ) {
01880                         $tbAttribs['maxlength'] = $this->mParams['maxlength'];
01881                 }
01882 
01883                 if ( $this->mClass !== '' ) {
01884                         $tbAttribs['class'] = $this->mClass;
01885                 }
01886 
01887                 $textbox = Html::input(
01888                         $this->mName . '-other',
01889                         $valInSelect ? '' : $value,
01890                         'text',
01891                         $tbAttribs
01892                 );
01893 
01894                 return "$select<br />\n$textbox";
01895         }
01896 
01901         function loadDataFromRequest( $request ) {
01902                 if ( $request->getCheck( $this->mName ) ) {
01903                         $val = $request->getText( $this->mName );
01904 
01905                         if ( $val == 'other' ) {
01906                                 $val = $request->getText( $this->mName . '-other' );
01907                         }
01908 
01909                         return $val;
01910                 } else {
01911                         return $this->getDefault();
01912                 }
01913         }
01914 }
01915 
01919 class HTMLMultiSelectField extends HTMLFormField {
01920 
01921         function validate( $value, $alldata ) {
01922                 $p = parent::validate( $value, $alldata );
01923 
01924                 if ( $p !== true ) {
01925                         return $p;
01926                 }
01927 
01928                 if ( !is_array( $value ) ) {
01929                         return false;
01930                 }
01931 
01932                 # If all options are valid, array_intersect of the valid options
01933                 # and the provided options will return the provided options.
01934                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01935 
01936                 $validValues = array_intersect( $value, $validOptions );
01937                 if ( count( $validValues ) == count( $value ) ) {
01938                         return true;
01939                 } else {
01940                         return $this->msg( 'htmlform-select-badoption' )->parse();
01941                 }
01942         }
01943 
01944         function getInputHTML( $value ) {
01945                 $html = $this->formatOptions( $this->mParams['options'], $value );
01946 
01947                 return $html;
01948         }
01949 
01950         function formatOptions( $options, $value ) {
01951                 $html = '';
01952 
01953                 $attribs = array();
01954 
01955                 if ( !empty( $this->mParams['disabled'] ) ) {
01956                         $attribs['disabled'] = 'disabled';
01957                 }
01958 
01959                 foreach ( $options as $label => $info ) {
01960                         if ( is_array( $info ) ) {
01961                                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
01962                                 $html .= $this->formatOptions( $info, $value );
01963                         } else {
01964                                 $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
01965 
01966                                 $checkbox = Xml::check(
01967                                         $this->mName . '[]',
01968                                         in_array( $info, $value, true ),
01969                                         $attribs + $thisAttribs );
01970                                 $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
01971 
01972                                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
01973                         }
01974                 }
01975 
01976                 return $html;
01977         }
01978 
01983         function loadDataFromRequest( $request ) {
01984                 if ( $this->mParent->getMethod() == 'post' ) {
01985                         if ( $request->wasPosted() ) {
01986                                 # Checkboxes are just not added to the request arrays if they're not checked,
01987                                 # so it's perfectly possible for there not to be an entry at all
01988                                 return $request->getArray( $this->mName, array() );
01989                         } else {
01990                                 # That's ok, the user has not yet submitted the form, so show the defaults
01991                                 return $this->getDefault();
01992                         }
01993                 } else {
01994                         # This is the impossible case: if we look at $_GET and see no data for our
01995                         # field, is it because the user has not yet submitted the form, or that they
01996                         # have submitted it with all the options unchecked? We will have to assume the
01997                         # latter, which basically means that you can't specify 'positive' defaults
01998                         # for GET forms.
01999                         # @todo FIXME...
02000                         return $request->getArray( $this->mName, array() );
02001                 }
02002         }
02003 
02004         function getDefault() {
02005                 if ( isset( $this->mDefault ) ) {
02006                         return $this->mDefault;
02007                 } else {
02008                         return array();
02009                 }
02010         }
02011 
02012         protected function needsLabel() {
02013                 return false;
02014         }
02015 }
02016 
02027 class HTMLSelectAndOtherField extends HTMLSelectField {
02028 
02029         function __construct( $params ) {
02030                 if ( array_key_exists( 'other', $params ) ) {
02031                 } elseif ( array_key_exists( 'other-message', $params ) ) {
02032                         $params['other'] = wfMessage( $params['other-message'] )->plain();
02033                 } else {
02034                         $params['other'] = null;
02035                 }
02036 
02037                 if ( array_key_exists( 'options', $params ) ) {
02038                         # Options array already specified
02039                 } elseif ( array_key_exists( 'options-message', $params ) ) {
02040                         # Generate options array from a system message
02041                         $params['options'] = self::parseMessage(
02042                                 wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
02043                                 $params['other']
02044                         );
02045                 } else {
02046                         # Sulk
02047                         throw new MWException( 'HTMLSelectAndOtherField called without any options' );
02048                 }
02049                 $this->mFlatOptions = self::flattenOptions( $params['options'] );
02050 
02051                 parent::__construct( $params );
02052         }
02053 
02061         public static function parseMessage( $string, $otherName = null ) {
02062                 if ( $otherName === null ) {
02063                         $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
02064                 }
02065 
02066                 $optgroup = false;
02067                 $options = array( $otherName => 'other' );
02068 
02069                 foreach ( explode( "\n", $string ) as $option ) {
02070                         $value = trim( $option );
02071                         if ( $value == '' ) {
02072                                 continue;
02073                         } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
02074                                 # A new group is starting...
02075                                 $value = trim( substr( $value, 1 ) );
02076                                 $optgroup = $value;
02077                         } elseif ( substr( $value, 0, 2 ) == '**' ) {
02078                                 # groupmember
02079                                 $opt = trim( substr( $value, 2 ) );
02080                                 if ( $optgroup === false ) {
02081                                         $options[$opt] = $opt;
02082                                 } else {
02083                                         $options[$optgroup][$opt] = $opt;
02084                                 }
02085                         } else {
02086                                 # groupless reason list
02087                                 $optgroup = false;
02088                                 $options[$option] = $option;
02089                         }
02090                 }
02091 
02092                 return $options;
02093         }
02094 
02095         function getInputHTML( $value ) {
02096                 $select = parent::getInputHTML( $value[1] );
02097 
02098                 $textAttribs = array(
02099                         'id' => $this->mID . '-other',
02100                         'size' => $this->getSize(),
02101                 );
02102 
02103                 if ( $this->mClass !== '' ) {
02104                         $textAttribs['class'] = $this->mClass;
02105                 }
02106 
02107                 foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
02108                         if ( isset( $this->mParams[$param] ) ) {
02109                                 $textAttribs[$param] = '';
02110                         }
02111                 }
02112 
02113                 $textbox = Html::input(
02114                         $this->mName . '-other',
02115                         $value[2],
02116                         'text',
02117                         $textAttribs
02118                 );
02119 
02120                 return "$select<br />\n$textbox";
02121         }
02122 
02127         function loadDataFromRequest( $request ) {
02128                 if ( $request->getCheck( $this->mName ) ) {
02129 
02130                         $list = $request->getText( $this->mName );
02131                         $text = $request->getText( $this->mName . '-other' );
02132 
02133                         if ( $list == 'other' ) {
02134                                 $final = $text;
02135                         } elseif ( !in_array( $list, $this->mFlatOptions ) ) {
02136                                 # User has spoofed the select form to give an option which wasn't
02137                                 # in the original offer.  Sulk...
02138                                 $final = $text;
02139                         } elseif ( $text == '' ) {
02140                                 $final = $list;
02141                         } else {
02142                                 $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
02143                         }
02144 
02145                 } else {
02146                         $final = $this->getDefault();
02147 
02148                         $list = 'other';
02149                         $text = $final;
02150                         foreach ( $this->mFlatOptions as $option ) {
02151                                 $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
02152                                 if ( strpos( $text, $match ) === 0 ) {
02153                                         $list = $option;
02154                                         $text = substr( $text, strlen( $match ) );
02155                                         break;
02156                                 }
02157                         }
02158                 }
02159                 return array( $final, $list, $text );
02160         }
02161 
02162         function getSize() {
02163                 return isset( $this->mParams['size'] )
02164                         ? $this->mParams['size']
02165                         : 45;
02166         }
02167 
02168         function validate( $value, $alldata ) {
02169                 # HTMLSelectField forces $value to be one of the options in the select
02170                 # field, which is not useful here.  But we do want the validation further up
02171                 # the chain
02172                 $p = parent::validate( $value[1], $alldata );
02173 
02174                 if ( $p !== true ) {
02175                         return $p;
02176                 }
02177 
02178                 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) {
02179                         return $this->msg( 'htmlform-required' )->parse();
02180                 }
02181 
02182                 return true;
02183         }
02184 }
02185 
02189 class HTMLRadioField extends HTMLFormField {
02190 
02191 
02192         function validate( $value, $alldata ) {
02193                 $p = parent::validate( $value, $alldata );
02194 
02195                 if ( $p !== true ) {
02196                         return $p;
02197                 }
02198 
02199                 if ( !is_string( $value ) && !is_int( $value ) ) {
02200                         return false;
02201                 }
02202 
02203                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
02204 
02205                 if ( in_array( $value, $validOptions ) ) {
02206                         return true;
02207                 } else {
02208                         return $this->msg( 'htmlform-select-badoption' )->parse();
02209                 }
02210         }
02211 
02218         function getInputHTML( $value ) {
02219                 $html = $this->formatOptions( $this->mParams['options'], $value );
02220 
02221                 return $html;
02222         }
02223 
02224         function formatOptions( $options, $value ) {
02225                 $html = '';
02226 
02227                 $attribs = array();
02228                 if ( !empty( $this->mParams['disabled'] ) ) {
02229                         $attribs['disabled'] = 'disabled';
02230                 }
02231 
02232                 # TODO: should this produce an unordered list perhaps?
02233                 foreach ( $options as $label => $info ) {
02234                         if ( is_array( $info ) ) {
02235                                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
02236                                 $html .= $this->formatOptions( $info, $value );
02237                         } else {
02238                                 $id = Sanitizer::escapeId( $this->mID . "-$info" );
02239                                 $radio = Xml::radio(
02240                                         $this->mName,
02241                                         $info,
02242                                         $info == $value,
02243                                         $attribs + array( 'id' => $id )
02244                                 );
02245                                 $radio .= '&#160;' .
02246                                                 Html::rawElement( 'label', array( 'for' => $id ), $label );
02247 
02248                                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
02249                         }
02250                 }
02251 
02252                 return $html;
02253         }
02254 
02255         protected function needsLabel() {
02256                 return false;
02257         }
02258 }
02259 
02263 class HTMLInfoField extends HTMLFormField {
02264         public function __construct( $info ) {
02265                 $info['nodata'] = true;
02266 
02267                 parent::__construct( $info );
02268         }
02269 
02270         public function getInputHTML( $value ) {
02271                 return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
02272         }
02273 
02274         public function getTableRow( $value ) {
02275                 if ( !empty( $this->mParams['rawrow'] ) ) {
02276                         return $value;
02277                 }
02278 
02279                 return parent::getTableRow( $value );
02280         }
02281 
02285         public function getDiv( $value ) {
02286                 if ( !empty( $this->mParams['rawrow'] ) ) {
02287                         return $value;
02288                 }
02289 
02290                 return parent::getDiv( $value );
02291         }
02292 
02296         public function getRaw( $value ) {
02297                 if ( !empty( $this->mParams['rawrow'] ) ) {
02298                         return $value;
02299                 }
02300 
02301                 return parent::getRaw( $value );
02302         }
02303 
02304         protected function needsLabel() {
02305                 return false;
02306         }
02307 }
02308 
02309 class HTMLHiddenField extends HTMLFormField {
02310         public function __construct( $params ) {
02311                 parent::__construct( $params );
02312 
02313                 # Per HTML5 spec, hidden fields cannot be 'required'
02314                 # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
02315                 unset( $this->mParams['required'] );
02316         }
02317 
02318         public function getTableRow( $value ) {
02319                 $params = array();
02320                 if ( $this->mID ) {
02321                         $params['id'] = $this->mID;
02322                 }
02323 
02324                 $this->mParent->addHiddenField(
02325                         $this->mName,
02326                         $this->mDefault,
02327                         $params
02328                 );
02329 
02330                 return '';
02331         }
02332 
02336         public function getDiv( $value ) {
02337                 return $this->getTableRow( $value );
02338         }
02339 
02343         public function getRaw( $value ) {
02344                 return $this->getTableRow( $value );
02345         }
02346 
02347         public function getInputHTML( $value ) { return ''; }
02348 }
02349 
02354 class HTMLSubmitField extends HTMLFormField {
02355 
02356         public function __construct( $info ) {
02357                 $info['nodata'] = true;
02358                 parent::__construct( $info );
02359         }
02360 
02361         public function getInputHTML( $value ) {
02362                 return Xml::submitButton(
02363                         $value,
02364                         array(
02365                                 'class' => 'mw-htmlform-submit ' . $this->mClass,
02366                                 'name' => $this->mName,
02367                                 'id' => $this->mID,
02368                         )
02369                 );
02370         }
02371 
02372         protected function needsLabel() {
02373                 return false;
02374         }
02375 
02382         public function validate( $value, $alldata ) {
02383                 return true;
02384         }
02385 }
02386 
02387 class HTMLEditTools extends HTMLFormField {
02388         public function getInputHTML( $value ) {
02389                 return '';
02390         }
02391 
02392         public function getTableRow( $value ) {
02393                 $msg = $this->formatMsg();
02394 
02395                 return '<tr><td></td><td class="mw-input">'
02396                         . '<div class="mw-editTools">'
02397                         . $msg->parseAsBlock()
02398                         . "</div></td></tr>\n";
02399         }
02400 
02404         public function getDiv( $value ) {
02405                 $msg = $this->formatMsg();
02406                 return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
02407         }
02408 
02412         public function getRaw( $value ) {
02413                 return $this->getDiv( $value );
02414         }
02415 
02416         protected function formatMsg() {
02417                 if ( empty( $this->mParams['message'] ) ) {
02418                         $msg = $this->msg( 'edittools' );
02419                 } else {
02420                         $msg = $this->msg( $this->mParams['message'] );
02421                         if ( $msg->isDisabled() ) {
02422                                 $msg = $this->msg( 'edittools' );
02423                         }
02424                 }
02425                 $msg->inContentLanguage();
02426                 return $msg;
02427         }
02428 }
02429 
02430 class HTMLApiField extends HTMLFormField {
02431         public function getTableRow( $value ) {
02432                 return '';
02433         }
02434 
02435         public function getDiv( $value ) {
02436                 return $this->getTableRow( $value );
02437         }
02438 
02439         public function getRaw( $value ) {
02440                 return $this->getTableRow( $value );
02441         }
02442 
02443         public function getInputHTML( $value ) {
02444                 return '';
02445         }
02446 }