MediaWiki  master
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                 '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 
00115                 // HTMLTextField will output the correct type="" attribute automagically.
00116                 // There are about four zillion other HTML5 input types, like url, but
00117                 // we don't use those at the moment, so no point in adding all of them.
00118                 'email' => 'HTMLTextField',
00119                 'password' => 'HTMLTextField',
00120         );
00121 
00122         protected $mMessagePrefix;
00123 
00125         protected $mFlatFields;
00126 
00127         protected $mFieldTree;
00128         protected $mShowReset = false;
00129         public $mFieldData;
00130 
00131         protected $mSubmitCallback;
00132         protected $mValidationErrorMessage;
00133 
00134         protected $mPre = '';
00135         protected $mHeader = '';
00136         protected $mFooter = '';
00137         protected $mSectionHeaders = array();
00138         protected $mSectionFooters = array();
00139         protected $mPost = '';
00140         protected $mId;
00141 
00142         protected $mSubmitID;
00143         protected $mSubmitName;
00144         protected $mSubmitText;
00145         protected $mSubmitTooltip;
00146 
00147         protected $mTitle;
00148         protected $mMethod = 'post';
00149 
00155         protected $mAction = false;
00156 
00157         protected $mUseMultipart = false;
00158         protected $mHiddenFields = array();
00159         protected $mButtons = array();
00160 
00161         protected $mWrapperLegend = false;
00162 
00170         protected $mSubSectionBeforeFields = true;
00171 
00177         protected $displayFormat = 'table';
00178 
00183         protected $availableDisplayFormats = array(
00184                 'table',
00185                 'div',
00186                 'raw',
00187         );
00188 
00196         public function __construct( $descriptor, /*IContextSource*/ $context = null, $messagePrefix = '' ) {
00197                 if ( $context instanceof IContextSource ) {
00198                         $this->setContext( $context );
00199                         $this->mTitle = false; // We don't need them to set a title
00200                         $this->mMessagePrefix = $messagePrefix;
00201                 } else {
00202                         // B/C since 1.18
00203                         if ( is_string( $context ) && $messagePrefix === '' ) {
00204                                 // it's actually $messagePrefix
00205                                 $this->mMessagePrefix = $context;
00206                         }
00207                 }
00208 
00209                 // Expand out into a tree.
00210                 $loadedDescriptor = array();
00211                 $this->mFlatFields = array();
00212 
00213                 foreach ( $descriptor as $fieldname => $info ) {
00214                         $section = isset( $info['section'] )
00215                                 ? $info['section']
00216                                 : '';
00217 
00218                         if ( isset( $info['type'] ) && $info['type'] == 'file' ) {
00219                                 $this->mUseMultipart = true;
00220                         }
00221 
00222                         $field = self::loadInputFromParameters( $fieldname, $info );
00223                         $field->mParent = $this;
00224 
00225                         $setSection =& $loadedDescriptor;
00226                         if ( $section ) {
00227                                 $sectionParts = explode( '/', $section );
00228 
00229                                 while ( count( $sectionParts ) ) {
00230                                         $newName = array_shift( $sectionParts );
00231 
00232                                         if ( !isset( $setSection[$newName] ) ) {
00233                                                 $setSection[$newName] = array();
00234                                         }
00235 
00236                                         $setSection =& $setSection[$newName];
00237                                 }
00238                         }
00239 
00240                         $setSection[$fieldname] = $field;
00241                         $this->mFlatFields[$fieldname] = $field;
00242                 }
00243 
00244                 $this->mFieldTree = $loadedDescriptor;
00245         }
00246 
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 
00286         static function loadInputFromParameters( $fieldname, $descriptor ) {
00287                 if ( isset( $descriptor['class'] ) ) {
00288                         $class = $descriptor['class'];
00289                 } elseif ( isset( $descriptor['type'] ) ) {
00290                         $class = self::$typeMappings[$descriptor['type']];
00291                         $descriptor['class'] = $class;
00292                 } else {
00293                         $class = null;
00294                 }
00295 
00296                 if ( !$class ) {
00297                         throw new MWException( "Descriptor with no class: " . print_r( $descriptor, true ) );
00298                 }
00299 
00300                 $descriptor['fieldname'] = $fieldname;
00301 
00302                 # TODO
00303                 # This will throw a fatal error whenever someone try to use
00304                 # 'class' to feed a CSS class instead of 'cssclass'. Would be
00305                 # great to avoid the fatal error and show a nice error.
00306                 $obj = new $class( $descriptor );
00307 
00308                 return $obj;
00309         }
00310 
00320         function prepareForm() {
00321                 # Check if we have the info we need
00322                 if ( !$this->mTitle instanceof Title && $this->mTitle !== false ) {
00323                         throw new MWException( "You must call setTitle() on an HTMLForm" );
00324                 }
00325 
00326                 # Load data from the request.
00327                 $this->loadData();
00328                 return $this;
00329         }
00330 
00335         function tryAuthorizedSubmit() {
00336                 $result = false;
00337 
00338                 $submit = false;
00339                 if ( $this->getMethod() != 'post' ) {
00340                         $submit = true; // no session check needed
00341                 } elseif ( $this->getRequest()->wasPosted() ) {
00342                         $editToken = $this->getRequest()->getVal( 'wpEditToken' );
00343                         if ( $this->getUser()->isLoggedIn() || $editToken != null ) {
00344                                 // Session tokens for logged-out users have no security value.
00345                                 // However, if the user gave one, check it in order to give a nice
00346                                 // "session expired" error instead of "permission denied" or such.
00347                                 $submit = $this->getUser()->matchEditToken( $editToken );
00348                         } else {
00349                                 $submit = true;
00350                         }
00351                 }
00352 
00353                 if ( $submit ) {
00354                         $result = $this->trySubmit();
00355                 }
00356 
00357                 return $result;
00358         }
00359 
00366         function show() {
00367                 $this->prepareForm();
00368 
00369                 $result = $this->tryAuthorizedSubmit();
00370                 if ( $result === true || ( $result instanceof Status && $result->isGood() ) ) {
00371                         return $result;
00372                 }
00373 
00374                 $this->displayForm( $result );
00375                 return false;
00376         }
00377 
00386         function trySubmit() {
00387                 # Check for validation
00388                 foreach ( $this->mFlatFields as $fieldname => $field ) {
00389                         if ( !empty( $field->mParams['nodata'] ) ) {
00390                                 continue;
00391                         }
00392                         if ( $field->validate(
00393                                         $this->mFieldData[$fieldname],
00394                                         $this->mFieldData )
00395                                 !== true
00396                         ) {
00397                                 return isset( $this->mValidationErrorMessage )
00398                                         ? $this->mValidationErrorMessage
00399                                         : array( 'htmlform-invalid-input' );
00400                         }
00401                 }
00402 
00403                 $callback = $this->mSubmitCallback;
00404                 if ( !is_callable( $callback ) ) {
00405                         throw new MWException( 'HTMLForm: no submit callback provided. Use setSubmitCallback() to set one.' );
00406                 }
00407 
00408                 $data = $this->filterDataForSubmit( $this->mFieldData );
00409 
00410                 $res = call_user_func( $callback, $data, $this );
00411 
00412                 return $res;
00413         }
00414 
00424         function setSubmitCallback( $cb ) {
00425                 $this->mSubmitCallback = $cb;
00426                 return $this;
00427         }
00428 
00435         function setValidationErrorMessage( $msg ) {
00436                 $this->mValidationErrorMessage = $msg;
00437                 return $this;
00438         }
00439 
00445         function setIntro( $msg ) {
00446                 $this->setPreText( $msg );
00447                 return $this;
00448         }
00449 
00456         function setPreText( $msg ) {
00457                 $this->mPre = $msg;
00458                 return $this;
00459         }
00460 
00466         function addPreText( $msg ) {
00467                 $this->mPre .= $msg;
00468                 return $this;
00469         }
00470 
00477         function addHeaderText( $msg, $section = null ) {
00478                 if ( is_null( $section ) ) {
00479                         $this->mHeader .= $msg;
00480                 } else {
00481                         if ( !isset( $this->mSectionHeaders[$section] ) ) {
00482                                 $this->mSectionHeaders[$section] = '';
00483                         }
00484                         $this->mSectionHeaders[$section] .= $msg;
00485                 }
00486                 return $this;
00487         }
00488 
00496         function setHeaderText( $msg, $section = null ) {
00497                 if ( is_null( $section ) ) {
00498                         $this->mHeader = $msg;
00499                 } else {
00500                         $this->mSectionHeaders[$section] = $msg;
00501                 }
00502                 return $this;
00503         }
00504 
00511         function addFooterText( $msg, $section = null ) {
00512                 if ( is_null( $section ) ) {
00513                         $this->mFooter .= $msg;
00514                 } else {
00515                         if ( !isset( $this->mSectionFooters[$section] ) ) {
00516                                 $this->mSectionFooters[$section] = '';
00517                         }
00518                         $this->mSectionFooters[$section] .= $msg;
00519                 }
00520                 return $this;
00521         }
00522 
00530         function setFooterText( $msg, $section = null ) {
00531                 if ( is_null( $section ) ) {
00532                         $this->mFooter = $msg;
00533                 } else {
00534                         $this->mSectionFooters[$section] = $msg;
00535                 }
00536                 return $this;
00537         }
00538 
00544         function addPostText( $msg ) {
00545                 $this->mPost .= $msg;
00546                 return $this;
00547         }
00548 
00554         function setPostText( $msg ) {
00555                 $this->mPost = $msg;
00556                 return $this;
00557         }
00558 
00566         public function addHiddenField( $name, $value, $attribs = array() ) {
00567                 $attribs += array( 'name' => $name );
00568                 $this->mHiddenFields[] = array( $value, $attribs );
00569                 return $this;
00570         }
00571 
00580         public function addButton( $name, $value, $id = null, $attribs = null ) {
00581                 $this->mButtons[] = compact( 'name', 'value', 'id', 'attribs' );
00582                 return $this;
00583         }
00584 
00596         function displayForm( $submitResult ) {
00597                 $this->getOutput()->addHTML( $this->getHTML( $submitResult ) );
00598         }
00599 
00605         function getHTML( $submitResult ) {
00606                 # For good measure (it is the default)
00607                 $this->getOutput()->preventClickjacking();
00608                 $this->getOutput()->addModules( 'mediawiki.htmlform' );
00609 
00610                 $html = ''
00611                         . $this->getErrors( $submitResult )
00612                         . $this->mHeader
00613                         . $this->getBody()
00614                         . $this->getHiddenFields()
00615                         . $this->getButtons()
00616                         . $this->mFooter
00617                 ;
00618 
00619                 $html = $this->wrapForm( $html );
00620 
00621                 return '' . $this->mPre . $html . $this->mPost;
00622         }
00623 
00629         function wrapForm( $html ) {
00630 
00631                 # Include a <fieldset> wrapper for style, if requested.
00632                 if ( $this->mWrapperLegend !== false ) {
00633                         $html = Xml::fieldset( $this->mWrapperLegend, $html );
00634                 }
00635                 # Use multipart/form-data
00636                 $encType = $this->mUseMultipart
00637                         ? 'multipart/form-data'
00638                         : 'application/x-www-form-urlencoded';
00639                 # Attributes
00640                 $attribs = array(
00641                         'action'  => $this->mAction === false ? $this->getTitle()->getFullURL() : $this->mAction,
00642                         'method'  => $this->mMethod,
00643                         'class'   => 'visualClear',
00644                         'enctype' => $encType,
00645                 );
00646                 if ( !empty( $this->mId ) ) {
00647                         $attribs['id'] = $this->mId;
00648                 }
00649 
00650                 return Html::rawElement( 'form', $attribs, $html );
00651         }
00652 
00657         function getHiddenFields() {
00658                 global $wgArticlePath;
00659 
00660                 $html = '';
00661                 if ( $this->getMethod() == 'post' ) {
00662                         $html .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken(), array( 'id' => 'wpEditToken' ) ) . "\n";
00663                         $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00664                 }
00665 
00666                 if ( strpos( $wgArticlePath, '?' ) !== false && $this->getMethod() == 'get' ) {
00667                         $html .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
00668                 }
00669 
00670                 foreach ( $this->mHiddenFields as $data ) {
00671                         list( $value, $attribs ) = $data;
00672                         $html .= Html::hidden( $attribs['name'], $value, $attribs ) . "\n";
00673                 }
00674 
00675                 return $html;
00676         }
00677 
00682         function getButtons() {
00683                 $html = '';
00684                 $attribs = array();
00685 
00686                 if ( isset( $this->mSubmitID ) ) {
00687                         $attribs['id'] = $this->mSubmitID;
00688                 }
00689 
00690                 if ( isset( $this->mSubmitName ) ) {
00691                         $attribs['name'] = $this->mSubmitName;
00692                 }
00693 
00694                 if ( isset( $this->mSubmitTooltip ) ) {
00695                         $attribs += Linker::tooltipAndAccesskeyAttribs( $this->mSubmitTooltip );
00696                 }
00697 
00698                 $attribs['class'] = 'mw-htmlform-submit';
00699 
00700                 $html .= Xml::submitButton( $this->getSubmitText(), $attribs ) . "\n";
00701 
00702                 if ( $this->mShowReset ) {
00703                         $html .= Html::element(
00704                                 'input',
00705                                 array(
00706                                         'type' => 'reset',
00707                                         'value' => $this->msg( 'htmlform-reset' )->text()
00708                                 )
00709                         ) . "\n";
00710                 }
00711 
00712                 foreach ( $this->mButtons as $button ) {
00713                         $attrs = array(
00714                                 'type'  => 'submit',
00715                                 'name'  => $button['name'],
00716                                 'value' => $button['value']
00717                         );
00718 
00719                         if ( $button['attribs'] ) {
00720                                 $attrs += $button['attribs'];
00721                         }
00722 
00723                         if ( isset( $button['id'] ) ) {
00724                                 $attrs['id'] = $button['id'];
00725                         }
00726 
00727                         $html .= Html::element( 'input', $attrs );
00728                 }
00729 
00730                 return $html;
00731         }
00732 
00737         function getBody() {
00738                 return $this->displaySection( $this->mFieldTree );
00739         }
00740 
00746         function getErrors( $errors ) {
00747                 if ( $errors instanceof Status ) {
00748                         if ( $errors->isOK() ) {
00749                                 $errorstr = '';
00750                         } else {
00751                                 $errorstr = $this->getOutput()->parse( $errors->getWikiText() );
00752                         }
00753                 } elseif ( is_array( $errors ) ) {
00754                         $errorstr = $this->formatErrors( $errors );
00755                 } else {
00756                         $errorstr = $errors;
00757                 }
00758 
00759                 return $errorstr
00760                         ? Html::rawElement( 'div', array( 'class' => 'error' ), $errorstr )
00761                         : '';
00762         }
00763 
00769         public static function formatErrors( $errors ) {
00770                 $errorstr = '';
00771 
00772                 foreach ( $errors as $error ) {
00773                         if ( is_array( $error ) ) {
00774                                 $msg = array_shift( $error );
00775                         } else {
00776                                 $msg = $error;
00777                                 $error = array();
00778                         }
00779 
00780                         $errorstr .= Html::rawElement(
00781                                 'li',
00782                                 array(),
00783                                 wfMessage( $msg, $error )->parse()
00784                         );
00785                 }
00786 
00787                 $errorstr = Html::rawElement( 'ul', array(), $errorstr );
00788 
00789                 return $errorstr;
00790         }
00791 
00797         function setSubmitText( $t ) {
00798                 $this->mSubmitText = $t;
00799                 return $this;
00800         }
00801 
00808         public function setSubmitTextMsg( $msg ) {
00809                 $this->setSubmitText( $this->msg( $msg )->text() );
00810                 return $this;
00811         }
00812 
00817         function getSubmitText() {
00818                 return $this->mSubmitText
00819                         ? $this->mSubmitText
00820                         : $this->msg( 'htmlform-submit' )->text();
00821         }
00822 
00827         public function setSubmitName( $name ) {
00828                 $this->mSubmitName = $name;
00829                 return $this;
00830         }
00831 
00836         public function setSubmitTooltip( $name ) {
00837                 $this->mSubmitTooltip = $name;
00838                 return $this;
00839         }
00840 
00847         function setSubmitID( $t ) {
00848                 $this->mSubmitID = $t;
00849                 return $this;
00850         }
00851 
00856         public function setId( $id ) {
00857                 $this->mId = $id;
00858                 return $this;
00859         }
00867         public function setWrapperLegend( $legend ) {
00868                 $this->mWrapperLegend = $legend;
00869                 return $this;
00870         }
00871 
00879         public function setWrapperLegendMsg( $msg ) {
00880                 $this->setWrapperLegend( $this->msg( $msg )->text() );
00881                 return $this;
00882         }
00883 
00891         function setMessagePrefix( $p ) {
00892                 $this->mMessagePrefix = $p;
00893                 return $this;
00894         }
00895 
00901         function setTitle( $t ) {
00902                 $this->mTitle = $t;
00903                 return $this;
00904         }
00905 
00910         function getTitle() {
00911                 return $this->mTitle === false
00912                         ? $this->getContext()->getTitle()
00913                         : $this->mTitle;
00914         }
00915 
00921         public function setMethod( $method = 'post' ) {
00922                 $this->mMethod = $method;
00923                 return $this;
00924         }
00925 
00926         public function getMethod() {
00927                 return $this->mMethod;
00928         }
00929 
00937         public function displaySection( $fields, $sectionName = '', $fieldsetIDPrefix = '' ) {
00938                 $displayFormat = $this->getDisplayFormat();
00939 
00940                 $html = '';
00941                 $subsectionHtml = '';
00942                 $hasLabel = false;
00943 
00944                 $getFieldHtmlMethod = ( $displayFormat == 'table' ) ? 'getTableRow' : 'get' . ucfirst( $displayFormat );
00945 
00946                 foreach ( $fields as $key => $value ) {
00947                         if ( $value instanceof HTMLFormField ) {
00948                                 $v = empty( $value->mParams['nodata'] )
00949                                         ? $this->mFieldData[$key]
00950                                         : $value->getDefault();
00951                                 $html .= $value->$getFieldHtmlMethod( $v );
00952 
00953                                 $labelValue = trim( $value->getLabel() );
00954                                 if ( $labelValue != '&#160;' && $labelValue !== '' ) {
00955                                         $hasLabel = true;
00956                                 }
00957                         } elseif ( is_array( $value ) ) {
00958                                 $section = $this->displaySection( $value, $key );
00959                                 $legend = $this->getLegend( $key );
00960                                 if ( isset( $this->mSectionHeaders[$key] ) ) {
00961                                         $section = $this->mSectionHeaders[$key] . $section;
00962                                 }
00963                                 if ( isset( $this->mSectionFooters[$key] ) ) {
00964                                         $section .= $this->mSectionFooters[$key];
00965                                 }
00966                                 $attributes = array();
00967                                 if ( $fieldsetIDPrefix ) {
00968                                         $attributes['id'] = Sanitizer::escapeId( "$fieldsetIDPrefix$key" );
00969                                 }
00970                                 $subsectionHtml .= Xml::fieldset( $legend, $section, $attributes ) . "\n";
00971                         }
00972                 }
00973 
00974                 if ( $displayFormat !== 'raw' ) {
00975                         $classes = array();
00976 
00977                         if ( !$hasLabel ) { // Avoid strange spacing when no labels exist
00978                                 $classes[] = 'mw-htmlform-nolabel';
00979                         }
00980 
00981                         $attribs = array(
00982                                 'class' => implode( ' ', $classes ),
00983                         );
00984 
00985                         if ( $sectionName ) {
00986                                 $attribs['id'] = Sanitizer::escapeId( "mw-htmlform-$sectionName" );
00987                         }
00988 
00989                         if ( $displayFormat === 'table' ) {
00990                                 $html = Html::rawElement( 'table', $attribs,
00991                                         Html::rawElement( 'tbody', array(), "\n$html\n" ) ) . "\n";
00992                         } elseif ( $displayFormat === 'div' ) {
00993                                 $html = Html::rawElement( 'div', $attribs, "\n$html\n" );
00994                         }
00995                 }
00996 
00997                 if ( $this->mSubSectionBeforeFields ) {
00998                         return $subsectionHtml . "\n" . $html;
00999                 } else {
01000                         return $html . "\n" . $subsectionHtml;
01001                 }
01002         }
01003 
01007         function loadData() {
01008                 $fieldData = array();
01009 
01010                 foreach ( $this->mFlatFields as $fieldname => $field ) {
01011                         if ( !empty( $field->mParams['nodata'] ) ) {
01012                                 continue;
01013                         } elseif ( !empty( $field->mParams['disabled'] ) ) {
01014                                 $fieldData[$fieldname] = $field->getDefault();
01015                         } else {
01016                                 $fieldData[$fieldname] = $field->loadDataFromRequest( $this->getRequest() );
01017                         }
01018                 }
01019 
01020                 # Filter data.
01021                 foreach ( $fieldData as $name => &$value ) {
01022                         $field = $this->mFlatFields[$name];
01023                         $value = $field->filter( $value, $this->mFlatFields );
01024                 }
01025 
01026                 $this->mFieldData = $fieldData;
01027         }
01028 
01035         function suppressReset( $suppressReset = true ) {
01036                 $this->mShowReset = !$suppressReset;
01037                 return $this;
01038         }
01039 
01047         function filterDataForSubmit( $data ) {
01048                 return $data;
01049         }
01050 
01057         public function getLegend( $key ) {
01058                 return $this->msg( "{$this->mMessagePrefix}-$key" )->text();
01059         }
01060 
01070         public function setAction( $action ) {
01071                 $this->mAction = $action;
01072                 return $this;
01073         }
01074 
01075 }
01076 
01081 abstract class HTMLFormField {
01082 
01083         protected $mValidationCallback;
01084         protected $mFilterCallback;
01085         protected $mName;
01086         public $mParams;
01087         protected $mLabel;      # String label.  Set on construction
01088         protected $mID;
01089         protected $mClass = '';
01090         protected $mDefault;
01091 
01095         public $mParent;
01096 
01105         abstract function getInputHTML( $value );
01106 
01117         function msg() {
01118                 $args = func_get_args();
01119 
01120                 if ( $this->mParent ) {
01121                         $callback = array( $this->mParent, 'msg' );
01122                 } else {
01123                         $callback = 'wfMessage';
01124                 }
01125 
01126                 return call_user_func_array( $callback, $args );
01127         }
01128 
01137         function validate( $value, $alldata ) {
01138                 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value === '' ) {
01139                         return $this->msg( 'htmlform-required' )->parse();
01140                 }
01141 
01142                 if ( isset( $this->mValidationCallback ) ) {
01143                         return call_user_func( $this->mValidationCallback, $value, $alldata, $this->mParent );
01144                 }
01145 
01146                 return true;
01147         }
01148 
01149         function filter( $value, $alldata ) {
01150                 if ( isset( $this->mFilterCallback ) ) {
01151                         $value = call_user_func( $this->mFilterCallback, $value, $alldata, $this->mParent );
01152                 }
01153 
01154                 return $value;
01155         }
01156 
01163         protected function needsLabel() {
01164                 return true;
01165         }
01166 
01173         function loadDataFromRequest( $request ) {
01174                 if ( $request->getCheck( $this->mName ) ) {
01175                         return $request->getText( $this->mName );
01176                 } else {
01177                         return $this->getDefault();
01178                 }
01179         }
01180 
01186         function __construct( $params ) {
01187                 $this->mParams = $params;
01188 
01189                 # Generate the label from a message, if possible
01190                 if ( isset( $params['label-message'] ) ) {
01191                         $msgInfo = $params['label-message'];
01192 
01193                         if ( is_array( $msgInfo ) ) {
01194                                 $msg = array_shift( $msgInfo );
01195                         } else {
01196                                 $msg = $msgInfo;
01197                                 $msgInfo = array();
01198                         }
01199 
01200                         $this->mLabel = wfMessage( $msg, $msgInfo )->parse();
01201                 } elseif ( isset( $params['label'] ) ) {
01202                         $this->mLabel = $params['label'];
01203                 }
01204 
01205                 $this->mName = "wp{$params['fieldname']}";
01206                 if ( isset( $params['name'] ) ) {
01207                         $this->mName = $params['name'];
01208                 }
01209 
01210                 $validName = Sanitizer::escapeId( $this->mName );
01211                 if ( $this->mName != $validName && !isset( $params['nodata'] ) ) {
01212                         throw new MWException( "Invalid name '{$this->mName}' passed to " . __METHOD__ );
01213                 }
01214 
01215                 $this->mID = "mw-input-{$this->mName}";
01216 
01217                 if ( isset( $params['default'] ) ) {
01218                         $this->mDefault = $params['default'];
01219                 }
01220 
01221                 if ( isset( $params['id'] ) ) {
01222                         $id = $params['id'];
01223                         $validId = Sanitizer::escapeId( $id );
01224 
01225                         if ( $id != $validId ) {
01226                                 throw new MWException( "Invalid id '$id' passed to " . __METHOD__ );
01227                         }
01228 
01229                         $this->mID = $id;
01230                 }
01231 
01232                 if ( isset( $params['cssclass'] ) ) {
01233                         $this->mClass = $params['cssclass'];
01234                 }
01235 
01236                 if ( isset( $params['validation-callback'] ) ) {
01237                         $this->mValidationCallback = $params['validation-callback'];
01238                 }
01239 
01240                 if ( isset( $params['filter-callback'] ) ) {
01241                         $this->mFilterCallback = $params['filter-callback'];
01242                 }
01243 
01244                 if ( isset( $params['flatlist'] ) ) {
01245                         $this->mClass .= ' mw-htmlform-flatlist';
01246                 }
01247         }
01248 
01255         function getTableRow( $value ) {
01256                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01257                 $inputHtml = $this->getInputHTML( $value );
01258                 $fieldType = get_class( $this );
01259                 $helptext = $this->getHelpTextHtmlTable( $this->getHelpText() );
01260                 $cellAttributes = array();
01261 
01262                 if ( !empty( $this->mParams['vertical-label'] ) ) {
01263                         $cellAttributes['colspan'] = 2;
01264                         $verticalLabel = true;
01265                 } else {
01266                         $verticalLabel = false;
01267                 }
01268 
01269                 $label = $this->getLabelHtml( $cellAttributes );
01270 
01271                 $field = Html::rawElement(
01272                         'td',
01273                         array( 'class' => 'mw-input' ) + $cellAttributes,
01274                         $inputHtml . "\n$errors"
01275                 );
01276 
01277                 if ( $verticalLabel ) {
01278                         $html = Html::rawElement( 'tr',
01279                                 array( 'class' => 'mw-htmlform-vertical-label' ), $label );
01280                         $html .= Html::rawElement( 'tr',
01281                                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01282                                 $field );
01283                 } else {
01284                         $html = Html::rawElement( 'tr',
01285                                 array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01286                                 $label . $field );
01287                 }
01288 
01289                 return $html . $helptext;
01290         }
01291 
01299         public function getDiv( $value ) {
01300                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01301                 $inputHtml = $this->getInputHTML( $value );
01302                 $fieldType = get_class( $this );
01303                 $helptext = $this->getHelpTextHtmlDiv( $this->getHelpText() );
01304                 $cellAttributes = array();
01305                 $label = $this->getLabelHtml( $cellAttributes );
01306 
01307                 $field = Html::rawElement(
01308                         'div',
01309                         array( 'class' => 'mw-input' ) + $cellAttributes,
01310                         $inputHtml . "\n$errors"
01311                 );
01312                 $html = Html::rawElement( 'div',
01313                         array( 'class' => "mw-htmlform-field-$fieldType {$this->mClass} $errorClass" ),
01314                         $label . $field );
01315                 $html .= $helptext;
01316                 return $html;
01317         }
01318 
01326         public function getRaw( $value ) {
01327                 list( $errors, $errorClass ) = $this->getErrorsAndErrorClass( $value );
01328                 $inputHtml = $this->getInputHTML( $value );
01329                 $helptext = $this->getHelpTextHtmlRaw( $this->getHelpText() );
01330                 $cellAttributes = array();
01331                 $label = $this->getLabelHtml( $cellAttributes );
01332 
01333                 $html = "\n$errors";
01334                 $html .= $label;
01335                 $html .= $inputHtml;
01336                 $html .= $helptext;
01337                 return $html;
01338         }
01339 
01346         public function getHelpTextHtmlTable( $helptext ) {
01347                 if ( is_null( $helptext ) ) {
01348                         return '';
01349                 }
01350 
01351                 $row = Html::rawElement(
01352                         'td',
01353                         array( 'colspan' => 2, 'class' => 'htmlform-tip' ),
01354                         $helptext
01355                 );
01356                 $row = Html::rawElement( 'tr', array(), $row );
01357                 return $row;
01358         }
01359 
01366         public function getHelpTextHtmlDiv( $helptext ) {
01367                 if ( is_null( $helptext ) ) {
01368                         return '';
01369                 }
01370 
01371                 $div = Html::rawElement( 'div', array( 'class' => 'htmlform-tip' ), $helptext );
01372                 return $div;
01373         }
01374 
01381         public function getHelpTextHtmlRaw( $helptext ) {
01382                 return $this->getHelpTextHtmlDiv( $helptext );
01383         }
01384 
01390         public function getHelpText() {
01391                 $helptext = null;
01392 
01393                 if ( isset( $this->mParams['help-message'] ) ) {
01394                         $this->mParams['help-messages'] = array( $this->mParams['help-message'] );
01395                 }
01396 
01397                 if ( isset( $this->mParams['help-messages'] ) ) {
01398                         foreach ( $this->mParams['help-messages'] as $name ) {
01399                                 $helpMessage = (array)$name;
01400                                 $msg = $this->msg( array_shift( $helpMessage ), $helpMessage );
01401 
01402                                 if ( $msg->exists() ) {
01403                                         if ( is_null( $helptext ) ) {
01404                                                 $helptext = '';
01405                                         } else {
01406                                                 $helptext .= $this->msg( 'word-separator' )->escaped(); // some space
01407                                         }
01408                                         $helptext .= $msg->parse(); // Append message
01409                                 }
01410                         }
01411                 }
01412                 elseif ( isset( $this->mParams['help'] ) ) {
01413                         $helptext = $this->mParams['help'];
01414                 }
01415                 return $helptext;
01416         }
01417 
01424         public function getErrorsAndErrorClass( $value ) {
01425                 $errors = $this->validate( $value, $this->mParent->mFieldData );
01426 
01427                 if ( $errors === true || ( !$this->mParent->getRequest()->wasPosted() && ( $this->mParent->getMethod() == 'post' ) ) ) {
01428                         $errors = '';
01429                         $errorClass = '';
01430                 } else {
01431                         $errors = self::formatErrors( $errors );
01432                         $errorClass = 'mw-htmlform-invalid-input';
01433                 }
01434                 return array( $errors, $errorClass );
01435         }
01436 
01437         function getLabel() {
01438                 return $this->mLabel;
01439         }
01440 
01441         function getLabelHtml( $cellAttributes = array() ) {
01442                 # Don't output a for= attribute for labels with no associated input.
01443                 # Kind of hacky here, possibly we don't want these to be <label>s at all.
01444                 $for = array();
01445 
01446                 if ( $this->needsLabel() ) {
01447                         $for['for'] = $this->mID;
01448                 }
01449 
01450                 $displayFormat = $this->mParent->getDisplayFormat();
01451                 $labelElement = Html::rawElement( 'label', $for, $this->getLabel() );
01452 
01453                 if ( $displayFormat == 'table' ) {
01454                         return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes,
01455                                 Html::rawElement( 'label', $for, $this->getLabel() )
01456                         );
01457                 } elseif ( $displayFormat == 'div' ) {
01458                         return Html::rawElement( 'div', array( 'class' => 'mw-label' ) + $cellAttributes,
01459                                 Html::rawElement( 'label', $for, $this->getLabel() )
01460                         );
01461                 } else {
01462                         return $labelElement;
01463                 }
01464         }
01465 
01466         function getDefault() {
01467                 if ( isset( $this->mDefault ) ) {
01468                         return $this->mDefault;
01469                 } else {
01470                         return null;
01471                 }
01472         }
01473 
01479         public function getTooltipAndAccessKey() {
01480                 if ( empty( $this->mParams['tooltip'] ) ) {
01481                         return array();
01482                 }
01483                 return Linker::tooltipAndAccesskeyAttribs( $this->mParams['tooltip'] );
01484         }
01485 
01493         public static function flattenOptions( $options ) {
01494                 $flatOpts = array();
01495 
01496                 foreach ( $options as $value ) {
01497                         if ( is_array( $value ) ) {
01498                                 $flatOpts = array_merge( $flatOpts, self::flattenOptions( $value ) );
01499                         } else {
01500                                 $flatOpts[] = $value;
01501                         }
01502                 }
01503 
01504                 return $flatOpts;
01505         }
01506 
01513         protected static function formatErrors( $errors ) {
01514                 if ( is_array( $errors ) && count( $errors ) === 1 ) {
01515                         $errors = array_shift( $errors );
01516                 }
01517 
01518                 if ( is_array( $errors ) ) {
01519                         $lines = array();
01520                         foreach ( $errors as $error ) {
01521                                 if ( $error instanceof Message ) {
01522                                         $lines[] = Html::rawElement( 'li', array(), $error->parse() );
01523                                 } else {
01524                                         $lines[] = Html::rawElement( 'li', array(), $error );
01525                                 }
01526                         }
01527                         return Html::rawElement( 'ul', array( 'class' => 'error' ), implode( "\n", $lines ) );
01528                 } else {
01529                         if ( $errors instanceof Message ) {
01530                                 $errors = $errors->parse();
01531                         }
01532                         return Html::rawElement( 'span', array( 'class' => 'error' ), $errors );
01533                 }
01534         }
01535 }
01536 
01537 class HTMLTextField extends HTMLFormField {
01538         function getSize() {
01539                 return isset( $this->mParams['size'] )
01540                         ? $this->mParams['size']
01541                         : 45;
01542         }
01543 
01544         function getInputHTML( $value ) {
01545                 $attribs = array(
01546                         'id' => $this->mID,
01547                         'name' => $this->mName,
01548                         'size' => $this->getSize(),
01549                         'value' => $value,
01550                 ) + $this->getTooltipAndAccessKey();
01551 
01552                 if ( $this->mClass !== '' ) {
01553                         $attribs['class'] = $this->mClass;
01554                 }
01555 
01556                 if ( !empty( $this->mParams['disabled'] ) ) {
01557                         $attribs['disabled'] = 'disabled';
01558                 }
01559 
01560                 # TODO: Enforce pattern, step, required, readonly on the server side as
01561                 # well
01562                 $allowedParams = array( 'min', 'max', 'pattern', 'title', 'step',
01563                         'placeholder', 'list', 'maxlength' );
01564                 foreach ( $allowedParams as $param ) {
01565                         if ( isset( $this->mParams[$param] ) ) {
01566                                 $attribs[$param] = $this->mParams[$param];
01567                         }
01568                 }
01569 
01570                 foreach ( array( 'required', 'autofocus', 'multiple', 'readonly' ) as $param ) {
01571                         if ( isset( $this->mParams[$param] ) ) {
01572                                 $attribs[$param] = '';
01573                         }
01574                 }
01575 
01576                 # Implement tiny differences between some field variants
01577                 # here, rather than creating a new class for each one which
01578                 # is essentially just a clone of this one.
01579                 if ( isset( $this->mParams['type'] ) ) {
01580                         switch ( $this->mParams['type'] ) {
01581                                 case 'email':
01582                                         $attribs['type'] = 'email';
01583                                         break;
01584                                 case 'int':
01585                                         $attribs['type'] = 'number';
01586                                         break;
01587                                 case 'float':
01588                                         $attribs['type'] = 'number';
01589                                         $attribs['step'] = 'any';
01590                                         break;
01591                                 # Pass through
01592                                 case 'password':
01593                                 case 'file':
01594                                         $attribs['type'] = $this->mParams['type'];
01595                                         break;
01596                         }
01597                 }
01598 
01599                 return Html::element( 'input', $attribs );
01600         }
01601 }
01602 class HTMLTextAreaField extends HTMLFormField {
01603         function getCols() {
01604                 return isset( $this->mParams['cols'] )
01605                         ? $this->mParams['cols']
01606                         : 80;
01607         }
01608 
01609         function getRows() {
01610                 return isset( $this->mParams['rows'] )
01611                         ? $this->mParams['rows']
01612                         : 25;
01613         }
01614 
01615         function getInputHTML( $value ) {
01616                 $attribs = array(
01617                         'id' => $this->mID,
01618                         'name' => $this->mName,
01619                         'cols' => $this->getCols(),
01620                         'rows' => $this->getRows(),
01621                 ) + $this->getTooltipAndAccessKey();
01622 
01623                 if ( $this->mClass !== '' ) {
01624                         $attribs['class'] = $this->mClass;
01625                 }
01626 
01627                 if ( !empty( $this->mParams['disabled'] ) ) {
01628                         $attribs['disabled'] = 'disabled';
01629                 }
01630 
01631                 if ( !empty( $this->mParams['readonly'] ) ) {
01632                         $attribs['readonly'] = 'readonly';
01633                 }
01634 
01635                 if ( isset( $this->mParams['placeholder'] ) ) {
01636                         $attribs['placeholder'] = $this->mParams['placeholder'];
01637                 }
01638 
01639                 foreach ( array( 'required', 'autofocus' ) as $param ) {
01640                         if ( isset( $this->mParams[$param] ) ) {
01641                                 $attribs[$param] = '';
01642                         }
01643                 }
01644 
01645                 return Html::element( 'textarea', $attribs, $value );
01646         }
01647 }
01648 
01652 class HTMLFloatField extends HTMLTextField {
01653         function getSize() {
01654                 return isset( $this->mParams['size'] )
01655                         ? $this->mParams['size']
01656                         : 20;
01657         }
01658 
01659         function validate( $value, $alldata ) {
01660                 $p = parent::validate( $value, $alldata );
01661 
01662                 if ( $p !== true ) {
01663                         return $p;
01664                 }
01665 
01666                 $value = trim( $value );
01667 
01668                 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#real-numbers
01669                 # with the addition that a leading '+' sign is ok.
01670                 if ( !preg_match( '/^((\+|\-)?\d+(\.\d+)?(E(\+|\-)?\d+)?)?$/i', $value ) ) {
01671                         return $this->msg( 'htmlform-float-invalid' )->parseAsBlock();
01672                 }
01673 
01674                 # The "int" part of these message names is rather confusing.
01675                 # They make equal sense for all numbers.
01676                 if ( isset( $this->mParams['min'] ) ) {
01677                         $min = $this->mParams['min'];
01678 
01679                         if ( $min > $value ) {
01680                                 return $this->msg( 'htmlform-int-toolow', $min )->parseAsBlock();
01681                         }
01682                 }
01683 
01684                 if ( isset( $this->mParams['max'] ) ) {
01685                         $max = $this->mParams['max'];
01686 
01687                         if ( $max < $value ) {
01688                                 return $this->msg( 'htmlform-int-toohigh', $max )->parseAsBlock();
01689                         }
01690                 }
01691 
01692                 return true;
01693         }
01694 }
01695 
01699 class HTMLIntField extends HTMLFloatField {
01700         function validate( $value, $alldata ) {
01701                 $p = parent::validate( $value, $alldata );
01702 
01703                 if ( $p !== true ) {
01704                         return $p;
01705                 }
01706 
01707                 # http://dev.w3.org/html5/spec/common-microsyntaxes.html#signed-integers
01708                 # with the addition that a leading '+' sign is ok. Note that leading zeros
01709                 # are fine, and will be left in the input, which is useful for things like
01710                 # phone numbers when you know that they are integers (the HTML5 type=tel
01711                 # input does not require its value to be numeric).  If you want a tidier
01712                 # value to, eg, save in the DB, clean it up with intval().
01713                 if ( !preg_match( '/^((\+|\-)?\d+)?$/', trim( $value ) )
01714                 ) {
01715                         return $this->msg( 'htmlform-int-invalid' )->parseAsBlock();
01716                 }
01717 
01718                 return true;
01719         }
01720 }
01721 
01725 class HTMLCheckField extends HTMLFormField {
01726         function getInputHTML( $value ) {
01727                 if ( !empty( $this->mParams['invert'] ) ) {
01728                         $value = !$value;
01729                 }
01730 
01731                 $attr = $this->getTooltipAndAccessKey();
01732                 $attr['id'] = $this->mID;
01733 
01734                 if ( !empty( $this->mParams['disabled'] ) ) {
01735                         $attr['disabled'] = 'disabled';
01736                 }
01737 
01738                 if ( $this->mClass !== '' ) {
01739                         $attr['class'] = $this->mClass;
01740                 }
01741 
01742                 return Xml::check( $this->mName, $value, $attr ) . '&#160;' .
01743                         Html::rawElement( 'label', array( 'for' => $this->mID ), $this->mLabel );
01744         }
01745 
01751         function getLabel() {
01752                 return '&#160;';
01753         }
01754 
01759         function loadDataFromRequest( $request ) {
01760                 $invert = false;
01761                 if ( isset( $this->mParams['invert'] ) && $this->mParams['invert'] ) {
01762                         $invert = true;
01763                 }
01764 
01765                 // GetCheck won't work like we want for checks.
01766                 // Fetch the value in either one of the two following case:
01767                 // - we have a valid token (form got posted or GET forged by the user)
01768                 // - checkbox name has a value (false or true), ie is not null
01769                 if ( $request->getCheck( 'wpEditToken' ) || $request->getVal( $this->mName ) !== null ) {
01770                         // XOR has the following truth table, which is what we want
01771                         // INVERT VALUE | OUTPUT
01772                         // true   true  | false
01773                         // false  true  | true
01774                         // false  false | false
01775                         // true   false | true
01776                         return $request->getBool( $this->mName ) xor $invert;
01777                 } else {
01778                         return $this->getDefault();
01779                 }
01780         }
01781 }
01782 
01786 class HTMLSelectField extends HTMLFormField {
01787         function validate( $value, $alldata ) {
01788                 $p = parent::validate( $value, $alldata );
01789 
01790                 if ( $p !== true ) {
01791                         return $p;
01792                 }
01793 
01794                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01795 
01796                 if ( in_array( $value, $validOptions ) )
01797                         return true;
01798                 else
01799                         return $this->msg( 'htmlform-select-badoption' )->parse();
01800         }
01801 
01802         function getInputHTML( $value ) {
01803                 $select = new XmlSelect( $this->mName, $this->mID, strval( $value ) );
01804 
01805                 # If one of the options' 'name' is int(0), it is automatically selected.
01806                 # because PHP sucks and thinks int(0) == 'some string'.
01807                 # Working around this by forcing all of them to strings.
01808                 foreach ( $this->mParams['options'] as &$opt ) {
01809                         if ( is_int( $opt ) ) {
01810                                 $opt = strval( $opt );
01811                         }
01812                 }
01813                 unset( $opt ); # PHP keeps $opt around as a reference, which is a bit scary
01814 
01815                 if ( !empty( $this->mParams['disabled'] ) ) {
01816                         $select->setAttribute( 'disabled', 'disabled' );
01817                 }
01818 
01819                 if ( $this->mClass !== '' ) {
01820                         $select->setAttribute( 'class', $this->mClass );
01821                 }
01822 
01823                 $select->addOptions( $this->mParams['options'] );
01824 
01825                 return $select->getHTML();
01826         }
01827 }
01828 
01832 class HTMLSelectOrOtherField extends HTMLTextField {
01833         static $jsAdded = false;
01834 
01835         function __construct( $params ) {
01836                 if ( !in_array( 'other', $params['options'], true ) ) {
01837                         $msg = isset( $params['other'] ) ?
01838                                 $params['other'] :
01839                                 wfMessage( 'htmlform-selectorother-other' )->text();
01840                         $params['options'][$msg] = 'other';
01841                 }
01842 
01843                 parent::__construct( $params );
01844         }
01845 
01846         static function forceToStringRecursive( $array ) {
01847                 if ( is_array( $array ) ) {
01848                         return array_map( array( __CLASS__, 'forceToStringRecursive' ), $array );
01849                 } else {
01850                         return strval( $array );
01851                 }
01852         }
01853 
01854         function getInputHTML( $value ) {
01855                 $valInSelect = false;
01856 
01857                 if ( $value !== false ) {
01858                         $valInSelect = in_array(
01859                                 $value,
01860                                 HTMLFormField::flattenOptions( $this->mParams['options'] )
01861                         );
01862                 }
01863 
01864                 $selected = $valInSelect ? $value : 'other';
01865 
01866                 $opts = self::forceToStringRecursive( $this->mParams['options'] );
01867 
01868                 $select = new XmlSelect( $this->mName, $this->mID, $selected );
01869                 $select->addOptions( $opts );
01870 
01871                 $select->setAttribute( 'class', 'mw-htmlform-select-or-other' );
01872 
01873                 $tbAttribs = array( 'id' => $this->mID . '-other', 'size' => $this->getSize() );
01874 
01875                 if ( !empty( $this->mParams['disabled'] ) ) {
01876                         $select->setAttribute( 'disabled', 'disabled' );
01877                         $tbAttribs['disabled'] = 'disabled';
01878                 }
01879 
01880                 $select = $select->getHTML();
01881 
01882                 if ( isset( $this->mParams['maxlength'] ) ) {
01883                         $tbAttribs['maxlength'] = $this->mParams['maxlength'];
01884                 }
01885 
01886                 if ( $this->mClass !== '' ) {
01887                         $tbAttribs['class'] = $this->mClass;
01888                 }
01889 
01890                 $textbox = Html::input(
01891                         $this->mName . '-other',
01892                         $valInSelect ? '' : $value,
01893                         'text',
01894                         $tbAttribs
01895                 );
01896 
01897                 return "$select<br />\n$textbox";
01898         }
01899 
01904         function loadDataFromRequest( $request ) {
01905                 if ( $request->getCheck( $this->mName ) ) {
01906                         $val = $request->getText( $this->mName );
01907 
01908                         if ( $val == 'other' ) {
01909                                 $val = $request->getText( $this->mName . '-other' );
01910                         }
01911 
01912                         return $val;
01913                 } else {
01914                         return $this->getDefault();
01915                 }
01916         }
01917 }
01918 
01922 class HTMLMultiSelectField extends HTMLFormField {
01923 
01924         function validate( $value, $alldata ) {
01925                 $p = parent::validate( $value, $alldata );
01926 
01927                 if ( $p !== true ) {
01928                         return $p;
01929                 }
01930 
01931                 if ( !is_array( $value ) ) {
01932                         return false;
01933                 }
01934 
01935                 # If all options are valid, array_intersect of the valid options
01936                 # and the provided options will return the provided options.
01937                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
01938 
01939                 $validValues = array_intersect( $value, $validOptions );
01940                 if ( count( $validValues ) == count( $value ) ) {
01941                         return true;
01942                 } else {
01943                         return $this->msg( 'htmlform-select-badoption' )->parse();
01944                 }
01945         }
01946 
01947         function getInputHTML( $value ) {
01948                 $html = $this->formatOptions( $this->mParams['options'], $value );
01949 
01950                 return $html;
01951         }
01952 
01953         function formatOptions( $options, $value ) {
01954                 $html = '';
01955 
01956                 $attribs = array();
01957 
01958                 if ( !empty( $this->mParams['disabled'] ) ) {
01959                         $attribs['disabled'] = 'disabled';
01960                 }
01961 
01962                 foreach ( $options as $label => $info ) {
01963                         if ( is_array( $info ) ) {
01964                                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
01965                                 $html .= $this->formatOptions( $info, $value );
01966                         } else {
01967                                 $thisAttribs = array( 'id' => "{$this->mID}-$info", 'value' => $info );
01968 
01969                                 $checkbox = Xml::check(
01970                                         $this->mName . '[]',
01971                                         in_array( $info, $value, true ),
01972                                         $attribs + $thisAttribs );
01973                                 $checkbox .= '&#160;' . Html::rawElement( 'label', array( 'for' => "{$this->mID}-$info" ), $label );
01974 
01975                                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $checkbox );
01976                         }
01977                 }
01978 
01979                 return $html;
01980         }
01981 
01986         function loadDataFromRequest( $request ) {
01987                 if ( $this->mParent->getMethod() == 'post' ) {
01988                         if ( $request->wasPosted() ) {
01989                                 # Checkboxes are just not added to the request arrays if they're not checked,
01990                                 # so it's perfectly possible for there not to be an entry at all
01991                                 return $request->getArray( $this->mName, array() );
01992                         } else {
01993                                 # That's ok, the user has not yet submitted the form, so show the defaults
01994                                 return $this->getDefault();
01995                         }
01996                 } else {
01997                         # This is the impossible case: if we look at $_GET and see no data for our
01998                         # field, is it because the user has not yet submitted the form, or that they
01999                         # have submitted it with all the options unchecked? We will have to assume the
02000                         # latter, which basically means that you can't specify 'positive' defaults
02001                         # for GET forms.
02002                         # @todo FIXME...
02003                         return $request->getArray( $this->mName, array() );
02004                 }
02005         }
02006 
02007         function getDefault() {
02008                 if ( isset( $this->mDefault ) ) {
02009                         return $this->mDefault;
02010                 } else {
02011                         return array();
02012                 }
02013         }
02014 
02015         protected function needsLabel() {
02016                 return false;
02017         }
02018 }
02019 
02030 class HTMLSelectAndOtherField extends HTMLSelectField {
02031 
02032         function __construct( $params ) {
02033                 if ( array_key_exists( 'other', $params ) ) {
02034                 } elseif ( array_key_exists( 'other-message', $params ) ) {
02035                         $params['other'] = wfMessage( $params['other-message'] )->plain();
02036                 } else {
02037                         $params['other'] = null;
02038                 }
02039 
02040                 if ( array_key_exists( 'options', $params ) ) {
02041                         # Options array already specified
02042                 } elseif ( array_key_exists( 'options-message', $params ) ) {
02043                         # Generate options array from a system message
02044                         $params['options'] = self::parseMessage(
02045                                 wfMessage( $params['options-message'] )->inContentLanguage()->plain(),
02046                                 $params['other']
02047                         );
02048                 } else {
02049                         # Sulk
02050                         throw new MWException( 'HTMLSelectAndOtherField called without any options' );
02051                 }
02052                 $this->mFlatOptions = self::flattenOptions( $params['options'] );
02053 
02054                 parent::__construct( $params );
02055         }
02056 
02064         public static function parseMessage( $string, $otherName = null ) {
02065                 if ( $otherName === null ) {
02066                         $otherName = wfMessage( 'htmlform-selectorother-other' )->plain();
02067                 }
02068 
02069                 $optgroup = false;
02070                 $options = array( $otherName => 'other' );
02071 
02072                 foreach ( explode( "\n", $string ) as $option ) {
02073                         $value = trim( $option );
02074                         if ( $value == '' ) {
02075                                 continue;
02076                         } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
02077                                 # A new group is starting...
02078                                 $value = trim( substr( $value, 1 ) );
02079                                 $optgroup = $value;
02080                         } elseif ( substr( $value, 0, 2 ) == '**' ) {
02081                                 # groupmember
02082                                 $opt = trim( substr( $value, 2 ) );
02083                                 if ( $optgroup === false ) {
02084                                         $options[$opt] = $opt;
02085                                 } else {
02086                                         $options[$optgroup][$opt] = $opt;
02087                                 }
02088                         } else {
02089                                 # groupless reason list
02090                                 $optgroup = false;
02091                                 $options[$option] = $option;
02092                         }
02093                 }
02094 
02095                 return $options;
02096         }
02097 
02098         function getInputHTML( $value ) {
02099                 $select = parent::getInputHTML( $value[1] );
02100 
02101                 $textAttribs = array(
02102                         'id' => $this->mID . '-other',
02103                         'size' => $this->getSize(),
02104                 );
02105 
02106                 if ( $this->mClass !== '' ) {
02107                         $textAttribs['class'] = $this->mClass;
02108                 }
02109 
02110                 foreach ( array( 'required', 'autofocus', 'multiple', 'disabled' ) as $param ) {
02111                         if ( isset( $this->mParams[$param] ) ) {
02112                                 $textAttribs[$param] = '';
02113                         }
02114                 }
02115 
02116                 $textbox = Html::input(
02117                         $this->mName . '-other',
02118                         $value[2],
02119                         'text',
02120                         $textAttribs
02121                 );
02122 
02123                 return "$select<br />\n$textbox";
02124         }
02125 
02130         function loadDataFromRequest( $request ) {
02131                 if ( $request->getCheck( $this->mName ) ) {
02132 
02133                         $list = $request->getText( $this->mName );
02134                         $text = $request->getText( $this->mName . '-other' );
02135 
02136                         if ( $list == 'other' ) {
02137                                 $final = $text;
02138                         } elseif ( !in_array( $list, $this->mFlatOptions ) ) {
02139                                 # User has spoofed the select form to give an option which wasn't
02140                                 # in the original offer.  Sulk...
02141                                 $final = $text;
02142                         } elseif ( $text == '' ) {
02143                                 $final = $list;
02144                         } else {
02145                                 $final = $list . $this->msg( 'colon-separator' )->inContentLanguage()->text() . $text;
02146                         }
02147 
02148                 } else {
02149                         $final = $this->getDefault();
02150 
02151                         $list = 'other';
02152                         $text = $final;
02153                         foreach ( $this->mFlatOptions as $option ) {
02154                                 $match = $option . $this->msg( 'colon-separator' )->inContentLanguage()->text();
02155                                 if ( strpos( $text, $match ) === 0 ) {
02156                                         $list = $option;
02157                                         $text = substr( $text, strlen( $match ) );
02158                                         break;
02159                                 }
02160                         }
02161                 }
02162                 return array( $final, $list, $text );
02163         }
02164 
02165         function getSize() {
02166                 return isset( $this->mParams['size'] )
02167                         ? $this->mParams['size']
02168                         : 45;
02169         }
02170 
02171         function validate( $value, $alldata ) {
02172                 # HTMLSelectField forces $value to be one of the options in the select
02173                 # field, which is not useful here.  But we do want the validation further up
02174                 # the chain
02175                 $p = parent::validate( $value[1], $alldata );
02176 
02177                 if ( $p !== true ) {
02178                         return $p;
02179                 }
02180 
02181                 if ( isset( $this->mParams['required'] ) && $this->mParams['required'] !== false && $value[1] === '' ) {
02182                         return $this->msg( 'htmlform-required' )->parse();
02183                 }
02184 
02185                 return true;
02186         }
02187 }
02188 
02192 class HTMLRadioField extends HTMLFormField {
02193 
02194 
02195         function validate( $value, $alldata ) {
02196                 $p = parent::validate( $value, $alldata );
02197 
02198                 if ( $p !== true ) {
02199                         return $p;
02200                 }
02201 
02202                 if ( !is_string( $value ) && !is_int( $value ) ) {
02203                         return false;
02204                 }
02205 
02206                 $validOptions = HTMLFormField::flattenOptions( $this->mParams['options'] );
02207 
02208                 if ( in_array( $value, $validOptions ) ) {
02209                         return true;
02210                 } else {
02211                         return $this->msg( 'htmlform-select-badoption' )->parse();
02212                 }
02213         }
02214 
02221         function getInputHTML( $value ) {
02222                 $html = $this->formatOptions( $this->mParams['options'], $value );
02223 
02224                 return $html;
02225         }
02226 
02227         function formatOptions( $options, $value ) {
02228                 $html = '';
02229 
02230                 $attribs = array();
02231                 if ( !empty( $this->mParams['disabled'] ) ) {
02232                         $attribs['disabled'] = 'disabled';
02233                 }
02234 
02235                 # TODO: should this produce an unordered list perhaps?
02236                 foreach ( $options as $label => $info ) {
02237                         if ( is_array( $info ) ) {
02238                                 $html .= Html::rawElement( 'h1', array(), $label ) . "\n";
02239                                 $html .= $this->formatOptions( $info, $value );
02240                         } else {
02241                                 $id = Sanitizer::escapeId( $this->mID . "-$info" );
02242                                 $radio = Xml::radio(
02243                                         $this->mName,
02244                                         $info,
02245                                         $info == $value,
02246                                         $attribs + array( 'id' => $id )
02247                                 );
02248                                 $radio .= '&#160;' .
02249                                                 Html::rawElement( 'label', array( 'for' => $id ), $label );
02250 
02251                                 $html .= ' ' . Html::rawElement( 'div', array( 'class' => 'mw-htmlform-flatlist-item' ), $radio );
02252                         }
02253                 }
02254 
02255                 return $html;
02256         }
02257 
02258         protected function needsLabel() {
02259                 return false;
02260         }
02261 }
02262 
02266 class HTMLInfoField extends HTMLFormField {
02267         public function __construct( $info ) {
02268                 $info['nodata'] = true;
02269 
02270                 parent::__construct( $info );
02271         }
02272 
02273         public function getInputHTML( $value ) {
02274                 return !empty( $this->mParams['raw'] ) ? $value : htmlspecialchars( $value );
02275         }
02276 
02277         public function getTableRow( $value ) {
02278                 if ( !empty( $this->mParams['rawrow'] ) ) {
02279                         return $value;
02280                 }
02281 
02282                 return parent::getTableRow( $value );
02283         }
02284 
02288         public function getDiv( $value ) {
02289                 if ( !empty( $this->mParams['rawrow'] ) ) {
02290                         return $value;
02291                 }
02292 
02293                 return parent::getDiv( $value );
02294         }
02295 
02299         public function getRaw( $value ) {
02300                 if ( !empty( $this->mParams['rawrow'] ) ) {
02301                         return $value;
02302                 }
02303 
02304                 return parent::getRaw( $value );
02305         }
02306 
02307         protected function needsLabel() {
02308                 return false;
02309         }
02310 }
02311 
02312 class HTMLHiddenField extends HTMLFormField {
02313         public function __construct( $params ) {
02314                 parent::__construct( $params );
02315 
02316                 # Per HTML5 spec, hidden fields cannot be 'required'
02317                 # http://dev.w3.org/html5/spec/states-of-the-type-attribute.html#hidden-state
02318                 unset( $this->mParams['required'] );
02319         }
02320 
02321         public function getTableRow( $value ) {
02322                 $params = array();
02323                 if ( $this->mID ) {
02324                         $params['id'] = $this->mID;
02325                 }
02326 
02327                 $this->mParent->addHiddenField(
02328                         $this->mName,
02329                         $this->mDefault,
02330                         $params
02331                 );
02332 
02333                 return '';
02334         }
02335 
02339         public function getDiv( $value ) {
02340                 return $this->getTableRow( $value );
02341         }
02342 
02346         public function getRaw( $value ) {
02347                 return $this->getTableRow( $value );
02348         }
02349 
02350         public function getInputHTML( $value ) { return ''; }
02351 }
02352 
02357 class HTMLSubmitField extends HTMLFormField {
02358 
02359         public function __construct( $info ) {
02360                 $info['nodata'] = true;
02361                 parent::__construct( $info );
02362         }
02363 
02364         public function getInputHTML( $value ) {
02365                 return Xml::submitButton(
02366                         $value,
02367                         array(
02368                                 'class' => 'mw-htmlform-submit ' . $this->mClass,
02369                                 'name' => $this->mName,
02370                                 'id' => $this->mID,
02371                         )
02372                 );
02373         }
02374 
02375         protected function needsLabel() {
02376                 return false;
02377         }
02378 
02385         public function validate( $value, $alldata ) {
02386                 return true;
02387         }
02388 }
02389 
02390 class HTMLEditTools extends HTMLFormField {
02391         public function getInputHTML( $value ) {
02392                 return '';
02393         }
02394 
02395         public function getTableRow( $value ) {
02396                 $msg = $this->formatMsg();
02397 
02398                 return '<tr><td></td><td class="mw-input">'
02399                         . '<div class="mw-editTools">'
02400                         . $msg->parseAsBlock()
02401                         . "</div></td></tr>\n";
02402         }
02403 
02407         public function getDiv( $value ) {
02408                 $msg = $this->formatMsg();
02409                 return '<div class="mw-editTools">' . $msg->parseAsBlock() . '</div>';
02410         }
02411 
02415         public function getRaw( $value ) {
02416                 return $this->getDiv( $value );
02417         }
02418 
02419         protected function formatMsg() {
02420                 if ( empty( $this->mParams['message'] ) ) {
02421                         $msg = $this->msg( 'edittools' );
02422                 } else {
02423                         $msg = $this->msg( $this->mParams['message'] );
02424                         if ( $msg->isDisabled() ) {
02425                                 $msg = $this->msg( 'edittools' );
02426                         }
02427                 }
02428                 $msg->inContentLanguage();
02429                 return $msg;
02430         }
02431 }