Remove useless scripts list. Nothing depends on it yet really, so nobody will miss...
[mediawiki.git] / includes / Preferences.php
bloba8e967c6d866509b47714f77d6f3ac8f293e6f05
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;
295 $defaultPreferences['nickname'] =
296 array(
297 'type' => $wgAuth->allowPropChange( 'nickname' ) ? 'text' : 'info',
298 'maxlength' => $wgMaxSigChars,
299 'label-message' => 'yournick',
300 'validation-callback' =>
301 array( 'Preferences', 'validateSignature' ),
302 'section' => 'personal/signature',
303 'filter-callback' => array( 'Preferences', 'cleanSignature' ),
305 $defaultPreferences['fancysig'] =
306 array(
307 'type' => 'toggle',
308 'label-message' => 'tog-fancysig',
309 'section' => 'personal/signature'
312 ## Email stuff
314 global $wgEnableEmail;
315 if ($wgEnableEmail) {
317 global $wgEmailConfirmToEdit;
319 $defaultPreferences['emailaddress'] =
320 array(
321 'type' => $wgAuth->allowPropChange( 'emailaddress' ) ? 'text' : 'info',
322 'default' => $user->getEmail(),
323 'section' => 'personal/email',
324 'label-message' => 'youremail',
325 'help-message' => $wgEmailConfirmToEdit
326 ? 'prefs-help-email-required'
327 : 'prefs-help-email',
328 'validation-callback' => array( 'Preferences', 'validateEmail' ),
331 global $wgEnableUserEmail, $wgEmailAuthentication;
333 $disableEmailPrefs = false;
335 if ( $wgEmailAuthentication ) {
336 if ( $user->getEmail() ) {
337 if( $user->getEmailAuthenticationTimestamp() ) {
338 // date and time are separate parameters to facilitate localisation.
339 // $time is kept for backward compat reasons.
340 // 'emailauthenticated' is also used in SpecialConfirmemail.php
341 $time = $wgLang->timeAndDate( $user->getEmailAuthenticationTimestamp(), true );
342 $d = $wgLang->date( $user->getEmailAuthenticationTimestamp(), true );
343 $t = $wgLang->time( $user->getEmailAuthenticationTimestamp(), true );
344 $emailauthenticated = wfMsgExt( 'emailauthenticated', 'parseinline',
345 array($time, $d, $t ) ) . '<br />';
346 $disableEmailPrefs = false;
347 } else {
348 $disableEmailPrefs = true;
349 global $wgUser; // wgUser is okay here, it's for display
350 $skin = $wgUser->getSkin();
351 $emailauthenticated = wfMsgExt( 'emailnotauthenticated', 'parseinline' ) . '<br />' .
352 $skin->link(
353 SpecialPage::getTitleFor( 'Confirmemail' ),
354 wfMsg( 'emailconfirmlink' ),
355 array(),
356 array(),
357 array( 'known', 'noclasses' )
358 ) . '<br />';
360 } else {
361 $disableEmailPrefs = true;
362 $emailauthenticated = wfMsgHtml( 'noemailprefs' );
365 $defaultPreferences['emailauthentication'] =
366 array(
367 'type' => 'info',
368 'raw' => true,
369 'section' => 'personal/email',
370 'label-message' => 'prefs-emailconfirm-label',
371 'default' => $emailauthenticated,
376 if( $wgEnableUserEmail ) {
377 $defaultPreferences['disablemail'] =
378 array(
379 'type' => 'toggle',
380 'invert' => true,
381 'section' => 'personal/email',
382 'label-message' => 'allowemail',
383 'disabled' => $disableEmailPrefs,
385 $defaultPreferences['ccmeonemails'] =
386 array(
387 'type' => 'toggle',
388 'section' => 'personal/email',
389 'label-message' => 'tog-ccmeonemails',
390 'disabled' => $disableEmailPrefs,
394 global $wgEnotifWatchlist;
395 if ( $wgEnotifWatchlist ) {
396 $defaultPreferences['enotifwatchlistpages'] =
397 array(
398 'type' => 'toggle',
399 'section' => 'personal/email',
400 'label-message' => 'tog-enotifwatchlistpages',
401 'disabled' => $disableEmailPrefs,
404 global $wgEnotifUserTalk;
405 if( $wgEnotifUserTalk ) {
406 $defaultPreferences['enotifusertalkpages'] =
407 array(
408 'type' => 'toggle',
409 'section' => 'personal/email',
410 'label-message' => 'tog-enotifusertalkpages',
411 'disabled' => $disableEmailPrefs,
414 if( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
415 $defaultPreferences['enotifminoredits'] =
416 array(
417 'type' => 'toggle',
418 'section' => 'personal/email',
419 'label-message' => 'tog-enotifminoredits',
420 'disabled' => $disableEmailPrefs,
423 $defaultPreferences['enotifrevealaddr'] =
424 array(
425 'type' => 'toggle',
426 'section' => 'personal/email',
427 'label-message' => 'tog-enotifrevealaddr',
428 'disabled' => $disableEmailPrefs,
433 static function skinPreferences( $user, &$defaultPreferences ) {
434 ## Skin #####################################
435 $defaultPreferences['skin'] =
436 array(
437 'type' => 'radio',
438 'options' => self::generateSkinOptions( $user ),
439 'label' => '&nbsp;',
440 'section' => 'rendering/skin',
443 $selectedSkin = $user->getOption( 'skin' );
444 if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
445 global $wgLang;
446 $settings = array_flip( $wgLang->getQuickbarSettings() );
448 $defaultPreferences['quickbar'] =
449 array(
450 'type' => 'radio',
451 'options' => $settings,
452 'section' => 'rendering/skin',
453 'label-message' => 'qbsettings',
458 static function mathPreferences( $user, &$defaultPreferences ) {
459 ## Math #####################################
460 global $wgUseTeX, $wgLang;
461 if( $wgUseTeX ) {
462 $defaultPreferences['math'] =
463 array(
464 'type' => 'radio',
465 'options' =>
466 array_flip( array_map( 'wfMsgHtml', $wgLang->getMathNames() ) ),
467 'label' => '&nbsp;',
468 'section' => 'rendering/math',
473 static function filesPreferences( $user, &$defaultPreferences ) {
474 ## Files #####################################
475 $defaultPreferences['imagesize'] =
476 array(
477 'type' => 'select',
478 'options' => self::getImageSizes(),
479 'label-message' => 'imagemaxsize',
480 'section' => 'rendering/files',
482 $defaultPreferences['thumbsize'] =
483 array(
484 'type' => 'select',
485 'options' => self::getThumbSizes(),
486 'label-message' => 'thumbsize',
487 'section' => 'rendering/files',
491 static function datetimePreferences( $user, &$defaultPreferences ) {
492 global $wgLang;
494 ## Date and time #####################################
495 $dateOptions = self::getDateOptions();
496 if( $dateOptions ) {
497 $defaultPreferences['date'] =
498 array(
499 'type' => 'radio',
500 'options' => $dateOptions,
501 'label' => '&nbsp;',
502 'section' => 'datetime/dateformat',
506 // Info
507 $nowlocal = Xml::element( 'span', array( 'id' => 'wpLocalTime' ),
508 $wgLang->time( $now = wfTimestampNow(), true ) );
509 $nowserver = $wgLang->time( $now, false ) .
510 Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
512 $defaultPreferences['nowserver'] =
513 array(
514 'type' => 'info',
515 'raw' => 1,
516 'label-message' => 'servertime',
517 'default' => $nowserver,
518 'section' => 'datetime/timeoffset',
521 $defaultPreferences['nowlocal'] =
522 array(
523 'type' => 'info',
524 'raw' => 1,
525 'label-message' => 'localtime',
526 'default' => $nowlocal,
527 'section' => 'datetime/timeoffset',
530 // Grab existing pref.
531 $tzOffset = $user->getOption( 'timecorrection' );
532 $tz = explode( '|', $tzOffset, 2 );
534 $tzSetting = $tzOffset;
535 if( count( $tz ) > 1 && $tz[0] == 'Offset' ) {
536 $minDiff = $tz[1];
537 $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff/60 ), abs( $minDiff )%60 );
540 $defaultPreferences['timecorrection'] =
541 array(
542 'class' => 'HTMLSelectOrOtherField',
543 'label-message' => 'timezonelegend',
544 'options' => self::getTimezoneOptions(),
545 'default' => $tzSetting,
546 'section' => 'datetime/timeoffset',
550 static function renderingPreferences( $user, &$defaultPreferences ) {
551 ## Page Rendering ##############################
552 $defaultPreferences['underline'] =
553 array(
554 'type' => 'select',
555 'options' => array(
556 wfMsg( 'underline-never' ) => 0,
557 wfMsg( 'underline-always' ) => 1,
558 wfMsg( 'underline-default' ) => 2,
560 'label-message' => 'tog-underline',
561 'section' => 'rendering/advancedrendering',
564 $stubThresholdValues = array( 0, 50, 100, 500, 1000, 2000, 5000, 10000 );
565 $stubThresholdOptions = array();
566 foreach( $stubThresholdValues as $value ) {
567 $stubThresholdOptions[wfMsg( 'size-bytes', $value )] = $value;
570 $defaultPreferences['stubthreshold'] =
571 array(
572 'type' => 'selectorother',
573 'section' => 'rendering/advancedrendering',
574 'options' => $stubThresholdOptions,
575 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
577 $defaultPreferences['highlightbroken'] =
578 array(
579 'type' => 'toggle',
580 'section' => 'rendering/advancedrendering',
581 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
583 $defaultPreferences['showtoc'] =
584 array(
585 'type' => 'toggle',
586 'section' => 'rendering/advancedrendering',
587 'label-message' => 'tog-showtoc',
589 $defaultPreferences['nocache'] =
590 array(
591 'type' => 'toggle',
592 'label-message' => 'tog-nocache',
593 'section' => 'rendering/advancedrendering',
595 $defaultPreferences['showhiddencats'] =
596 array(
597 'type' => 'toggle',
598 'section' => 'rendering/advancedrendering',
599 'label-message' => 'tog-showhiddencats'
601 $defaultPreferences['showjumplinks'] =
602 array(
603 'type' => 'toggle',
604 'section' => 'rendering/advancedrendering',
605 'label-message' => 'tog-showjumplinks',
607 $defaultPreferences['justify'] =
608 array(
609 'type' => 'toggle',
610 'section' => 'rendering/advancedrendering',
611 'label-message' => 'tog-justify',
613 $defaultPreferences['numberheadings'] =
614 array(
615 'type' => 'toggle',
616 'section' => 'rendering/advancedrendering',
617 'label-message' => 'tog-numberheadings',
621 static function editingPreferences( $user, &$defaultPreferences ) {
622 global $wgUseExternalEditor, $wgLivePreview;
624 ## Editing #####################################
625 $defaultPreferences['cols'] =
626 array(
627 'type' => 'int',
628 'label-message' => 'columns',
629 'section' => 'editing/textboxsize',
630 'min' => 4,
631 'max' => 1000,
633 $defaultPreferences['rows'] =
634 array(
635 'type' => 'int',
636 'label-message' => 'rows',
637 'section' => 'editing/textboxsize',
638 'min' => 4,
639 'max' => 1000,
642 $defaultPreferences['editfont'] =
643 array(
644 'type' => 'select',
645 'section' => 'editing/advancedediting',
646 'label-message' => 'editfont-style',
647 'options' => array(
648 wfMsg( 'editfont-default' ) => 'default',
649 wfMsg( 'editfont-monospace' ) => 'monospace',
650 wfMsg( 'editfont-sansserif' ) => 'sans-serif',
651 wfMsg( 'editfont-serif' ) => 'serif',
654 $defaultPreferences['previewontop'] =
655 array(
656 'type' => 'toggle',
657 'section' => 'editing/advancedediting',
658 'label-message' => 'tog-previewontop',
660 $defaultPreferences['previewonfirst'] =
661 array(
662 'type' => 'toggle',
663 'section' => 'editing/advancedediting',
664 'label-message' => 'tog-previewonfirst',
666 $defaultPreferences['editsection'] =
667 array(
668 'type' => 'toggle',
669 'section' => 'editing/advancedediting',
670 'label-message' => 'tog-editsection',
672 $defaultPreferences['editsectiononrightclick'] =
673 array(
674 'type' => 'toggle',
675 'section' => 'editing/advancedediting',
676 'label-message' => 'tog-editsectiononrightclick',
678 $defaultPreferences['editondblclick'] =
679 array(
680 'type' => 'toggle',
681 'section' => 'editing/advancedediting',
682 'label-message' => 'tog-editondblclick',
684 $defaultPreferences['editwidth'] =
685 array(
686 'type' => 'toggle',
687 'section' => 'editing/advancedediting',
688 'label-message' => 'tog-editwidth',
690 $defaultPreferences['showtoolbar'] =
691 array(
692 'type' => 'toggle',
693 'section' => 'editing/advancedediting',
694 'label-message' => 'tog-showtoolbar',
696 $defaultPreferences['minordefault'] =
697 array(
698 'type' => 'toggle',
699 'section' => 'editing/advancedediting',
700 'label-message' => 'tog-minordefault',
703 if ( $wgUseExternalEditor ) {
704 $defaultPreferences['externaleditor'] =
705 array(
706 'type' => 'toggle',
707 'section' => 'editing/advancedediting',
708 'label-message' => 'tog-externaleditor',
710 $defaultPreferences['externaldiff'] =
711 array(
712 'type' => 'toggle',
713 'section' => 'editing/advancedediting',
714 'label-message' => 'tog-externaldiff',
718 $defaultPreferences['forceeditsummary'] =
719 array(
720 'type' => 'toggle',
721 'section' => 'editing/advancedediting',
722 'label-message' => 'tog-forceeditsummary',
724 if ( $wgLivePreview ) {
725 $defaultPreferences['uselivepreview'] =
726 array(
727 'type' => 'toggle',
728 'section' => 'editing/advancedediting',
729 'label-message' => 'tog-uselivepreview',
734 static function rcPreferences( $user, &$defaultPreferences ) {
735 global $wgRCMaxAge, $wgUseRCPatrol, $wgLang;
736 ## RecentChanges #####################################
737 $defaultPreferences['rcdays'] =
738 array(
739 'type' => 'float',
740 'label-message' => 'recentchangesdays',
741 'section' => 'rc/display',
742 'min' => 1,
743 'max' => ceil( $wgRCMaxAge / ( 3600*24 ) ),
744 'help' => wfMsgExt( 'recentchangesdays-max', array( 'parsemag' ), $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600*24 ) ) ) ),
746 $defaultPreferences['rclimit'] =
747 array(
748 'type' => 'int',
749 'label-message' => 'recentchangescount',
750 'help-message' => 'prefs-help-recentchangescount',
751 'section' => 'rc/display',
753 $defaultPreferences['usenewrc'] =
754 array(
755 'type' => 'toggle',
756 'label-message' => 'tog-usenewrc',
757 'section' => 'rc/advancedrc',
759 $defaultPreferences['hideminor'] =
760 array(
761 'type' => 'toggle',
762 'label-message' => 'tog-hideminor',
763 'section' => 'rc/advancedrc',
766 global $wgUseRCPatrol;
767 if( $wgUseRCPatrol ) {
768 $defaultPreferences['hidepatrolled'] =
769 array(
770 'type' => 'toggle',
771 'section' => 'rc/advancedrc',
772 'label-message' => 'tog-hidepatrolled',
774 $defaultPreferences['newpageshidepatrolled'] =
775 array(
776 'type' => 'toggle',
777 'section' => 'rc/advancedrc',
778 'label-message' => 'tog-newpageshidepatrolled',
782 global $wgRCShowWatchingUsers;
783 if( $wgRCShowWatchingUsers ) {
784 $defaultPreferences['shownumberswatching'] =
785 array(
786 'type' => 'toggle',
787 'section' => 'rc/advancedrc',
788 'label-message' => 'tog-shownumberswatching',
793 static function watchlistPreferences( $user, &$defaultPreferences ) {
794 global $wgUseRCPatrol, $wgEnableAPI;
795 ## Watchlist #####################################
796 $defaultPreferences['watchlistdays'] =
797 array(
798 'type' => 'float',
799 'min' => 0,
800 'max' => 7,
801 'section' => 'watchlist/display',
802 'help' => wfMsgHtml( 'prefs-watchlist-days-max' ),
803 'label-message' => 'prefs-watchlist-days',
805 $defaultPreferences['wllimit'] =
806 array(
807 'type' => 'int',
808 'min' => 0,
809 'max' => 1000,
810 'label-message' => 'prefs-watchlist-edits',
811 'help' => wfMsgHtml( 'prefs-watchlist-edits-max' ),
812 'section' => 'watchlist/display',
814 $defaultPreferences['extendwatchlist'] =
815 array(
816 'type' => 'toggle',
817 'section' => 'watchlist/advancedwatchlist',
818 'label-message' => 'tog-extendwatchlist',
820 $defaultPreferences['watchlisthideminor'] =
821 array(
822 'type' => 'toggle',
823 'section' => 'watchlist/advancedwatchlist',
824 'label-message' => 'tog-watchlisthideminor',
826 $defaultPreferences['watchlisthidebots'] =
827 array(
828 'type' => 'toggle',
829 'section' => 'watchlist/advancedwatchlist',
830 'label-message' => 'tog-watchlisthidebots',
832 $defaultPreferences['watchlisthideown'] =
833 array(
834 'type' => 'toggle',
835 'section' => 'watchlist/advancedwatchlist',
836 'label-message' => 'tog-watchlisthideown',
838 $defaultPreferences['watchlisthideanons'] =
839 array(
840 'type' => 'toggle',
841 'section' => 'watchlist/advancedwatchlist',
842 'label-message' => 'tog-watchlisthideanons',
844 $defaultPreferences['watchlisthideliu'] =
845 array(
846 'type' => 'toggle',
847 'section' => 'watchlist/advancedwatchlist',
848 'label-message' => 'tog-watchlisthideliu',
850 if ( $wgEnableAPI ) {
851 # Some random gibberish as a proposed default
852 $hash = sha1( mt_rand() . microtime( true ) );
853 $defaultPreferences['watchlisttoken'] =
854 array(
855 'type' => 'text',
856 'section' => 'watchlist/advancedwatchlist',
857 'label-message' => 'prefs-watchlist-token',
858 'help' => wfMsgHtml( 'prefs-help-watchlist-token', $hash )
862 if ( $wgUseRCPatrol ) {
863 $defaultPreferences['watchlisthidepatrolled'] =
864 array(
865 'type' => 'toggle',
866 'section' => 'watchlist/advancedwatchlist',
867 'label-message' => 'tog-watchlisthidepatrolled',
871 $watchTypes = array(
872 'edit' => 'watchdefault',
873 'move' => 'watchmoves',
874 'delete' => 'watchdeletion'
877 // Kinda hacky
878 if( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
879 $watchTypes['read'] = 'watchcreations';
882 foreach( $watchTypes as $action => $pref ) {
883 if ( $user->isAllowed( $action ) ) {
884 $defaultPreferences[$pref] = array(
885 'type' => 'toggle',
886 'section' => 'watchlist/advancedwatchlist',
887 'label-message' => "tog-$pref",
893 static function searchPreferences( $user, &$defaultPreferences ) {
894 global $wgContLang;
896 ## Search #####################################
897 $defaultPreferences['searchlimit'] =
898 array(
899 'type' => 'int',
900 'label-message' => 'resultsperpage',
901 'section' => 'searchoptions/display',
902 'min' => 0,
904 $defaultPreferences['contextlines'] =
905 array(
906 'type' => 'int',
907 'label-message' => 'contextlines',
908 'section' => 'searchoptions/display',
909 'min' => 0,
911 $defaultPreferences['contextchars'] =
912 array(
913 'type' => 'int',
914 'label-message' => 'contextchars',
915 'section' => 'searchoptions/display',
916 'min' => 0,
918 global $wgEnableMWSuggest;
919 if( $wgEnableMWSuggest ) {
920 $defaultPreferences['disablesuggest'] =
921 array(
922 'type' => 'toggle',
923 'label-message' => 'mwsuggest-disable',
924 'section' => 'searchoptions/display',
928 $defaultPreferences['searcheverything'] =
929 array(
930 'type' => 'toggle',
931 'label-message' => 'searcheverything-enable',
932 'section' => 'searchoptions/advancedsearchoptions',
935 // Searchable namespaces back-compat with old format
936 $searchableNamespaces = SearchEngine::searchableNamespaces();
938 $nsOptions = array();
939 foreach( $wgContLang->getNamespaces() as $ns => $name ) {
940 if( $ns < 0 ) continue;
941 $displayNs = str_replace( '_', ' ', $name );
943 if( !$displayNs ) $displayNs = wfMsg( 'blanknamespace' );
945 $displayNs = htmlspecialchars( $displayNs );
946 $nsOptions[$displayNs] = $ns;
949 $defaultPreferences['searchnamespaces'] =
950 array(
951 'type' => 'multiselect',
952 'label-message' => 'defaultns',
953 'options' => $nsOptions,
954 'section' => 'searchoptions/advancedsearchoptions',
955 'prefix' => 'searchNs',
959 static function miscPreferences( $user, &$defaultPreferences ) {
960 ## Misc #####################################
961 $defaultPreferences['diffonly'] =
962 array(
963 'type' => 'toggle',
964 'section' => 'misc/diffs',
965 'label-message' => 'tog-diffonly',
967 $defaultPreferences['norollbackdiff'] =
968 array(
969 'type' => 'toggle',
970 'section' => 'misc/diffs',
971 'label-message' => 'tog-norollbackdiff',
974 // Stuff from Language::getExtraUserToggles()
975 global $wgContLang;
977 $toggles = $wgContLang->getExtraUserToggles();
979 foreach( $toggles as $toggle ) {
980 $defaultPreferences[$toggle] =
981 array(
982 'type' => 'toggle',
983 'section' => 'personal/i18n',
984 'label-message' => "tog-$toggle",
989 static function generateSkinOptions( $user ) {
990 global $wgDefaultSkin;
991 $ret = array();
993 $mptitle = Title::newMainPage();
994 $previewtext = wfMsgHtml( 'skin-preview' );
995 # Only show members of Skin::getSkinNames() rather than
996 # $skinNames (skins is all skin names from Language.php)
997 $validSkinNames = Skin::getUsableSkins();
998 # Sort by UI skin name. First though need to update validSkinNames as sometimes
999 # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
1000 foreach ( $validSkinNames as $skinkey => &$skinname ) {
1001 $msgName = "skinname-{$skinkey}";
1002 $localisedSkinName = wfMsg( $msgName );
1003 if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) {
1004 $skinname = htmlspecialchars( $localisedSkinName );
1007 asort( $validSkinNames );
1008 $sk = $user->getSkin();
1010 foreach( $validSkinNames as $skinkey => $sn ) {
1011 $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
1012 $previewlink = "(<a target='_blank' href=\"$mplink\">$previewtext</a>)";
1013 $extraLinks = '';
1014 global $wgAllowUserCss, $wgAllowUserJs;
1015 if( $wgAllowUserCss ) {
1016 $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' );
1017 $customCSS = $sk->link( $cssPage, wfMsgHtml( 'prefs-custom-css' ) );
1018 $extraLinks .= " ($customCSS)";
1020 if( $wgAllowUserJs ) {
1021 $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' );
1022 $customJS = $sk->link( $jsPage, wfMsgHtml( 'prefs-custom-js' ) );
1023 $extraLinks .= " ($customJS)";
1025 if( $skinkey == $wgDefaultSkin )
1026 $sn .= ' (' . wfMsgHtml( 'default' ) . ')';
1027 $display = "$sn $previewlink{$extraLinks}";
1028 $ret[$display] = $skinkey;
1031 return $ret;
1034 static function getDateOptions() {
1035 global $wgLang;
1036 $dateopts = $wgLang->getDatePreferences();
1038 $ret = array();
1040 if( $dateopts ) {
1041 if ( !in_array( 'default', $dateopts ) ) {
1042 $dateopts[] = 'default'; // Make sure default is always valid
1043 // Bug 19237
1046 $idCnt = 0;
1047 $epoch = '20010115161234'; # Wikipedia day
1048 foreach( $dateopts as $key ) {
1049 if( $key == 'default' ) {
1050 $formatted = wfMsgHtml( 'datedefault' );
1051 } else {
1052 $formatted = htmlspecialchars( $wgLang->timeanddate( $epoch, false, $key ) );
1054 $ret[$formatted] = $key;
1057 return $ret;
1060 static function getImageSizes() {
1061 global $wgImageLimits;
1063 $ret = array();
1065 foreach ( $wgImageLimits as $index => $limits ) {
1066 $display = "{$limits[0]}×{$limits[1]}" . wfMsg( 'unit-pixel' );
1067 $ret[$display] = $index;
1070 return $ret;
1073 static function getThumbSizes() {
1074 global $wgThumbLimits;
1076 $ret = array();
1078 foreach ( $wgThumbLimits as $index => $size ) {
1079 $display = $size . wfMsg( 'unit-pixel' );
1080 $ret[$display] = $index;
1083 return $ret;
1086 static function validateSignature( $signature, $alldata ) {
1087 global $wgParser, $wgMaxSigChars, $wgLang;
1088 if( mb_strlen( $signature ) > $wgMaxSigChars ) {
1089 return
1090 Xml::element( 'span', array( 'class' => 'error' ),
1091 wfMsgExt( 'badsiglength', 'parsemag',
1092 $wgLang->formatNum( $wgMaxSigChars )
1095 } elseif( !empty( $alldata['fancysig'] ) &&
1096 false === $wgParser->validateSig( $signature ) ) {
1097 return Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) );
1098 } else {
1099 return true;
1103 static function cleanSignature( $signature, $alldata ) {
1104 global $wgParser;
1105 if( $alldata['fancysig'] ) {
1106 $signature = $wgParser->cleanSig( $signature );
1107 } else {
1108 // When no fancy sig used, make sure ~{3,5} get removed.
1109 $signature = $wgParser->cleanSigInSig( $signature );
1112 return $signature;
1115 static function validateEmail( $email, $alldata ) {
1116 if ( $email && !User::isValidEmailAddr( $email ) ) {
1117 return wfMsgExt( 'invalidemailaddress', 'parseinline' );
1120 global $wgEmailConfirmToEdit;
1121 if( $wgEmailConfirmToEdit && !$email ) {
1122 return wfMsgExt( 'noemailtitle', 'parseinline' );
1124 return true;
1127 static function getFormObject( $user ) {
1128 $formDescriptor = Preferences::getPreferences( $user );
1129 $htmlForm = new PreferencesForm( $formDescriptor, 'prefs' );
1131 $htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
1132 $htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
1133 $htmlForm->setSubmitID( 'prefsubmit' );
1134 $htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
1136 return $htmlForm;
1139 static function getTimezoneOptions() {
1140 $opt = array();
1142 global $wgLocalTZoffset;
1144 $opt[wfMsg( 'timezoneuseserverdefault' )] = "System|$wgLocalTZoffset";
1145 $opt[wfMsg( 'timezoneuseoffset' )] = 'other';
1146 $opt[wfMsg( 'guesstimezone' )] = 'guess';
1148 if ( function_exists( 'timezone_identifiers_list' ) ) {
1149 # Read timezone list
1150 $tzs = timezone_identifiers_list();
1151 sort( $tzs );
1153 $tzRegions = array();
1154 $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' );
1155 $tzRegions['America'] = wfMsg( 'timezoneregion-america' );
1156 $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' );
1157 $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' );
1158 $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' );
1159 $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' );
1160 $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' );
1161 $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' );
1162 $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' );
1163 $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' );
1164 asort( $tzRegions );
1166 $prefill = array_fill_keys( array_values( $tzRegions ), array() );
1167 $opt = array_merge( $opt, $prefill );
1169 $now = date_create( 'now' );
1171 foreach ( $tzs as $tz ) {
1172 $z = explode( '/', $tz, 2 );
1174 # timezone_identifiers_list() returns a number of
1175 # backwards-compatibility entries. This filters them out of the
1176 # list presented to the user.
1177 if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) )
1178 continue;
1180 # Localize region
1181 $z[0] = $tzRegions[$z[0]];
1183 $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
1185 $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
1186 $value = "ZoneInfo|$minDiff|$tz";
1188 $opt[$z[0]][$display] = $value;
1191 return $opt;
1194 static function filterTimezoneInput( $tz, $alldata ) {
1195 $data = explode( '|', $tz, 3 );
1196 switch ( $data[0] ) {
1197 case 'ZoneInfo':
1198 case 'System':
1199 return $tz;
1200 default:
1201 $data = explode( ':', $tz, 2 );
1202 $minDiff = 0;
1203 if( count( $data ) == 2 ) {
1204 $data[0] = intval( $data[0] );
1205 $data[1] = intval( $data[1] );
1206 $minDiff = abs( $data[0] ) * 60 + $data[1];
1207 if ( $data[0] < 0 ) $minDiff = -$minDiff;
1208 } else {
1209 $minDiff = intval( $data[0] ) * 60;
1212 # Max is +14:00 and min is -12:00, see:
1213 # http://en.wikipedia.org/wiki/Timezone
1214 $minDiff = min( $minDiff, 840 ); # 14:00
1215 $minDiff = max( $minDiff, -720 ); # -12:00
1216 return 'Offset|'.$minDiff;
1220 static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
1221 global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
1223 $result = true;
1225 // Filter input
1226 foreach( array_keys( $formData ) as $name ) {
1227 if ( isset( self::$saveFilters[$name] ) ) {
1228 $formData[$name] =
1229 call_user_func( self::$saveFilters[$name], $formData[$name], $formData );
1233 // Stuff that shouldn't be saved as a preference.
1234 $saveBlacklist = array(
1235 'realname',
1236 'emailaddress',
1239 if( $wgEnableEmail ) {
1240 $newadr = $formData['emailaddress'];
1241 $oldadr = $wgUser->getEmail();
1242 if( ( $newadr != '' ) && ( $newadr != $oldadr ) ) {
1243 # the user has supplied a new email address on the login page
1244 # new behaviour: set this new emailaddr from login-page into user database record
1245 $wgUser->setEmail( $newadr );
1246 # but flag as "dirty" = unauthenticated
1247 $wgUser->invalidateEmail();
1248 if( $wgEmailAuthentication ) {
1249 # Mail a temporary password to the dirty address.
1250 # User can come back through the confirmation URL to re-enable email.
1251 $result = $wgUser->sendConfirmationMail();
1252 if( WikiError::isError( $result ) ) {
1253 return wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
1254 } elseif( $entryPoint == 'ui' ) {
1255 $result = 'eauth';
1258 } else {
1259 $wgUser->setEmail( $newadr );
1261 if( $oldadr != $newadr ) {
1262 wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
1266 // Fortunately, the realname field is MUCH simpler
1267 global $wgHiddenPrefs;
1268 if ( !in_array( 'realname', $wgHiddenPrefs ) ) {
1269 $realName = $formData['realname'];
1270 $wgUser->setRealName( $realName );
1273 foreach( $saveBlacklist as $b )
1274 unset( $formData[$b] );
1276 // Keeps old preferences from interfering due to back-compat
1277 // code, etc.
1278 $wgUser->resetOptions();
1280 foreach( $formData as $key => $value ) {
1281 $wgUser->setOption( $key, $value );
1284 $wgUser->saveSettings();
1286 return $result;
1289 public static function tryUISubmit( $formData ) {
1290 $res = self::tryFormSubmit( $formData, 'ui' );
1292 if( $res ) {
1293 $urlOptions = array( 'success' );
1294 if( $res === 'eauth' )
1295 $urlOptions[] = 'eauth';
1297 $queryString = implode( '&', $urlOptions );
1299 $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
1300 global $wgOut;
1301 $wgOut->redirect( $url );
1304 return true;
1307 public static function loadOldSearchNs( $user ) {
1308 $searchableNamespaces = SearchEngine::searchableNamespaces();
1309 // Back compat with old format
1310 $arr = array();
1312 foreach( $searchableNamespaces as $ns => $name ) {
1313 if( $user->getOption( 'searchNs' . $ns ) ) {
1314 $arr[] = $ns;
1318 return $arr;
1322 /** Some tweaks to allow js prefs to work */
1323 class PreferencesForm extends HTMLForm {
1325 function wrapForm( $html ) {
1326 $html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
1328 return parent::wrapForm( $html );
1331 function getButtons() {
1332 $html = parent::getButtons();
1334 global $wgUser;
1336 $sk = $wgUser->getSkin();
1337 $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
1339 $html .= "\n" . $sk->link( $t, wfMsgHtml( 'restoreprefs' ) );
1341 $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
1343 return $html;
1346 function filterDataForSubmit( $data ) {
1347 // Support for separating MultiSelect preferences into multiple preferences
1348 // Due to lack of array support.
1349 foreach( $this->mFlatFields as $fieldname => $field ) {
1350 $info = $field->mParams;
1351 if( $field instanceof HTMLMultiSelectField ) {
1352 $options = HTMLFormField::flattenOptions( $info['options'] );
1353 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname;
1355 foreach( $options as $opt ) {
1356 $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
1359 unset( $data[$fieldname] );
1363 return $data;