RELEASE-NOTES typo
[mediawiki.git] / includes / Preferences.php
blob9553a401aee5ace33ec31da1070a2947e085931d
1 <?php
3 /**
4 General information about this file:
5 We're now using the HTMLForm object with some customisation to generate the Preferences
6 form. This object handles generic submission, CSRF protection, layout and other logic
7 in a reusable manner. We subclass it as a PreferencesForm to make some minor
8 customisations.
9 In order to generate the form, the HTMLForm object needs an array structure detailing the
10 form fields available, and that's what this class is for. Each element of the array is
11 a basic property-list, including the type of field, the label it is to be given in the
12 form, callbacks for validation and 'filtering', and other pertinent information. Note that
13 the 'default' field is named for generic forms, and does not represent the preference's
14 default (which is stored in $wgDefaultUserOptions), but the default for the form field,
15 which should be whatever the user has set for that preference. There is no need to
16 override it unless you have some special storage logic (for instance, those not presently
17 stored as options, but which are best set from the user preferences view).
18 Field types are implemented as subclasses of the generic HTMLFormField object, and
19 typically implement at least getInputHTML, which generates the HTML for the input field
20 to be placed in the table.
21 Once fields have been retrieved and validated, submission logic is handed over to the
22 tryUISubmit static method of this class.
25 class Preferences {
26 static $defaultPreferences = null;
27 static $saveFilters =
28 array(
29 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
32 static function getPreferences( $user ) {
33 if ( self::$defaultPreferences )
34 return self::$defaultPreferences;
36 global $wgRCMaxAge;
38 $defaultPreferences = array();
40 self::profilePreferences( $user, $defaultPreferences );
41 self::skinPreferences( $user, $defaultPreferences );
42 self::filesPreferences( $user, $defaultPreferences );
43 self::mathPreferences( $user, $defaultPreferences );
44 self::datetimePreferences( $user, $defaultPreferences );
45 self::renderingPreferences( $user, $defaultPreferences );
46 self::editingPreferences( $user, $defaultPreferences );
47 self::rcPreferences( $user, $defaultPreferences );
48 self::watchlistPreferences( $user, $defaultPreferences );
49 self::searchPreferences( $user, $defaultPreferences );
50 self::miscPreferences( $user, $defaultPreferences );
52 wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
54 ## Remove preferences that wikis don't want to use
55 global $wgHiddenPrefs;
56 foreach ( $wgHiddenPrefs as $pref ) {
57 if ( isset( $defaultPreferences[$pref] ) ) {
58 unset( $defaultPreferences[$pref] );
62 ## Prod in defaults from the user
63 global $wgDefaultUserOptions;
64 foreach( $defaultPreferences as $name => &$info ) {
65 $prefFromUser = self::getOptionFromUser( $name, $info, $user );
66 $field = HTMLForm::loadInputFromParameters( $info ); // For validation
67 $defaultOptions = User::getDefaultOptions();
68 $globalDefault = isset( $defaultOptions[$name] )
69 ? $defaultOptions[$name]
70 : null;
72 // If it validates, set it as the default
73 if ( isset( $info['default'] ) ) {
74 // Already set, no problem
75 continue;
76 } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing
77 $field->validate( $prefFromUser, $user->mOptions ) === true ) {
78 $info['default'] = $prefFromUser;
79 } elseif( $field->validate( $globalDefault, $user->mOptions ) === true ) {
80 $info['default'] = $globalDefault;
81 } else {
82 throw new MWException( "Global default '$globalDefault' is invalid for field $name" );
86 self::$defaultPreferences = $defaultPreferences;
88 return $defaultPreferences;
91 // Pull option from a user account. Handles stuff like array-type preferences.
92 static function getOptionFromUser( $name, $info, $user ) {
93 $val = $user->getOption( $name );
95 // Handling for array-type preferences
96 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
97 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
99 $options = HTMLFormField::flattenOptions( $info['options'] );
100 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
101 $val = array();
103 foreach( $options as $label => $value ) {
104 if( $user->getOption( "$prefix$value" ) ) {
105 $val[] = $value;
110 return $val;
113 static function profilePreferences( $user, &$defaultPreferences ) {
114 global $wgLang;
115 ## User info #####################################
116 // Information panel
117 $defaultPreferences['username'] =
118 array(
119 'type' => 'info',
120 'label-message' => 'username',
121 'default' => $user->getName(),
122 'section' => 'personal/info',
125 $defaultPreferences['userid'] =
126 array(
127 'type' => 'info',
128 'label-message' => 'uid',
129 'default' => $user->getId(),
130 'section' => 'personal/info',
133 # Get groups to which the user belongs
134 $userEffectiveGroups = $user->getEffectiveGroups();
135 $userGroups = $userMembers = array();
136 foreach( $userEffectiveGroups as $ueg ) {
137 if( $ueg == '*' ) {
138 // Skip the default * group, seems useless here
139 continue;
141 $groupName = User::getGroupName( $ueg );
142 $userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
144 $memberName = User::getGroupMember( $ueg );
145 $userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
147 asort( $userGroups );
148 asort( $userMembers );
150 $defaultPreferences['usergroups'] =
151 array(
152 'type' => 'info',
153 'label' => wfMsgExt( 'prefs-memberingroups', 'parseinline',
154 $wgLang->formatNum( count($userGroups) ) ),
155 'default' => wfMsgExt( 'prefs-memberingroups-type', array(),
156 $wgLang->commaList( $userGroups ),
157 $wgLang->commaList( $userMembers )
159 'raw' => true,
160 'section' => 'personal/info',
163 $defaultPreferences['editcount'] =
164 array(
165 'type' => 'info',
166 'label-message' => 'prefs-edits',
167 'default' => $wgLang->formatNum( $user->getEditCount() ),
168 'section' => 'personal/info',
171 if( $user->getRegistration() ) {
172 $defaultPreferences['registrationdate'] =
173 array(
174 'type' => 'info',
175 'label-message' => 'prefs-registration',
176 'default' => wfMsgExt( 'prefs-registration-date-time', 'parsemag',
177 $wgLang->timeanddate( $user->getRegistration(), true ),
178 $wgLang->date( $user->getRegistration(), true ),
179 $wgLang->time( $user->getRegistration(), true ) ),
180 'section' => 'personal/info',
184 // Actually changeable stuff
185 global $wgAuth;
186 $defaultPreferences['realname'] =
187 array(
188 'type' => $wgAuth->allowPropChange( 'realname' ) ? 'text' : 'info',
189 'default' => $user->getRealName(),
190 'section' => 'personal/info',
191 'label-message' => 'yourrealname',
192 'help-message' => 'prefs-help-realname',
195 $defaultPreferences['gender'] =
196 array(
197 'type' => 'select',
198 'section' => 'personal/info',
199 'options' => array(
200 wfMsg( 'gender-male' ) => 'male',
201 wfMsg( 'gender-female' ) => 'female',
202 wfMsg( 'gender-unknown' ) => 'unknown',
204 'label-message' => 'yourgender',
205 'help-message' => 'prefs-help-gender',
208 if( $wgAuth->allowPasswordChange() ) {
209 global $wgUser; // For skin.
210 $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Resetpass' ),
211 wfMsgHtml( 'prefs-resetpass' ), array(),
212 array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' ) ) );
214 $defaultPreferences['password'] =
215 array(
216 'type' => 'info',
217 'raw' => true,
218 'default' => $link,
219 'label-message' => 'yourpassword',
220 'section' => 'personal/info',
224 $defaultPreferences['rememberpassword'] =
225 array(
226 'type' => 'toggle',
227 'label-message' => 'tog-rememberpassword',
228 'section' => 'personal/info',
231 // Language
232 global $wgContLanguageCode;
233 $languages = array_reverse( Language::getLanguageNames( false ) );
234 if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
235 $languages[$wgContLanguageCode] = $wgContLanguageCode;
237 ksort( $languages );
239 $options = array();
240 foreach( $languages as $code => $name ) {
241 $display = wfBCP47( $code ) . ' - ' . $name;
242 $options[$display] = $code;
244 $defaultPreferences['language'] =
245 array(
246 'type' => 'select',
247 'section' => 'personal/i18n',
248 'options' => $options,
249 'label-message' => 'yourlanguage',
252 global $wgContLang, $wgDisableLangConversion;
253 global $wgDisableTitleConversion;
254 /* see if there are multiple language variants to choose from*/
255 $variantArray = array();
256 if( !$wgDisableLangConversion ) {
257 $variants = $wgContLang->getVariants();
259 $languages = Language::getLanguageNames( true );
260 foreach( $variants as $v ) {
261 $v = str_replace( '_', '-', strtolower( $v ) );
262 if( array_key_exists( $v, $languages ) ) {
263 // If it doesn't have a name, we'll pretend it doesn't exist
264 $variantArray[$v] = $languages[$v];
268 $options = array();
269 foreach( $variantArray as $code => $name ) {
270 $display = wfBCP47( $code ) . ' - ' . $name;
271 $options[$display] = $code;
274 if( count( $variantArray ) > 1 ) {
275 $defaultPreferences['variant'] =
276 array(
277 'label-message' => 'yourvariant',
278 'type' => 'select',
279 'options' => $options,
280 'section' => 'personal/i18n',
285 if( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
286 $defaultPreferences['noconvertlink'] =
287 array(
288 'type' => 'toggle',
289 'section' => 'personal/i18n',
290 'label-message' => 'tog-noconvertlink',
294 global $wgMaxSigChars, $wgOut, $wgParser;
296 // show a preview of the old signature first
297 $oldsigWikiText = $wgParser->preSaveTransform( "~~~", new Title , $user, new ParserOptions );
298 $oldsigHTML = $wgOut->parseInline( $oldsigWikiText );
299 $defaultPreferences['oldsig'] =
300 array(
301 'type' => 'info',
302 'raw' => true,
303 'label-message' => 'tog-oldsig',
304 'default' => $oldsigHTML,
305 'section' => 'personal/signature',
307 $defaultPreferences['nickname'] =
308 array(
309 'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
310 'maxlength' => $wgMaxSigChars,
311 'label-message' => 'yournick',
312 'validation-callback' =>
313 array( 'Preferences', 'validateSignature' ),
314 'section' => 'personal/signature',
315 'filter-callback' => array( 'Preferences', 'cleanSignature' ),
317 $defaultPreferences['fancysig'] =
318 array(
319 'type' => 'toggle',
320 'label-message' => 'tog-fancysig',
321 'help-message' => 'prefs-help-signature', // show general help about signature at the bottom of the section
322 'section' => 'personal/signature'
325 ## Email stuff
327 global $wgEnableEmail;
328 if ($wgEnableEmail) {
330 global $wgEmailConfirmToEdit;
332 $defaultPreferences['emailaddress'] =
333 array(
334 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'email' : 'info',
335 'default' => $user->getEmail(),
336 'section' => 'personal/email',
337 'label-message' => 'youremail',
338 'help-message' => $wgEmailConfirmToEdit
339 ? 'prefs-help-email-required'
340 : 'prefs-help-email',
341 'validation-callback' => array( 'Preferences', 'validateEmail' ),
344 global $wgEnableUserEmail, $wgEmailAuthentication;
346 $disableEmailPrefs = false;
348 if ( $wgEmailAuthentication ) {
349 if ( $user->getEmail() ) {
350 if( $user->getEmailAuthenticationTimestamp() ) {
351 // date and time are separate parameters to facilitate localisation.
352 // $time is kept for backward compat reasons.
353 // 'emailauthenticated' is also used in SpecialConfirmemail.php
354 $time = $wgLang->timeAndDate( $user->getEmailAuthenticationTimestamp(), true );
355 $d = $wgLang->date( $user->getEmailAuthenticationTimestamp(), true );
356 $t = $wgLang->time( $user->getEmailAuthenticationTimestamp(), true );
357 $emailauthenticated = wfMsgExt( 'emailauthenticated', 'parseinline',
358 array($time, $d, $t ) ) . '<br />';
359 $disableEmailPrefs = false;
360 } else {
361 $disableEmailPrefs = true;
362 global $wgUser; // wgUser is okay here, it's for display
363 $skin = $wgUser->getSkin();
364 $emailauthenticated = wfMsgExt( 'emailnotauthenticated', 'parseinline' ) . '<br />' .
365 $skin->link(
366 SpecialPage::getTitleFor( 'Confirmemail' ),
367 wfMsg( 'emailconfirmlink' ),
368 array(),
369 array(),
370 array( 'known', 'noclasses' )
371 ) . '<br />';
373 } else {
374 $disableEmailPrefs = true;
375 $emailauthenticated = wfMsgHtml( 'noemailprefs' );
378 $defaultPreferences['emailauthentication'] =
379 array(
380 'type' => 'info',
381 'raw' => true,
382 'section' => 'personal/email',
383 'label-message' => 'prefs-emailconfirm-label',
384 'default' => $emailauthenticated,
389 if( $wgEnableUserEmail ) {
390 $defaultPreferences['disablemail'] =
391 array(
392 'type' => 'toggle',
393 'invert' => true,
394 'section' => 'personal/email',
395 'label-message' => 'allowemail',
396 'disabled' => $disableEmailPrefs,
398 $defaultPreferences['ccmeonemails'] =
399 array(
400 'type' => 'toggle',
401 'section' => 'personal/email',
402 'label-message' => 'tog-ccmeonemails',
403 'disabled' => $disableEmailPrefs,
407 global $wgEnotifWatchlist;
408 if ( $wgEnotifWatchlist ) {
409 $defaultPreferences['enotifwatchlistpages'] =
410 array(
411 'type' => 'toggle',
412 'section' => 'personal/email',
413 'label-message' => 'tog-enotifwatchlistpages',
414 'disabled' => $disableEmailPrefs,
417 global $wgEnotifUserTalk;
418 if( $wgEnotifUserTalk ) {
419 $defaultPreferences['enotifusertalkpages'] =
420 array(
421 'type' => 'toggle',
422 'section' => 'personal/email',
423 'label-message' => 'tog-enotifusertalkpages',
424 'disabled' => $disableEmailPrefs,
427 if( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
428 $defaultPreferences['enotifminoredits'] =
429 array(
430 'type' => 'toggle',
431 'section' => 'personal/email',
432 'label-message' => 'tog-enotifminoredits',
433 'disabled' => $disableEmailPrefs,
436 $defaultPreferences['enotifrevealaddr'] =
437 array(
438 'type' => 'toggle',
439 'section' => 'personal/email',
440 'label-message' => 'tog-enotifrevealaddr',
441 'disabled' => $disableEmailPrefs,
446 static function skinPreferences( $user, &$defaultPreferences ) {
447 ## Skin #####################################
448 $defaultPreferences['skin'] =
449 array(
450 'type' => 'radio',
451 'options' => self::generateSkinOptions( $user ),
452 'label' => '&nbsp;',
453 'section' => 'rendering/skin',
456 $selectedSkin = $user->getOption( 'skin' );
457 if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
458 global $wgLang;
459 $settings = array_flip( $wgLang->getQuickbarSettings() );
461 $defaultPreferences['quickbar'] =
462 array(
463 'type' => 'radio',
464 'options' => $settings,
465 'section' => 'rendering/skin',
466 'label-message' => 'qbsettings',
471 static function mathPreferences( $user, &$defaultPreferences ) {
472 ## Math #####################################
473 global $wgUseTeX, $wgLang;
474 if( $wgUseTeX ) {
475 $defaultPreferences['math'] =
476 array(
477 'type' => 'radio',
478 'options' =>
479 array_flip( array_map( 'wfMsgHtml', $wgLang->getMathNames() ) ),
480 'label' => '&nbsp;',
481 'section' => 'rendering/math',
486 static function filesPreferences( $user, &$defaultPreferences ) {
487 ## Files #####################################
488 $defaultPreferences['imagesize'] =
489 array(
490 'type' => 'select',
491 'options' => self::getImageSizes(),
492 'label-message' => 'imagemaxsize',
493 'section' => 'rendering/files',
495 $defaultPreferences['thumbsize'] =
496 array(
497 'type' => 'select',
498 'options' => self::getThumbSizes(),
499 'label-message' => 'thumbsize',
500 'section' => 'rendering/files',
504 static function datetimePreferences( $user, &$defaultPreferences ) {
505 global $wgLang;
507 ## Date and time #####################################
508 $dateOptions = self::getDateOptions();
509 if( $dateOptions ) {
510 $defaultPreferences['date'] =
511 array(
512 'type' => 'radio',
513 'options' => $dateOptions,
514 'label' => '&nbsp;',
515 'section' => 'datetime/dateformat',
519 // Info
520 $nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
521 $wgLang->time( $now = wfTimestampNow(), true ) );
522 $nowserver = $wgLang->time( $now, false ) .
523 Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
525 $defaultPreferences['nowserver'] =
526 array(
527 'type' => 'info',
528 'raw' => 1,
529 'label-message' => 'servertime',
530 'default' => $nowserver,
531 'section' => 'datetime/timeoffset',
534 $defaultPreferences['nowlocal'] =
535 array(
536 'type' => 'info',
537 'raw' => 1,
538 'label-message' => 'localtime',
539 'default' => $nowlocal,
540 'section' => 'datetime/timeoffset',
543 // Grab existing pref.
544 $tzOffset = $user->getOption( 'timecorrection' );
545 $tz = explode( '|', $tzOffset, 2 );
547 $tzSetting = $tzOffset;
548 if( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
549 $minDiff = $tz[1];
550 $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff/60 ), abs( $minDiff )%60 );
553 $defaultPreferences['timecorrection'] =
554 array(
555 'class' => 'HTMLSelectOrOtherField',
556 'label-message' => 'timezonelegend',
557 'options' => self::getTimezoneOptions(),
558 'default' => $tzSetting,
559 'section' => 'datetime/timeoffset',
563 static function renderingPreferences( $user, &$defaultPreferences ) {
564 ## Page Rendering ##############################
565 $defaultPreferences['underline'] =
566 array(
567 'type' => 'select',
568 'options' => array(
569 wfMsg( 'underline-never' ) => 0,
570 wfMsg( 'underline-always' ) => 1,
571 wfMsg( 'underline-default' ) => 2,
573 'label-message' => 'tog-underline',
574 'section' => 'rendering/advancedrendering',
577 $stubThresholdValues = array( 0, 50, 100, 500, 1000, 2000, 5000, 10000 );
578 $stubThresholdOptions = array();
579 foreach( $stubThresholdValues as $value ) {
580 $stubThresholdOptions[wfMsg( 'size-bytes', $value )] = $value;
583 $defaultPreferences['stubthreshold'] =
584 array(
585 'type' => 'selectorother',
586 'section' => 'rendering/advancedrendering',
587 'options' => $stubThresholdOptions,
588 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
590 $defaultPreferences['highlightbroken'] =
591 array(
592 'type' => 'toggle',
593 'section' => 'rendering/advancedrendering',
594 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
596 $defaultPreferences['showtoc'] =
597 array(
598 'type' => 'toggle',
599 'section' => 'rendering/advancedrendering',
600 'label-message' => 'tog-showtoc',
602 $defaultPreferences['nocache'] =
603 array(
604 'type' => 'toggle',
605 'label-message' => 'tog-nocache',
606 'section' => 'rendering/advancedrendering',
608 $defaultPreferences['showhiddencats'] =
609 array(
610 'type' => 'toggle',
611 'section' => 'rendering/advancedrendering',
612 'label-message' => 'tog-showhiddencats'
614 $defaultPreferences['showjumplinks'] =
615 array(
616 'type' => 'toggle',
617 'section' => 'rendering/advancedrendering',
618 'label-message' => 'tog-showjumplinks',
620 $defaultPreferences['justify'] =
621 array(
622 'type' => 'toggle',
623 'section' => 'rendering/advancedrendering',
624 'label-message' => 'tog-justify',
626 $defaultPreferences['numberheadings'] =
627 array(
628 'type' => 'toggle',
629 'section' => 'rendering/advancedrendering',
630 'label-message' => 'tog-numberheadings',
634 static function editingPreferences( $user, &$defaultPreferences ) {
635 global $wgUseExternalEditor, $wgLivePreview;
637 ## Editing #####################################
638 $defaultPreferences['cols'] =
639 array(
640 'type' => 'int',
641 'label-message' => 'columns',
642 'section' => 'editing/textboxsize',
643 'min' => 4,
644 'max' => 1000,
646 $defaultPreferences['rows'] =
647 array(
648 'type' => 'int',
649 'label-message' => 'rows',
650 'section' => 'editing/textboxsize',
651 'min' => 4,
652 'max' => 1000,
655 $defaultPreferences['editfont'] =
656 array(
657 'type' => 'select',
658 'section' => 'editing/advancedediting',
659 'label-message' => 'editfont-style',
660 'options' => array(
661 wfMsg( 'editfont-default' ) => 'default',
662 wfMsg( 'editfont-monospace' ) => 'monospace',
663 wfMsg( 'editfont-sansserif' ) => 'sans-serif',
664 wfMsg( 'editfont-serif' ) => 'serif',
667 $defaultPreferences['previewontop'] =
668 array(
669 'type' => 'toggle',
670 'section' => 'editing/advancedediting',
671 'label-message' => 'tog-previewontop',
673 $defaultPreferences['previewonfirst'] =
674 array(
675 'type' => 'toggle',
676 'section' => 'editing/advancedediting',
677 'label-message' => 'tog-previewonfirst',
679 $defaultPreferences['editsection'] =
680 array(
681 'type' => 'toggle',
682 'section' => 'editing/advancedediting',
683 'label-message' => 'tog-editsection',
685 $defaultPreferences['editsectiononrightclick'] =
686 array(
687 'type' => 'toggle',
688 'section' => 'editing/advancedediting',
689 'label-message' => 'tog-editsectiononrightclick',
691 $defaultPreferences['editondblclick'] =
692 array(
693 'type' => 'toggle',
694 'section' => 'editing/advancedediting',
695 'label-message' => 'tog-editondblclick',
697 $defaultPreferences['editwidth'] =
698 array(
699 'type' => 'toggle',
700 'section' => 'editing/advancedediting',
701 'label-message' => 'tog-editwidth',
703 $defaultPreferences['showtoolbar'] =
704 array(
705 'type' => 'toggle',
706 'section' => 'editing/advancedediting',
707 'label-message' => 'tog-showtoolbar',
709 $defaultPreferences['minordefault'] =
710 array(
711 'type' => 'toggle',
712 'section' => 'editing/advancedediting',
713 'label-message' => 'tog-minordefault',
716 if ( $wgUseExternalEditor ) {
717 $defaultPreferences['externaleditor'] =
718 array(
719 'type' => 'toggle',
720 'section' => 'editing/advancedediting',
721 'label-message' => 'tog-externaleditor',
723 $defaultPreferences['externaldiff'] =
724 array(
725 'type' => 'toggle',
726 'section' => 'editing/advancedediting',
727 'label-message' => 'tog-externaldiff',
731 $defaultPreferences['forceeditsummary'] =
732 array(
733 'type' => 'toggle',
734 'section' => 'editing/advancedediting',
735 'label-message' => 'tog-forceeditsummary',
737 if ( $wgLivePreview ) {
738 $defaultPreferences['uselivepreview'] =
739 array(
740 'type' => 'toggle',
741 'section' => 'editing/advancedediting',
742 'label-message' => 'tog-uselivepreview',
747 static function rcPreferences( $user, &$defaultPreferences ) {
748 global $wgRCMaxAge, $wgUseRCPatrol, $wgLang;
749 ## RecentChanges #####################################
750 $defaultPreferences['rcdays'] =
751 array(
752 'type' => 'float',
753 'label-message' => 'recentchangesdays',
754 'section' => 'rc/display',
755 'min' => 1,
756 'max' => ceil( $wgRCMaxAge / ( 3600*24 ) ),
757 'help' => wfMsgExt( 'recentchangesdays-max', array( 'parsemag' ), $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600*24 ) ) ) ),
759 $defaultPreferences['rclimit'] =
760 array(
761 'type' => 'int',
762 'label-message' => 'recentchangescount',
763 'help-message' => 'prefs-help-recentchangescount',
764 'section' => 'rc/display',
766 $defaultPreferences['usenewrc'] =
767 array(
768 'type' => 'toggle',
769 'label-message' => 'tog-usenewrc',
770 'section' => 'rc/advancedrc',
772 $defaultPreferences['hideminor'] =
773 array(
774 'type' => 'toggle',
775 'label-message' => 'tog-hideminor',
776 'section' => 'rc/advancedrc',
779 global $wgUseRCPatrol;
780 if( $wgUseRCPatrol ) {
781 $defaultPreferences['hidepatrolled'] =
782 array(
783 'type' => 'toggle',
784 'section' => 'rc/advancedrc',
785 'label-message' => 'tog-hidepatrolled',
787 $defaultPreferences['newpageshidepatrolled'] =
788 array(
789 'type' => 'toggle',
790 'section' => 'rc/advancedrc',
791 'label-message' => 'tog-newpageshidepatrolled',
795 global $wgRCShowWatchingUsers;
796 if( $wgRCShowWatchingUsers ) {
797 $defaultPreferences['shownumberswatching'] =
798 array(
799 'type' => 'toggle',
800 'section' => 'rc/advancedrc',
801 'label-message' => 'tog-shownumberswatching',
806 static function watchlistPreferences( $user, &$defaultPreferences ) {
807 global $wgUseRCPatrol, $wgEnableAPI;
808 ## Watchlist #####################################
809 $defaultPreferences['watchlistdays'] =
810 array(
811 'type' => 'float',
812 'min' => 0,
813 'max' => 7,
814 'section' => 'watchlist/display',
815 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
816 'label-message' => 'prefs-watchlist-days',
818 $defaultPreferences['wllimit'] =
819 array(
820 'type' => 'int',
821 'min' => 0,
822 'max' => 1000,
823 'label-message' => 'prefs-watchlist-edits',
824 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
825 'section' => 'watchlist/display',
827 $defaultPreferences['extendwatchlist'] =
828 array(
829 'type' => 'toggle',
830 'section' => 'watchlist/advancedwatchlist',
831 'label-message' => 'tog-extendwatchlist',
833 $defaultPreferences['watchlisthideminor'] =
834 array(
835 'type' => 'toggle',
836 'section' => 'watchlist/advancedwatchlist',
837 'label-message' => 'tog-watchlisthideminor',
839 $defaultPreferences['watchlisthidebots'] =
840 array(
841 'type' => 'toggle',
842 'section' => 'watchlist/advancedwatchlist',
843 'label-message' => 'tog-watchlisthidebots',
845 $defaultPreferences['watchlisthideown'] =
846 array(
847 'type' => 'toggle',
848 'section' => 'watchlist/advancedwatchlist',
849 'label-message' => 'tog-watchlisthideown',
851 $defaultPreferences['watchlisthideanons'] =
852 array(
853 'type' => 'toggle',
854 'section' => 'watchlist/advancedwatchlist',
855 'label-message' => 'tog-watchlisthideanons',
857 $defaultPreferences['watchlisthideliu'] =
858 array(
859 'type' => 'toggle',
860 'section' => 'watchlist/advancedwatchlist',
861 'label-message' => 'tog-watchlisthideliu',
863 if ( $wgEnableAPI ) {
864 # Some random gibberish as a proposed default
865 $hash = sha1( mt_rand() . microtime( true ) );
866 $defaultPreferences['watchlisttoken'] =
867 array(
868 'type' => 'text',
869 'section' => 'watchlist/advancedwatchlist',
870 'label-message' => 'prefs-watchlist-token',
871 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
875 if ( $wgUseRCPatrol ) {
876 $defaultPreferences['watchlisthidepatrolled'] =
877 array(
878 'type' => 'toggle',
879 'section' => 'watchlist/advancedwatchlist',
880 'label-message' => 'tog-watchlisthidepatrolled',
884 $watchTypes = array(
885 'edit' => 'watchdefault',
886 'move' => 'watchmoves',
887 'delete' => 'watchdeletion'
890 // Kinda hacky
891 if( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
892 $watchTypes['read'] = 'watchcreations';
895 foreach( $watchTypes as $action => $pref ) {
896 if ( $user->isAllowed( $action ) ) {
897 $defaultPreferences[$pref] = array(
898 'type' => 'toggle',
899 'section' => 'watchlist/advancedwatchlist',
900 'label-message' => "tog-$pref",
906 static function searchPreferences( $user, &$defaultPreferences ) {
907 global $wgContLang;
909 ## Search #####################################
910 $defaultPreferences['searchlimit'] =
911 array(
912 'type' => 'int',
913 'label-message' => 'resultsperpage',
914 'section' => 'searchoptions/display',
915 'min' => 0,
917 $defaultPreferences['contextlines'] =
918 array(
919 'type' => 'int',
920 'label-message' => 'contextlines',
921 'section' => 'searchoptions/display',
922 'min' => 0,
924 $defaultPreferences['contextchars'] =
925 array(
926 'type' => 'int',
927 'label-message' => 'contextchars',
928 'section' => 'searchoptions/display',
929 'min' => 0,
931 global $wgEnableMWSuggest;
932 if( $wgEnableMWSuggest ) {
933 $defaultPreferences['disablesuggest'] =
934 array(
935 'type' => 'toggle',
936 'label-message' => 'mwsuggest-disable',
937 'section' => 'searchoptions/display',
941 $defaultPreferences['searcheverything'] =
942 array(
943 'type' => 'toggle',
944 'label-message' => 'searcheverything-enable',
945 'section' => 'searchoptions/advancedsearchoptions',
948 // Searchable namespaces back-compat with old format
949 $searchableNamespaces = SearchEngine::searchableNamespaces();
951 $nsOptions = array();
952 foreach( $wgContLang->getNamespaces() as $ns => $name ) {
953 if( $ns < 0 ) continue;
954 $displayNs = str_replace( '_', ' ', $name );
956 if( !$displayNs ) $displayNs = wfMsg( 'blanknamespace' );
958 $displayNs = htmlspecialchars( $displayNs );
959 $nsOptions[$displayNs] = $ns;
962 $defaultPreferences['searchnamespaces'] =
963 array(
964 'type' => 'multiselect',
965 'label-message' => 'defaultns',
966 'options' => $nsOptions,
967 'section' => 'searchoptions/advancedsearchoptions',
968 'prefix' => 'searchNs',
972 static function miscPreferences( $user, &$defaultPreferences ) {
973 ## Misc #####################################
974 $defaultPreferences['diffonly'] =
975 array(
976 'type' => 'toggle',
977 'section' => 'misc/diffs',
978 'label-message' => 'tog-diffonly',
980 $defaultPreferences['norollbackdiff'] =
981 array(
982 'type' => 'toggle',
983 'section' => 'misc/diffs',
984 'label-message' => 'tog-norollbackdiff',
987 // Stuff from Language::getExtraUserToggles()
988 global $wgContLang;
990 $toggles = $wgContLang->getExtraUserToggles();
992 foreach( $toggles as $toggle ) {
993 $defaultPreferences[$toggle] =
994 array(
995 'type' => 'toggle',
996 'section' => 'personal/i18n',
997 'label-message' => "tog-$toggle",
1002 static function generateSkinOptions( $user ) {
1003 global $wgDefaultSkin;
1004 $ret = array();
1006 $mptitle = Title::newMainPage();
1007 $previewtext = wfMsgHtml( 'skin-preview' );
1008 # Only show members of Skin::getSkinNames() rather than
1009 # $skinNames (skins is all skin names from Language.php)
1010 $validSkinNames = Skin::getUsableSkins();
1011 # Sort by UI skin name. First though need to update validSkinNames as sometimes
1012 # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
1013 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1014 $msgName = "skinname-{$skinkey}";
1015 $localisedSkinName = wfMsg( $msgName );
1016 if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) {
1017 $skinname = htmlspecialchars( $localisedSkinName );
1020 asort( $validSkinNames );
1021 $sk = $user->getSkin();
1023 foreach( $validSkinNames as $skinkey => $sn ) {
1024 $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
1025 $previewlink = "(<a target='_blank' href=\"$mplink\">$previewtext</a>)";
1026 $extraLinks = '';
1027 global $wgAllowUserCss, $wgAllowUserJs;
1028 if( $wgAllowUserCss ) {
1029 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1030 $customCSS = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
1031 $extraLinks .= " ($customCSS)";
1033 if( $wgAllowUserJs ) {
1034 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1035 $customJS = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
1036 $extraLinks .= " ($customJS)";
1038 if( $skinkey == $wgDefaultSkin )
1039 $sn .= ' (' . wfMsgHtml( 'default' ) . ')';
1040 $display = "$sn $previewlink{$extraLinks}";
1041 $ret[$display] = $skinkey;
1044 return $ret;
1047 static function getDateOptions() {
1048 global $wgLang;
1049 $dateopts = $wgLang->getDatePreferences();
1051 $ret = array();
1053 if( $dateopts ) {
1054 if ( !in_array( 'default', $dateopts ) ) {
1055 $dateopts[] = 'default'; // Make sure default is always valid
1056 // Bug 19237
1059 $idCnt = 0;
1060 $epoch = '20010115161234'; # Wikipedia day
1061 foreach( $dateopts as $key ) {
1062 if( $key == 'default' ) {
1063 $formatted = wfMsgHtml( 'datedefault' );
1064 } else {
1065 $formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
1067 $ret[$formatted] = $key;
1070 return $ret;
1073 static function getImageSizes() {
1074 global $wgImageLimits;
1076 $ret = array();
1078 foreach ( $wgImageLimits as $index => $limits ) {
1079 $display = "{$limits[0]}×{$limits[1]}" . wfMsg( 'unit-pixel' );
1080 $ret[$display] = $index;
1083 return $ret;
1086 static function getThumbSizes() {
1087 global $wgThumbLimits;
1089 $ret = array();
1091 foreach ( $wgThumbLimits as $index => $size ) {
1092 $display = $size . wfMsg( 'unit-pixel' );
1093 $ret[$display] = $index;
1096 return $ret;
1099 static function validateSignature( $signature, $alldata ) {
1100 global $wgParser, $wgMaxSigChars, $wgLang;
1101 if( mb_strlen( $signature ) > $wgMaxSigChars ) {
1102 return
1103 Xml::element( 'span', array( 'class' => 'error' ),
1104 wfMsgExt( 'badsiglength', 'parsemag',
1105 $wgLang->formatNum( $wgMaxSigChars )
1108 } elseif( !empty( $alldata['fancysig'] ) &&
1109 false === $wgParser->validateSig( $signature ) ) {
1110 return Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) );
1111 } else {
1112 return true;
1116 static function cleanSignature( $signature, $alldata ) {
1117 global $wgParser;
1118 if( $alldata['fancysig'] ) {
1119 $signature = $wgParser->cleanSig( $signature );
1120 } else {
1121 // When no fancy sig used, make sure ~{3,5} get removed.
1122 $signature = $wgParser->cleanSigInSig( $signature );
1125 return $signature;
1128 static function validateEmail( $email, $alldata ) {
1129 if ( $email && !User::isValidEmailAddr( $email ) ) {
1130 return wfMsgExt( 'invalidemailaddress', 'parseinline' );
1133 global $wgEmailConfirmToEdit;
1134 if( $wgEmailConfirmToEdit && !$email ) {
1135 return wfMsgExt( 'noemailtitle', 'parseinline' );
1137 return true;
1140 static function getFormObject( $user ) {
1141 $formDescriptor = Preferences::getPreferences( $user );
1142 $htmlForm = new PreferencesForm( $formDescriptor, 'prefs' );
1144 $htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
1145 $htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
1146 $htmlForm->setSubmitID( 'prefsubmit' );
1147 $htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
1149 return $htmlForm;
1152 static function getTimezoneOptions() {
1153 $opt = array();
1155 global $wgLocalTZoffset;
1157 $opt[wfMsg( 'timezoneuseserverdefault' )] = "System|$wgLocalTZoffset";
1158 $opt[wfMsg( 'timezoneuseoffset' )] = 'other';
1159 $opt[wfMsg( 'guesstimezone' )] = 'guess';
1161 if ( function_exists( 'timezone_identifiers_list' ) ) {
1162 # Read timezone list
1163 $tzs = timezone_identifiers_list();
1164 sort( $tzs );
1166 $tzRegions = array();
1167 $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' );
1168 $tzRegions['America'] = wfMsg( 'timezoneregion-america' );
1169 $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' );
1170 $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' );
1171 $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' );
1172 $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' );
1173 $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' );
1174 $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' );
1175 $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' );
1176 $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' );
1177 asort( $tzRegions );
1179 $prefill = array_fill_keys( array_values( $tzRegions ), array() );
1180 $opt = array_merge( $opt, $prefill );
1182 $now = date_create( 'now' );
1184 foreach ( $tzs as $tz ) {
1185 $z = explode( '/', $tz, 2 );
1187 # timezone_identifiers_list() returns a number of
1188 # backwards-compatibility entries. This filters them out of the
1189 # list presented to the user.
1190 if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) )
1191 continue;
1193 # Localize region
1194 $z[0] = $tzRegions[$z[0]];
1196 $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
1198 $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
1199 $value = "ZoneInfo|$minDiff|$tz";
1201 $opt[$z[0]][$display] = $value;
1204 return $opt;
1207 static function filterTimezoneInput( $tz, $alldata ) {
1208 $data = explode( '|', $tz, 3 );
1209 switch ( $data[0] ) {
1210 case 'ZoneInfo':
1211 case 'System':
1212 return $tz;
1213 default:
1214 $data = explode( ':', $tz, 2 );
1215 $minDiff = 0;
1216 if( count( $data ) == 2 ) {
1217 $data[0] = intval( $data[0] );
1218 $data[1] = intval( $data[1] );
1219 $minDiff = abs( $data[0] ) * 60 + $data[1];
1220 if ( $data[0] < 0 ) $minDiff = -$minDiff;
1221 } else {
1222 $minDiff = intval( $data[0] ) * 60;
1225 # Max is +14:00 and min is -12:00, see:
1226 # http://en.wikipedia.org/wiki/Timezone
1227 $minDiff = min( $minDiff, 840 ); # 14:00
1228 $minDiff = max( $minDiff, -720 ); # -12:00
1229 return 'Offset|'.$minDiff;
1233 static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
1234 global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
1236 $result = true;
1238 // Filter input
1239 foreach( array_keys( $formData ) as $name ) {
1240 if ( isset( self::$saveFilters[$name] ) ) {
1241 $formData[$name] =
1242 call_user_func( self::$saveFilters[$name], $formData[$name], $formData );
1246 // Stuff that shouldn't be saved as a preference.
1247 $saveBlacklist = array(
1248 'realname',
1249 'emailaddress',
1252 if( $wgEnableEmail ) {
1253 $newadr = $formData['emailaddress'];
1254 $oldadr = $wgUser->getEmail();
1255 if( ( $newadr != '' ) && ( $newadr != $oldadr ) ) {
1256 # the user has supplied a new email address on the login page
1257 # new behaviour: set this new emailaddr from login-page into user database record
1258 $wgUser->setEmail( $newadr );
1259 # but flag as "dirty" = unauthenticated
1260 $wgUser->invalidateEmail();
1261 if( $wgEmailAuthentication ) {
1262 # Mail a temporary password to the dirty address.
1263 # User can come back through the confirmation URL to re-enable email.
1264 $result = $wgUser->sendConfirmationMail();
1265 if( WikiError::isError( $result ) ) {
1266 return wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
1267 } elseif( $entryPoint == 'ui' ) {
1268 $result = 'eauth';
1271 } else {
1272 $wgUser->setEmail( $newadr );
1274 if( $oldadr != $newadr ) {
1275 wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
1279 // Fortunately, the realname field is MUCH simpler
1280 global $wgHiddenPrefs;
1281 if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
1282 $realName = $formData['realname'];
1283 $wgUser->setRealName( $realName );
1286 foreach( $saveBlacklist as $b )
1287 unset( $formData[$b] );
1289 // Keeps old preferences from interfering due to back-compat
1290 // code, etc.
1291 $wgUser->resetOptions();
1293 foreach( $formData as $key => $value ) {
1294 $wgUser->setOption( $key, $value );
1297 $wgUser->saveSettings();
1299 return $result;
1302 public static function tryUISubmit( $formData ) {
1303 $res = self::tryFormSubmit( $formData, 'ui' );
1305 if( $res ) {
1306 $urlOptions = array( 'success' );
1307 if( $res === 'eauth' )
1308 $urlOptions[] = 'eauth';
1310 $queryString = implode( '&', $urlOptions );
1312 $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
1313 global $wgOut;
1314 $wgOut->redirect( $url );
1317 return true;
1320 public static function loadOldSearchNs( $user ) {
1321 $searchableNamespaces = SearchEngine::searchableNamespaces();
1322 // Back compat with old format
1323 $arr = array();
1325 foreach( $searchableNamespaces as $ns => $name ) {
1326 if( $user->getOption( 'searchNs' . $ns ) ) {
1327 $arr[] = $ns;
1331 return $arr;
1335 /** Some tweaks to allow js prefs to work */
1336 class PreferencesForm extends HTMLForm {
1338 function wrapForm( $html ) {
1339 $html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
1341 return parent::wrapForm( $html );
1344 function getButtons() {
1345 $html = parent::getButtons();
1347 global $wgUser;
1349 $sk = $wgUser->getSkin();
1350 $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
1352 $html .= "\n" . $sk->link( $t, wfMsgHtml( 'restoreprefs' ) );
1354 $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
1356 return $html;
1359 function filterDataForSubmit( $data ) {
1360 // Support for separating MultiSelect preferences into multiple preferences
1361 // Due to lack of array support.
1362 foreach( $this->mFlatFields as $fieldname => $field ) {
1363 $info = $field->mParams;
1364 if( $field instanceof HTMLMultiSelectField ) {
1365 $options = HTMLFormField::flattenOptions( $info['options'] );
1366 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
1368 foreach( $options as $opt ) {
1369 $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
1372 unset( $data[$fieldname] );
1376 return $data;