00001 <?php
00002
00028 class Preferences {
00029 static $defaultPreferences = null;
00030 static $saveFilters =
00031 array(
00032 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
00033 );
00034
00035 static function getPreferences( $user ) {
00036 if ( self::$defaultPreferences )
00037 return self::$defaultPreferences;
00038
00039 global $wgRCMaxAge;
00040
00041 $defaultPreferences = array();
00042
00043 self::profilePreferences( $user, $defaultPreferences );
00044 self::skinPreferences( $user, $defaultPreferences );
00045 self::filesPreferences( $user, $defaultPreferences );
00046 self::mathPreferences( $user, $defaultPreferences );
00047 self::datetimePreferences( $user, $defaultPreferences );
00048 self::renderingPreferences( $user, $defaultPreferences );
00049 self::editingPreferences( $user, $defaultPreferences );
00050 self::rcPreferences( $user, $defaultPreferences );
00051 self::watchlistPreferences( $user, $defaultPreferences );
00052 self::searchPreferences( $user, $defaultPreferences );
00053 self::miscPreferences( $user, $defaultPreferences );
00054
00055 wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
00056
00057 ## Remove preferences that wikis don't want to use
00058 global $wgHiddenPrefs;
00059 foreach ( $wgHiddenPrefs as $pref ) {
00060 if ( isset( $defaultPreferences[$pref] ) ) {
00061 unset( $defaultPreferences[$pref] );
00062 }
00063 }
00064
00065 ## Prod in defaults from the user
00066 global $wgDefaultUserOptions;
00067 foreach( $defaultPreferences as $name => &$info ) {
00068 $prefFromUser = self::getOptionFromUser( $name, $info, $user );
00069 $field = HTMLForm::loadInputFromParameters( $info );
00070 $defaultOptions = User::getDefaultOptions();
00071 $globalDefault = isset( $defaultOptions[$name] )
00072 ? $defaultOptions[$name]
00073 : null;
00074
00075
00076 if ( isset( $info['default'] ) ) {
00077
00078 continue;
00079 } elseif ( !is_null( $prefFromUser ) &&
00080 $field->validate( $prefFromUser, $user->mOptions ) === true ) {
00081 $info['default'] = $prefFromUser;
00082 } elseif( $field->validate( $globalDefault, $user->mOptions ) === true ) {
00083 $info['default'] = $globalDefault;
00084 } else {
00085 throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
00086 }
00087 }
00088
00089 self::$defaultPreferences = $defaultPreferences;
00090
00091 return $defaultPreferences;
00092 }
00093
00094
00095 static function getOptionFromUser( $name, $info, $user ) {
00096 $val = $user->getOption( $name );
00097
00098
00099 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
00100 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
00101
00102 $options = HTMLFormField::flattenOptions( $info['options'] );
00103 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
00104 $val = array();
00105
00106 foreach( $options as $label => $value ) {
00107 if( $user->getOption( "$prefix$value" ) ) {
00108 $val[] = $value;
00109 }
00110 }
00111 }
00112
00113 return $val;
00114 }
00115
00116 static function profilePreferences( $user, &$defaultPreferences ) {
00117 global $wgLang, $wgUser;
00118 ## User info #####################################
00119
00120 $defaultPreferences['username'] =
00121 array(
00122 'type' => 'info',
00123 'label-message' => 'username',
00124 'default' => $user->getName(),
00125 'section' => 'personal/info',
00126 );
00127
00128 $defaultPreferences['userid'] =
00129 array(
00130 'type' => 'info',
00131 'label-message' => 'uid',
00132 'default' => $user->getId(),
00133 'section' => 'personal/info',
00134 );
00135
00136 # Get groups to which the user belongs
00137 $userEffectiveGroups = $user->getEffectiveGroups();
00138 $userGroups = $userMembers = array();
00139 foreach( $userEffectiveGroups as $ueg ) {
00140 if( $ueg == '*' ) {
00141
00142 continue;
00143 }
00144 $groupName = User::getGroupName( $ueg );
00145 $userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
00146
00147 $memberName = User::getGroupMember( $ueg );
00148 $userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
00149 }
00150 asort( $userGroups );
00151 asort( $userMembers );
00152
00153 $defaultPreferences['usergroups'] =
00154 array(
00155 'type' => 'info',
00156 'label' => wfMsgExt( 'prefs-memberingroups', 'parseinline',
00157 $wgLang->formatNum( count($userGroups) ) ),
00158 'default' => wfMsgExt( 'prefs-memberingroups-type', array(),
00159 $wgLang->commaList( $userGroups ),
00160 $wgLang->commaList( $userMembers )
00161 ),
00162 'raw' => true,
00163 'section' => 'personal/info',
00164 );
00165
00166 $defaultPreferences['editcount'] =
00167 array(
00168 'type' => 'info',
00169 'label-message' => 'prefs-edits',
00170 'default' => $wgLang->formatNum( $user->getEditCount() ),
00171 'section' => 'personal/info',
00172 );
00173
00174 if( $user->getRegistration() ) {
00175 $defaultPreferences['registrationdate'] =
00176 array(
00177 'type' => 'info',
00178 'label-message' => 'prefs-registration',
00179 'default' => wfMsgExt( 'prefs-registration-date-time', 'parsemag',
00180 $wgLang->timeanddate( $user->getRegistration(), true ),
00181 $wgLang->date( $user->getRegistration(), true ),
00182 $wgLang->time( $user->getRegistration(), true ) ),
00183 'section' => 'personal/info',
00184 );
00185 }
00186
00187
00188 global $wgAuth;
00189 $defaultPreferences['realname'] =
00190 array(
00191 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
00192 'default' => $user->getRealName(),
00193 'section' => 'personal/info',
00194 'label-message' => 'yourrealname',
00195 'help-message' => 'prefs-help-realname',
00196 );
00197
00198 $defaultPreferences['gender'] =
00199 array(
00200 'type' => 'select',
00201 'section' => 'personal/info',
00202 'options' => array(
00203 wfMsg( 'gender-male' ) => 'male',
00204 wfMsg( 'gender-female' ) => 'female',
00205 wfMsg( 'gender-unknown' ) => 'unknown',
00206 ),
00207 'label-message' => 'yourgender',
00208 'help-message' => 'prefs-help-gender',
00209 );
00210
00211 if( $wgAuth->allowPasswordChange() ) {
00212 $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Resetpass' ),
00213 wfMsgHtml( 'prefs-resetpass' ), array(),
00214 array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
00215
00216 $defaultPreferences['password'] =
00217 array(
00218 'type' => 'info',
00219 'raw' => true,
00220 'default' => $link,
00221 'label-message' => 'yourpassword',
00222 'section' => 'personal/info',
00223 );
00224 }
00225
00226 $defaultPreferences['rememberpassword'] =
00227 array(
00228 'type' => 'toggle',
00229 'label-message' => 'tog-rememberpassword',
00230 'section' => 'personal/info',
00231 );
00232
00233
00234 global $wgContLanguageCode;
00235 $languages = array_reverse( Language::getLanguageNames( false ) );
00236 if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
00237 $languages[$wgContLanguageCode] = $wgContLanguageCode;
00238 }
00239 ksort( $languages );
00240
00241 $options = array();
00242 foreach( $languages as $code => $name ) {
00243 $display = wfBCP47( $code ) . ' - ' . $name;
00244 $options[$display] = $code;
00245 }
00246 $defaultPreferences['language'] =
00247 array(
00248 'type' => 'select',
00249 'section' => 'personal/i18n',
00250 'options' => $options,
00251 'label-message' => 'yourlanguage',
00252 );
00253
00254 global $wgContLang, $wgDisableLangConversion;
00255 global $wgDisableTitleConversion;
00256
00257 $variantArray = array();
00258 if( !$wgDisableLangConversion ) {
00259 $variants = $wgContLang->getVariants();
00260
00261 $languages = Language::getLanguageNames( true );
00262 foreach( $variants as $v ) {
00263 $v = str_replace( '_', '-', strtolower( $v ) );
00264 if( array_key_exists( $v, $languages ) ) {
00265
00266 $variantArray[$v] = $languages[$v];
00267 }
00268 }
00269
00270 $options = array();
00271 foreach( $variantArray as $code => $name ) {
00272 $display = wfBCP47( $code ) . ' - ' . $name;
00273 $options[$display] = $code;
00274 }
00275
00276 if( count( $variantArray ) > 1 ) {
00277 $defaultPreferences['variant'] =
00278 array(
00279 'label-message' => 'yourvariant',
00280 'type' => 'select',
00281 'options' => $options,
00282 'section' => 'personal/i18n',
00283 );
00284 }
00285 }
00286
00287 if( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
00288 $defaultPreferences['noconvertlink'] =
00289 array(
00290 'type' => 'toggle',
00291 'section' => 'personal/i18n',
00292 'label-message' => 'tog-noconvertlink',
00293 );
00294 }
00295
00296 global $wgMaxSigChars, $wgOut, $wgParser;
00297
00298
00299 $oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title , $user, new ParserOptions );
00300 $oldsigHTML = $wgOut->parseInline( $oldsigWikiText );
00301 $defaultPreferences['oldsig'] =
00302 array(
00303 'type' => 'info',
00304 'raw' => true,
00305 'label-message' => 'tog-oldsig',
00306 'default' => $oldsigHTML,
00307 'section' => 'personal/signature',
00308 );
00309 $defaultPreferences['nickname'] =
00310 array(
00311 'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
00312 'maxlength' => $wgMaxSigChars,
00313 'label-message' => 'yournick',
00314 'validation-callback' =>
00315 array( 'Preferences', 'validateSignature' ),
00316 'section' => 'personal/signature',
00317 'filter-callback' => array( 'Preferences', 'cleanSignature' ),
00318 );
00319 $defaultPreferences['fancysig'] =
00320 array(
00321 'type' => 'toggle',
00322 'label-message' => 'tog-fancysig',
00323 'help-message' => 'prefs-help-signature',
00324 'section' => 'personal/signature'
00325 );
00326
00327 ## Email stuff
00328
00329 global $wgEnableEmail;
00330 if ($wgEnableEmail) {
00331
00332 global $wgEmailConfirmToEdit;
00333
00334 $defaultPreferences['emailaddress'] =
00335 array(
00336 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
00337 'default' => $user->getEmail(),
00338 'section' => 'personal/email',
00339 'label-message' => 'youremail',
00340 'help-message' => $wgEmailConfirmToEdit
00341 ? 'prefs-help-email-required'
00342 : 'prefs-help-email',
00343 'validation-callback' => array( 'Preferences', 'validateEmail' ),
00344 );
00345
00346 global $wgEnableUserEmail, $wgEmailAuthentication;
00347
00348 $disableEmailPrefs = false;
00349
00350 if ( $wgEmailAuthentication ) {
00351 if ( $user->getEmail() ) {
00352 if( $user->getEmailAuthenticationTimestamp() ) {
00353
00354
00355
00356 $time = $wgLang->timeAndDate( $user->getEmailAuthenticationTimestamp(), true );
00357 $d = $wgLang->date( $user->getEmailAuthenticationTimestamp(), true );
00358 $t = $wgLang->time( $user->getEmailAuthenticationTimestamp(), true );
00359 $emailauthenticated = wfMsgExt( 'emailauthenticated', 'parseinline',
00360 array($time, $d, $t ) ) . '<br />';
00361 $disableEmailPrefs = false;
00362 } else {
00363 $disableEmailPrefs = true;
00364 $skin = $wgUser->getSkin();
00365 $emailauthenticated = wfMsgExt( 'emailnotauthenticated', 'parseinline' ) . '<br />' .
00366 $skin->link(
00367 SpecialPage::getTitleFor( 'Confirmemail' ),
00368 wfMsg( 'emailconfirmlink' ),
00369 array(),
00370 array(),
00371 array( 'known', 'noclasses' )
00372 ) . '<br />';
00373 }
00374 } else {
00375 $disableEmailPrefs = true;
00376 $emailauthenticated = wfMsgHtml( 'noemailprefs' );
00377 }
00378
00379 $defaultPreferences['emailauthentication'] =
00380 array(
00381 'type' => 'info',
00382 'raw' => true,
00383 'section' => 'personal/email',
00384 'label-message' => 'prefs-emailconfirm-label',
00385 'default' => $emailauthenticated,
00386 );
00387
00388 }
00389
00390 if( $wgEnableUserEmail && $user->isAllowed( 'sendemail' ) ) {
00391 $defaultPreferences['disablemail'] =
00392 array(
00393 'type' => 'toggle',
00394 'invert' => true,
00395 'section' => 'personal/email',
00396 'label-message' => 'allowemail',
00397 'disabled' => $disableEmailPrefs,
00398 );
00399 $defaultPreferences['ccmeonemails'] =
00400 array(
00401 'type' => 'toggle',
00402 'section' => 'personal/email',
00403 'label-message' => 'tog-ccmeonemails',
00404 'disabled' => $disableEmailPrefs,
00405 );
00406 }
00407
00408 global $wgEnotifWatchlist;
00409 if ( $wgEnotifWatchlist ) {
00410 $defaultPreferences['enotifwatchlistpages'] =
00411 array(
00412 'type' => 'toggle',
00413 'section' => 'personal/email',
00414 'label-message' => 'tog-enotifwatchlistpages',
00415 'disabled' => $disableEmailPrefs,
00416 );
00417 }
00418 global $wgEnotifUserTalk;
00419 if( $wgEnotifUserTalk ) {
00420 $defaultPreferences['enotifusertalkpages'] =
00421 array(
00422 'type' => 'toggle',
00423 'section' => 'personal/email',
00424 'label-message' => 'tog-enotifusertalkpages',
00425 'disabled' => $disableEmailPrefs,
00426 );
00427 }
00428 if( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
00429 $defaultPreferences['enotifminoredits'] =
00430 array(
00431 'type' => 'toggle',
00432 'section' => 'personal/email',
00433 'label-message' => 'tog-enotifminoredits',
00434 'disabled' => $disableEmailPrefs,
00435 );
00436 }
00437 $defaultPreferences['enotifrevealaddr'] =
00438 array(
00439 'type' => 'toggle',
00440 'section' => 'personal/email',
00441 'label-message' => 'tog-enotifrevealaddr',
00442 'disabled' => $disableEmailPrefs,
00443 );
00444 }
00445 }
00446
00447 static function skinPreferences( $user, &$defaultPreferences ) {
00448 ## Skin #####################################
00449 $defaultPreferences['skin'] =
00450 array(
00451 'type' => 'radio',
00452 'options' => self::generateSkinOptions( $user ),
00453 'label' => ' ',
00454 'section' => 'rendering/skin',
00455 );
00456
00457 $selectedSkin = $user->getOption( 'skin' );
00458 if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
00459 global $wgLang;
00460 $settings = array_flip( $wgLang->getQuickbarSettings() );
00461
00462 $defaultPreferences['quickbar'] =
00463 array(
00464 'type' => 'radio',
00465 'options' => $settings,
00466 'section' => 'rendering/skin',
00467 'label-message' => 'qbsettings',
00468 );
00469 }
00470 }
00471
00472 static function mathPreferences( $user, &$defaultPreferences ) {
00473 ## Math #####################################
00474 global $wgUseTeX, $wgLang;
00475 if( $wgUseTeX ) {
00476 $defaultPreferences['math'] =
00477 array(
00478 'type' => 'radio',
00479 'options' =>
00480 array_flip( array_map( 'wfMsgHtml', $wgLang->getMathNames() ) ),
00481 'label' => ' ',
00482 'section' => 'rendering/math',
00483 );
00484 }
00485 }
00486
00487 static function filesPreferences( $user, &$defaultPreferences ) {
00488 ## Files #####################################
00489 $defaultPreferences['imagesize'] =
00490 array(
00491 'type' => 'select',
00492 'options' => self::getImageSizes(),
00493 'label-message' => 'imagemaxsize',
00494 'section' => 'rendering/files',
00495 );
00496 $defaultPreferences['thumbsize'] =
00497 array(
00498 'type' => 'select',
00499 'options' => self::getThumbSizes(),
00500 'label-message' => 'thumbsize',
00501 'section' => 'rendering/files',
00502 );
00503 }
00504
00505 static function datetimePreferences( $user, &$defaultPreferences ) {
00506 global $wgLang;
00507
00508 ## Date and time #####################################
00509 $dateOptions = self::getDateOptions();
00510 if( $dateOptions ) {
00511 $defaultPreferences['date'] =
00512 array(
00513 'type' => 'radio',
00514 'options' => $dateOptions,
00515 'label' => ' ',
00516 'section' => 'datetime/dateformat',
00517 );
00518 }
00519
00520
00521 $nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
00522 $wgLang->time( $now = wfTimestampNow(), true ) );
00523 $nowserver = $wgLang->time( $now, false ) .
00524 Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
00525
00526 $defaultPreferences['nowserver'] =
00527 array(
00528 'type' => 'info',
00529 'raw' => 1,
00530 'label-message' => 'servertime',
00531 'default' => $nowserver,
00532 'section' => 'datetime/timeoffset',
00533 );
00534
00535 $defaultPreferences['nowlocal'] =
00536 array(
00537 'type' => 'info',
00538 'raw' => 1,
00539 'label-message' => 'localtime',
00540 'default' => $nowlocal,
00541 'section' => 'datetime/timeoffset',
00542 );
00543
00544
00545 $tzOffset = $user->getOption( 'timecorrection' );
00546 $tz = explode( '|', $tzOffset, 2 );
00547
00548 $tzSetting = $tzOffset;
00549 if( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
00550 $minDiff = $tz[1];
00551 $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff/60 ), abs( $minDiff )%60 );
00552 }
00553
00554 $defaultPreferences['timecorrection'] =
00555 array(
00556 'class' => 'HTMLSelectOrOtherField',
00557 'label-message' => 'timezonelegend',
00558 'options' => self::getTimezoneOptions(),
00559 'default' => $tzSetting,
00560 'size' => 20,
00561 'section' => 'datetime/timeoffset',
00562 );
00563 }
00564
00565 static function renderingPreferences( $user, &$defaultPreferences ) {
00566 ## Page Rendering ##############################
00567 $defaultPreferences['underline'] =
00568 array(
00569 'type' => 'select',
00570 'options' => array(
00571 wfMsg( 'underline-never' ) => 0,
00572 wfMsg( 'underline-always' ) => 1,
00573 wfMsg( 'underline-default' ) => 2,
00574 ),
00575 'label-message' => 'tog-underline',
00576 'section' => 'rendering/advancedrendering',
00577 );
00578
00579 $stubThresholdValues = array( 0, 50, 100, 500, 1000, 2000, 5000, 10000 );
00580 $stubThresholdOptions = array();
00581 foreach( $stubThresholdValues as $value ) {
00582 $stubThresholdOptions[wfMsg( 'size-bytes', $value )] = $value;
00583 }
00584
00585 $defaultPreferences['stubthreshold'] =
00586 array(
00587 'type' => 'selectorother',
00588 'section' => 'rendering/advancedrendering',
00589 'options' => $stubThresholdOptions,
00590 'size' => 20,
00591 'label' => wfMsg( 'stub-threshold' ),
00592 );
00593 $defaultPreferences['highlightbroken'] =
00594 array(
00595 'type' => 'toggle',
00596 'section' => 'rendering/advancedrendering',
00597 'label' => wfMsg( 'tog-highlightbroken' ),
00598 );
00599 $defaultPreferences['showtoc'] =
00600 array(
00601 'type' => 'toggle',
00602 'section' => 'rendering/advancedrendering',
00603 'label-message' => 'tog-showtoc',
00604 );
00605 $defaultPreferences['nocache'] =
00606 array(
00607 'type' => 'toggle',
00608 'label-message' => 'tog-nocache',
00609 'section' => 'rendering/advancedrendering',
00610 );
00611 $defaultPreferences['showhiddencats'] =
00612 array(
00613 'type' => 'toggle',
00614 'section' => 'rendering/advancedrendering',
00615 'label-message' => 'tog-showhiddencats'
00616 );
00617 $defaultPreferences['showjumplinks'] =
00618 array(
00619 'type' => 'toggle',
00620 'section' => 'rendering/advancedrendering',
00621 'label-message' => 'tog-showjumplinks',
00622 );
00623 $defaultPreferences['justify'] =
00624 array(
00625 'type' => 'toggle',
00626 'section' => 'rendering/advancedrendering',
00627 'label-message' => 'tog-justify',
00628 );
00629 $defaultPreferences['numberheadings'] =
00630 array(
00631 'type' => 'toggle',
00632 'section' => 'rendering/advancedrendering',
00633 'label-message' => 'tog-numberheadings',
00634 );
00635 }
00636
00637 static function editingPreferences( $user, &$defaultPreferences ) {
00638 global $wgUseExternalEditor, $wgLivePreview;
00639
00640 ## Editing #####################################
00641 $defaultPreferences['cols'] =
00642 array(
00643 'type' => 'int',
00644 'label-message' => 'columns',
00645 'section' => 'editing/textboxsize',
00646 'min' => 4,
00647 'max' => 1000,
00648 );
00649 $defaultPreferences['rows'] =
00650 array(
00651 'type' => 'int',
00652 'label-message' => 'rows',
00653 'section' => 'editing/textboxsize',
00654 'min' => 4,
00655 'max' => 1000,
00656 );
00657
00658 $defaultPreferences['editfont'] =
00659 array(
00660 'type' => 'select',
00661 'section' => 'editing/advancedediting',
00662 'label-message' => 'editfont-style',
00663 'options' => array(
00664 wfMsg( 'editfont-default' ) => 'default',
00665 wfMsg( 'editfont-monospace' ) => 'monospace',
00666 wfMsg( 'editfont-sansserif' ) => 'sans-serif',
00667 wfMsg( 'editfont-serif' ) => 'serif',
00668 )
00669 );
00670 $defaultPreferences['previewontop'] =
00671 array(
00672 'type' => 'toggle',
00673 'section' => 'editing/advancedediting',
00674 'label-message' => 'tog-previewontop',
00675 );
00676 $defaultPreferences['previewonfirst'] =
00677 array(
00678 'type' => 'toggle',
00679 'section' => 'editing/advancedediting',
00680 'label-message' => 'tog-previewonfirst',
00681 );
00682 $defaultPreferences['editsection'] =
00683 array(
00684 'type' => 'toggle',
00685 'section' => 'editing/advancedediting',
00686 'label-message' => 'tog-editsection',
00687 );
00688 $defaultPreferences['editsectiononrightclick'] =
00689 array(
00690 'type' => 'toggle',
00691 'section' => 'editing/advancedediting',
00692 'label-message' => 'tog-editsectiononrightclick',
00693 );
00694 $defaultPreferences['editondblclick'] =
00695 array(
00696 'type' => 'toggle',
00697 'section' => 'editing/advancedediting',
00698 'label-message' => 'tog-editondblclick',
00699 );
00700 $defaultPreferences['editwidth'] =
00701 array(
00702 'type' => 'toggle',
00703 'section' => 'editing/advancedediting',
00704 'label-message' => 'tog-editwidth',
00705 );
00706 $defaultPreferences['showtoolbar'] =
00707 array(
00708 'type' => 'toggle',
00709 'section' => 'editing/advancedediting',
00710 'label-message' => 'tog-showtoolbar',
00711 );
00712 $defaultPreferences['minordefault'] =
00713 array(
00714 'type' => 'toggle',
00715 'section' => 'editing/advancedediting',
00716 'label-message' => 'tog-minordefault',
00717 );
00718
00719 if ( $wgUseExternalEditor ) {
00720 $defaultPreferences['externaleditor'] =
00721 array(
00722 'type' => 'toggle',
00723 'section' => 'editing/advancedediting',
00724 'label-message' => 'tog-externaleditor',
00725 );
00726 $defaultPreferences['externaldiff'] =
00727 array(
00728 'type' => 'toggle',
00729 'section' => 'editing/advancedediting',
00730 'label-message' => 'tog-externaldiff',
00731 );
00732 }
00733
00734 $defaultPreferences['forceeditsummary'] =
00735 array(
00736 'type' => 'toggle',
00737 'section' => 'editing/advancedediting',
00738 'label-message' => 'tog-forceeditsummary',
00739 );
00740 if ( $wgLivePreview ) {
00741 $defaultPreferences['uselivepreview'] =
00742 array(
00743 'type' => 'toggle',
00744 'section' => 'editing/advancedediting',
00745 'label-message' => 'tog-uselivepreview',
00746 );
00747 }
00748 }
00749
00750 static function rcPreferences( $user, &$defaultPreferences ) {
00751 global $wgRCMaxAge, $wgUseRCPatrol, $wgLang;
00752 ## RecentChanges #####################################
00753 $defaultPreferences['rcdays'] =
00754 array(
00755 'type' => 'float',
00756 'label-message' => 'recentchangesdays',
00757 'section' => 'rc/display',
00758 'min' => 1,
00759 'max' => ceil( $wgRCMaxAge / ( 3600*24 ) ),
00760 'help' => wfMsgExt( 'recentchangesdays-max', array( 'parsemag' ), $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600*24 ) ) ) ),
00761 );
00762 $defaultPreferences['rclimit'] =
00763 array(
00764 'type' => 'int',
00765 'label-message' => 'recentchangescount',
00766 'help-message' => 'prefs-help-recentchangescount',
00767 'section' => 'rc/display',
00768 );
00769 $defaultPreferences['usenewrc'] =
00770 array(
00771 'type' => 'toggle',
00772 'label-message' => 'tog-usenewrc',
00773 'section' => 'rc/advancedrc',
00774 );
00775 $defaultPreferences['hideminor'] =
00776 array(
00777 'type' => 'toggle',
00778 'label-message' => 'tog-hideminor',
00779 'section' => 'rc/advancedrc',
00780 );
00781
00782 if( $wgUseRCPatrol ) {
00783 $defaultPreferences['hidepatrolled'] =
00784 array(
00785 'type' => 'toggle',
00786 'section' => 'rc/advancedrc',
00787 'label-message' => 'tog-hidepatrolled',
00788 );
00789 $defaultPreferences['newpageshidepatrolled'] =
00790 array(
00791 'type' => 'toggle',
00792 'section' => 'rc/advancedrc',
00793 'label-message' => 'tog-newpageshidepatrolled',
00794 );
00795 }
00796
00797 global $wgRCShowWatchingUsers;
00798 if( $wgRCShowWatchingUsers ) {
00799 $defaultPreferences['shownumberswatching'] =
00800 array(
00801 'type' => 'toggle',
00802 'section' => 'rc/advancedrc',
00803 'label-message' => 'tog-shownumberswatching',
00804 );
00805 }
00806 }
00807
00808 static function watchlistPreferences( $user, &$defaultPreferences ) {
00809 global $wgUseRCPatrol, $wgEnableAPI;
00810 ## Watchlist #####################################
00811 $defaultPreferences['watchlistdays'] =
00812 array(
00813 'type' => 'float',
00814 'min' => 0,
00815 'max' => 7,
00816 'section' => 'watchlist/display',
00817 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
00818 'label-message' => 'prefs-watchlist-days',
00819 );
00820 $defaultPreferences['wllimit'] =
00821 array(
00822 'type' => 'int',
00823 'min' => 0,
00824 'max' => 1000,
00825 'label-message' => 'prefs-watchlist-edits',
00826 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
00827 'section' => 'watchlist/display',
00828 );
00829 $defaultPreferences['extendwatchlist'] =
00830 array(
00831 'type' => 'toggle',
00832 'section' => 'watchlist/advancedwatchlist',
00833 'label-message' => 'tog-extendwatchlist',
00834 );
00835 $defaultPreferences['watchlisthideminor'] =
00836 array(
00837 'type' => 'toggle',
00838 'section' => 'watchlist/advancedwatchlist',
00839 'label-message' => 'tog-watchlisthideminor',
00840 );
00841 $defaultPreferences['watchlisthidebots'] =
00842 array(
00843 'type' => 'toggle',
00844 'section' => 'watchlist/advancedwatchlist',
00845 'label-message' => 'tog-watchlisthidebots',
00846 );
00847 $defaultPreferences['watchlisthideown'] =
00848 array(
00849 'type' => 'toggle',
00850 'section' => 'watchlist/advancedwatchlist',
00851 'label-message' => 'tog-watchlisthideown',
00852 );
00853 $defaultPreferences['watchlisthideanons'] =
00854 array(
00855 'type' => 'toggle',
00856 'section' => 'watchlist/advancedwatchlist',
00857 'label-message' => 'tog-watchlisthideanons',
00858 );
00859 $defaultPreferences['watchlisthideliu'] =
00860 array(
00861 'type' => 'toggle',
00862 'section' => 'watchlist/advancedwatchlist',
00863 'label-message' => 'tog-watchlisthideliu',
00864 );
00865 if ( $wgEnableAPI ) {
00866 # Some random gibberish as a proposed default
00867 $hash = sha1( mt_rand() . microtime( true ) );
00868 $defaultPreferences['watchlisttoken'] =
00869 array(
00870 'type' => 'text',
00871 'section' => 'watchlist/advancedwatchlist',
00872 'label-message' => 'prefs-watchlist-token',
00873 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
00874 );
00875 }
00876
00877 if ( $wgUseRCPatrol ) {
00878 $defaultPreferences['watchlisthidepatrolled'] =
00879 array(
00880 'type' => 'toggle',
00881 'section' => 'watchlist/advancedwatchlist',
00882 'label-message' => 'tog-watchlisthidepatrolled',
00883 );
00884 }
00885
00886 $watchTypes = array(
00887 'edit' => 'watchdefault',
00888 'move' => 'watchmoves',
00889 'delete' => 'watchdeletion'
00890 );
00891
00892
00893 if( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
00894 $watchTypes['read'] = 'watchcreations';
00895 }
00896
00897 foreach( $watchTypes as $action => $pref ) {
00898 if ( $user->isAllowed( $action ) ) {
00899 $defaultPreferences[$pref] = array(
00900 'type' => 'toggle',
00901 'section' => 'watchlist/advancedwatchlist',
00902 'label-message' => "tog-$pref",
00903 );
00904 }
00905 }
00906 }
00907
00908 static function searchPreferences( $user, &$defaultPreferences ) {
00909 global $wgContLang;
00910
00911 ## Search #####################################
00912 $defaultPreferences['searchlimit'] =
00913 array(
00914 'type' => 'int',
00915 'label-message' => 'resultsperpage',
00916 'section' => 'searchoptions/display',
00917 'min' => 0,
00918 );
00919 $defaultPreferences['contextlines'] =
00920 array(
00921 'type' => 'int',
00922 'label-message' => 'contextlines',
00923 'section' => 'searchoptions/display',
00924 'min' => 0,
00925 );
00926 $defaultPreferences['contextchars'] =
00927 array(
00928 'type' => 'int',
00929 'label-message' => 'contextchars',
00930 'section' => 'searchoptions/display',
00931 'min' => 0,
00932 );
00933 global $wgEnableMWSuggest;
00934 if( $wgEnableMWSuggest ) {
00935 $defaultPreferences['disablesuggest'] =
00936 array(
00937 'type' => 'toggle',
00938 'label-message' => 'mwsuggest-disable',
00939 'section' => 'searchoptions/display',
00940 );
00941 }
00942
00943 $defaultPreferences['searcheverything'] =
00944 array(
00945 'type' => 'toggle',
00946 'label-message' => 'searcheverything-enable',
00947 'section' => 'searchoptions/advancedsearchoptions',
00948 );
00949
00950
00951 $searchableNamespaces = SearchEngine::searchableNamespaces();
00952
00953 $nsOptions = array();
00954 foreach( $wgContLang->getNamespaces() as $ns => $name ) {
00955 if( $ns < 0 ) continue;
00956 $displayNs = str_replace( '_', ' ', $name );
00957
00958 if( !$displayNs ) $displayNs = wfMsg( 'blanknamespace' );
00959
00960 $displayNs = htmlspecialchars( $displayNs );
00961 $nsOptions[$displayNs] = $ns;
00962 }
00963
00964 $defaultPreferences['searchnamespaces'] =
00965 array(
00966 'type' => 'multiselect',
00967 'label-message' => 'defaultns',
00968 'options' => $nsOptions,
00969 'section' => 'searchoptions/advancedsearchoptions',
00970 'prefix' => 'searchNs',
00971 );
00972 }
00973
00974 static function miscPreferences( $user, &$defaultPreferences ) {
00975 ## Misc #####################################
00976 $defaultPreferences['diffonly'] =
00977 array(
00978 'type' => 'toggle',
00979 'section' => 'misc/diffs',
00980 'label-message' => 'tog-diffonly',
00981 );
00982 $defaultPreferences['norollbackdiff'] =
00983 array(
00984 'type' => 'toggle',
00985 'section' => 'misc/diffs',
00986 'label-message' => 'tog-norollbackdiff',
00987 );
00988
00989
00990 global $wgContLang;
00991
00992 $toggles = $wgContLang->getExtraUserToggles();
00993
00994 foreach( $toggles as $toggle ) {
00995 $defaultPreferences[$toggle] =
00996 array(
00997 'type' => 'toggle',
00998 'section' => 'personal/i18n',
00999 'label-message' => "tog-$toggle",
01000 );
01001 }
01002 }
01003
01008 static function generateSkinOptions( $user ) {
01009 global $wgDefaultSkin, $wgLang, $wgAllowUserCss, $wgAllowUserJs;
01010 $ret = array();
01011
01012 $mptitle = Title::newMainPage();
01013 $previewtext = wfMsgHtml( 'skin-preview' );
01014 # Only show members of Skin::getSkinNames() rather than
01015 # $skinNames (skins is all skin names from Language.php)
01016 $validSkinNames = Skin::getUsableSkins();
01017 # Sort by UI skin name. First though need to update validSkinNames as sometimes
01018 # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
01019 foreach ( $validSkinNames as $skinkey => &$skinname ) {
01020 $msgName = "skinname-{$skinkey}";
01021 $localisedSkinName = wfMsg( $msgName );
01022 if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) {
01023 $skinname = htmlspecialchars( $localisedSkinName );
01024 }
01025 }
01026 asort( $validSkinNames );
01027 $sk = $user->getSkin();
01028
01029 foreach( $validSkinNames as $skinkey => $sn ) {
01030 $linkTools = array();
01031
01032 # Mark the default skin
01033 if( $skinkey == $wgDefaultSkin ) {
01034 $linkTools[] = wfMsgHtml( 'default' );
01035 }
01036
01037 # Create preview link
01038 $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
01039 $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>";
01040
01041 # Create links to user CSS/JS pages
01042 if( $wgAllowUserCss ) {
01043 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
01044 $linkTools[] = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
01045 }
01046 if( $wgAllowUserJs ) {
01047 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
01048 $linkTools[] = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
01049 }
01050
01051 $display = $sn . ' ' . wfMsg( 'parentheses', $wgLang->pipeList( $linkTools ) );
01052 $ret[$display] = $skinkey;
01053 }
01054
01055 return $ret;
01056 }
01057
01058 static function getDateOptions() {
01059 global $wgLang;
01060 $dateopts = $wgLang->getDatePreferences();
01061
01062 $ret = array();
01063
01064 if( $dateopts ) {
01065 if ( !in_array( 'default', $dateopts ) ) {
01066 $dateopts[] = 'default';
01067
01068 }
01069
01070 $idCnt = 0;
01071 $epoch = wfTimestampNow();
01072 foreach( $dateopts as $key ) {
01073 if( $key == 'default' ) {
01074 $formatted = wfMsgHtml( 'datedefault' );
01075 } else {
01076 $formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
01077 }
01078 $ret[$formatted] = $key;
01079 }
01080 }
01081 return $ret;
01082 }
01083
01084 static function getImageSizes() {
01085 global $wgImageLimits;
01086
01087 $ret = array();
01088
01089 foreach ( $wgImageLimits as $index => $limits ) {
01090 $display = "{$limits[0]}×{$limits[1]}" . wfMsg( 'unit-pixel' );
01091 $ret[$display] = $index;
01092 }
01093
01094 return $ret;
01095 }
01096
01097 static function getThumbSizes() {
01098 global $wgThumbLimits;
01099
01100 $ret = array();
01101
01102 foreach ( $wgThumbLimits as $index => $size ) {
01103 $display = $size . wfMsg( 'unit-pixel' );
01104 $ret[$display] = $index;
01105 }
01106
01107 return $ret;
01108 }
01109
01110 static function validateSignature( $signature, $alldata ) {
01111 global $wgParser, $wgMaxSigChars, $wgLang;
01112 if( mb_strlen( $signature ) > $wgMaxSigChars ) {
01113 return
01114 Xml::element( 'span', array( 'class' => 'error' ),
01115 wfMsgExt( 'badsiglength', 'parsemag',
01116 $wgLang->formatNum( $wgMaxSigChars )
01117 )
01118 );
01119 } elseif( !empty( $alldata['fancysig'] ) &&
01120 false === $wgParser->validateSig( $signature ) ) {
01121 return Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) );
01122 } else {
01123 return true;
01124 }
01125 }
01126
01127 static function cleanSignature( $signature, $alldata ) {
01128 global $wgParser;
01129 if( $alldata['fancysig'] ) {
01130 $signature = $wgParser->cleanSig( $signature );
01131 } else {
01132
01133 $signature = $wgParser->cleanSigInSig( $signature );
01134 }
01135
01136 return $signature;
01137 }
01138
01139 static function validateEmail( $email, $alldata ) {
01140 if ( $email && !User::isValidEmailAddr( $email ) ) {
01141 return wfMsgExt( 'invalidemailaddress', 'parseinline' );
01142 }
01143
01144 global $wgEmailConfirmToEdit;
01145 if( $wgEmailConfirmToEdit && !$email ) {
01146 return wfMsgExt( 'noemailtitle', 'parseinline' );
01147 }
01148 return true;
01149 }
01150
01151 static function getFormObject( $user ) {
01152 $formDescriptor = Preferences::getPreferences( $user );
01153 $htmlForm = new PreferencesForm( $formDescriptor, 'prefs' );
01154
01155 $htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
01156 $htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
01157 $htmlForm->setSubmitID( 'prefsubmit' );
01158 $htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
01159
01160 return $htmlForm;
01161 }
01162
01163 static function getTimezoneOptions() {
01164 $opt = array();
01165
01166 global $wgLocalTZoffset;
01167
01168 $opt[wfMsg( 'timezoneuseserverdefault' )] = "System|$wgLocalTZoffset";
01169 $opt[wfMsg( 'timezoneuseoffset' )] = 'other';
01170 $opt[wfMsg( 'guesstimezone' )] = 'guess';
01171
01172 if ( function_exists( 'timezone_identifiers_list' ) ) {
01173 # Read timezone list
01174 $tzs = timezone_identifiers_list();
01175 sort( $tzs );
01176
01177 $tzRegions = array();
01178 $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' );
01179 $tzRegions['America'] = wfMsg( 'timezoneregion-america' );
01180 $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' );
01181 $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' );
01182 $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' );
01183 $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' );
01184 $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' );
01185 $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' );
01186 $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' );
01187 $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' );
01188 asort( $tzRegions );
01189
01190 $prefill = array_fill_keys( array_values( $tzRegions ), array() );
01191 $opt = array_merge( $opt, $prefill );
01192
01193 $now = date_create( 'now' );
01194
01195 foreach ( $tzs as $tz ) {
01196 $z = explode( '/', $tz, 2 );
01197
01198 # timezone_identifiers_list() returns a number of
01199 # backwards-compatibility entries. This filters them out of the
01200 # list presented to the user.
01201 if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) )
01202 continue;
01203
01204 # Localize region
01205 $z[0] = $tzRegions[$z[0]];
01206
01207 $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
01208
01209 $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
01210 $value = "ZoneInfo|$minDiff|$tz";
01211
01212 $opt[$z[0]][$display] = $value;
01213 }
01214 }
01215 return $opt;
01216 }
01217
01218 static function filterTimezoneInput( $tz, $alldata ) {
01219 $data = explode( '|', $tz, 3 );
01220 switch ( $data[0] ) {
01221 case 'ZoneInfo':
01222 case 'System':
01223 return $tz;
01224 default:
01225 $data = explode( ':', $tz, 2 );
01226 $minDiff = 0;
01227 if( count( $data ) == 2 ) {
01228 $data[0] = intval( $data[0] );
01229 $data[1] = intval( $data[1] );
01230 $minDiff = abs( $data[0] ) * 60 + $data[1];
01231 if ( $data[0] < 0 ) $minDiff = -$minDiff;
01232 } else {
01233 $minDiff = intval( $data[0] ) * 60;
01234 }
01235
01236 # Max is +14:00 and min is -12:00, see:
01237 # http://en.wikipedia.org/wiki/Timezone
01238 $minDiff = min( $minDiff, 840 ); # 14:00
01239 $minDiff = max( $minDiff, -720 ); # -12:00
01240 return 'Offset|'.$minDiff;
01241 }
01242 }
01243
01244 static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
01245 global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
01246
01247 $result = true;
01248
01249
01250 foreach( array_keys( $formData ) as $name ) {
01251 if ( isset( self::$saveFilters[$name] ) ) {
01252 $formData[$name] =
01253 call_user_func( self::$saveFilters[$name], $formData[$name], $formData );
01254 }
01255 }
01256
01257
01258 $saveBlacklist = array(
01259 'realname',
01260 'emailaddress',
01261 );
01262
01263 if( $wgEnableEmail ) {
01264 $newadr = $formData['emailaddress'];
01265 $oldadr = $wgUser->getEmail();
01266 if( ( $newadr != '' ) && ( $newadr != $oldadr ) ) {
01267 # the user has supplied a new email address on the login page
01268 # new behaviour: set this new emailaddr from login-page into user database record
01269 $wgUser->setEmail( $newadr );
01270 # but flag as "dirty" = unauthenticated
01271 $wgUser->invalidateEmail();
01272 if( $wgEmailAuthentication ) {
01273 # Mail a temporary password to the dirty address.
01274 # User can come back through the confirmation URL to re-enable email.
01275 $result = $wgUser->sendConfirmationMail();
01276 if( WikiError::isError( $result ) ) {
01277 return wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
01278 } elseif( $entryPoint == 'ui' ) {
01279 $result = 'eauth';
01280 }
01281 }
01282 } else {
01283 $wgUser->setEmail( $newadr );
01284 }
01285 if( $oldadr != $newadr ) {
01286 wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
01287 }
01288 }
01289
01290
01291 global $wgHiddenPrefs;
01292 if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
01293 $realName = $formData['realname'];
01294 $wgUser->setRealName( $realName );
01295 }
01296
01297 foreach( $saveBlacklist as $b )
01298 unset( $formData[$b] );
01299
01300
01301
01302 $wgUser->resetOptions();
01303
01304 foreach( $formData as $key => $value ) {
01305 $wgUser->setOption( $key, $value );
01306 }
01307
01308 $wgUser->saveSettings();
01309
01310 return $result;
01311 }
01312
01313 public static function tryUISubmit( $formData ) {
01314 $res = self::tryFormSubmit( $formData, 'ui' );
01315
01316 if( $res ) {
01317 $urlOptions = array( 'success' );
01318 if( $res === 'eauth' )
01319 $urlOptions[] = 'eauth';
01320
01321 $queryString = implode( '&', $urlOptions );
01322
01323 $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
01324 global $wgOut;
01325 $wgOut->redirect( $url );
01326 }
01327
01328 return true;
01329 }
01330
01331 public static function loadOldSearchNs( $user ) {
01332 $searchableNamespaces = SearchEngine::searchableNamespaces();
01333
01334 $arr = array();
01335
01336 foreach( $searchableNamespaces as $ns => $name ) {
01337 if( $user->getOption( 'searchNs' . $ns ) ) {
01338 $arr[] = $ns;
01339 }
01340 }
01341
01342 return $arr;
01343 }
01344 }
01345
01347 class PreferencesForm extends HTMLForm {
01348
01349 function wrapForm( $html ) {
01350 $html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
01351
01352 return parent::wrapForm( $html );
01353 }
01354
01355 function getButtons() {
01356 $html = parent::getButtons();
01357
01358 global $wgUser;
01359
01360 $sk = $wgUser->getSkin();
01361 $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
01362
01363 $html .= "\n" . $sk->link( $t, wfMsgHtml( 'restoreprefs' ) );
01364
01365 $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
01366
01367 return $html;
01368 }
01369
01370 function filterDataForSubmit( $data ) {
01371
01372
01373 foreach( $this->mFlatFields as $fieldname => $field ) {
01374 $info = $field->mParams;
01375 if( $field instanceof HTMLMultiSelectField ) {
01376 $options = HTMLFormField::flattenOptions( $info['options'] );
01377 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
01378
01379 foreach( $options as $opt ) {
01380 $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
01381 }
01382
01383 unset( $data[$fieldname] );
01384 }
01385 }
01386
01387 return $data;
01388 }
01389 }