9553a401aee5ace33ec31da1070a2947e085931d
[lhc/web/wiklou.git] / includes / Preferences.php
1 <?php
2
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.
23 */
24
25 class Preferences {
26 static $defaultPreferences = null;
27 static $saveFilters =
28 array(
29 'timecorrection' => array( 'Preferences', 'filterTimezoneInput' ),
30 );
31
32 static function getPreferences( $user ) {
33 if ( self::$defaultPreferences )
34 return self::$defaultPreferences;
35
36 global $wgRCMaxAge;
37
38 $defaultPreferences = array();
39
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 );
51
52 wfRunHooks( 'GetPreferences', array( $user, &$defaultPreferences ) );
53
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] );
59 }
60 }
61
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;
71
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" );
83 }
84 }
85
86 self::$defaultPreferences = $defaultPreferences;
87
88 return $defaultPreferences;
89 }
90
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 );
94
95 // Handling for array-type preferences
96 if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) ||
97 ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) {
98
99 $options = HTMLFormField::flattenOptions( $info['options'] );
100 $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name;
101 $val = array();
102
103 foreach( $options as $label => $value ) {
104 if( $user->getOption( "$prefix$value" ) ) {
105 $val[] = $value;
106 }
107 }
108 }
109
110 return $val;
111 }
112
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',
123 );
124
125 $defaultPreferences['userid'] =
126 array(
127 'type' => 'info',
128 'label-message' => 'uid',
129 'default' => $user->getId(),
130 'section' => 'personal/info',
131 );
132
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;
140 }
141 $groupName = User::getGroupName( $ueg );
142 $userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName );
143
144 $memberName = User::getGroupMember( $ueg );
145 $userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName );
146 }
147 asort( $userGroups );
148 asort( $userMembers );
149
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 )
158 ),
159 'raw' => true,
160 'section' => 'personal/info',
161 );
162
163 $defaultPreferences['editcount'] =
164 array(
165 'type' => 'info',
166 'label-message' => 'prefs-edits',
167 'default' => $wgLang->formatNum( $user->getEditCount() ),
168 'section' => 'personal/info',
169 );
170
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',
181 );
182 }
183
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',
193 );
194
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',
203 ),
204 'label-message' => 'yourgender',
205 'help-message' => 'prefs-help-gender',
206 );
207
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' ) ) );
213
214 $defaultPreferences['password'] =
215 array(
216 'type' => 'info',
217 'raw' => true,
218 'default' => $link,
219 'label-message' => 'yourpassword',
220 'section' => 'personal/info',
221 );
222 }
223
224 $defaultPreferences['rememberpassword'] =
225 array(
226 'type' => 'toggle',
227 'label-message' => 'tog-rememberpassword',
228 'section' => 'personal/info',
229 );
230
231 // Language
232 global $wgContLanguageCode;
233 $languages = array_reverse( Language::getLanguageNames( false ) );
234 if( !array_key_exists( $wgContLanguageCode, $languages ) ) {
235 $languages[$wgContLanguageCode] = $wgContLanguageCode;
236 }
237 ksort( $languages );
238
239 $options = array();
240 foreach( $languages as $code => $name ) {
241 $display = wfBCP47( $code ) . ' - ' . $name;
242 $options[$display] = $code;
243 }
244 $defaultPreferences['language'] =
245 array(
246 'type' => 'select',
247 'section' => 'personal/i18n',
248 'options' => $options,
249 'label-message' => 'yourlanguage',
250 );
251
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();
258
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];
265 }
266 }
267
268 $options = array();
269 foreach( $variantArray as $code => $name ) {
270 $display = wfBCP47( $code ) . ' - ' . $name;
271 $options[$display] = $code;
272 }
273
274 if( count( $variantArray ) > 1 ) {
275 $defaultPreferences['variant'] =
276 array(
277 'label-message' => 'yourvariant',
278 'type' => 'select',
279 'options' => $options,
280 'section' => 'personal/i18n',
281 );
282 }
283 }
284
285 if( count( $variantArray ) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion ) {
286 $defaultPreferences['noconvertlink'] =
287 array(
288 'type' => 'toggle',
289 'section' => 'personal/i18n',
290 'label-message' => 'tog-noconvertlink',
291 );
292 }
293
294 global $wgMaxSigChars, $wgOut, $wgParser;
295
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',
306 );
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' ),
316 );
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'
323 );
324
325 ## Email stuff
326
327 global $wgEnableEmail;
328 if ($wgEnableEmail) {
329
330 global $wgEmailConfirmToEdit;
331
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' ),
342 );
343
344 global $wgEnableUserEmail, $wgEmailAuthentication;
345
346 $disableEmailPrefs = false;
347
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 />';
372 }
373 } else {
374 $disableEmailPrefs = true;
375 $emailauthenticated = wfMsgHtml( 'noemailprefs' );
376 }
377
378 $defaultPreferences['emailauthentication'] =
379 array(
380 'type' => 'info',
381 'raw' => true,
382 'section' => 'personal/email',
383 'label-message' => 'prefs-emailconfirm-label',
384 'default' => $emailauthenticated,
385 );
386
387 }
388
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,
397 );
398 $defaultPreferences['ccmeonemails'] =
399 array(
400 'type' => 'toggle',
401 'section' => 'personal/email',
402 'label-message' => 'tog-ccmeonemails',
403 'disabled' => $disableEmailPrefs,
404 );
405 }
406
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,
415 );
416 }
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,
425 );
426 }
427 if( $wgEnotifUserTalk || $wgEnotifWatchlist ) {
428 $defaultPreferences['enotifminoredits'] =
429 array(
430 'type' => 'toggle',
431 'section' => 'personal/email',
432 'label-message' => 'tog-enotifminoredits',
433 'disabled' => $disableEmailPrefs,
434 );
435 }
436 $defaultPreferences['enotifrevealaddr'] =
437 array(
438 'type' => 'toggle',
439 'section' => 'personal/email',
440 'label-message' => 'tog-enotifrevealaddr',
441 'disabled' => $disableEmailPrefs,
442 );
443 }
444 }
445
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',
454 );
455
456 $selectedSkin = $user->getOption( 'skin' );
457 if ( in_array( $selectedSkin, array( 'cologneblue', 'standard' ) ) ) {
458 global $wgLang;
459 $settings = array_flip( $wgLang->getQuickbarSettings() );
460
461 $defaultPreferences['quickbar'] =
462 array(
463 'type' => 'radio',
464 'options' => $settings,
465 'section' => 'rendering/skin',
466 'label-message' => 'qbsettings',
467 );
468 }
469 }
470
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',
482 );
483 }
484 }
485
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',
494 );
495 $defaultPreferences['thumbsize'] =
496 array(
497 'type' => 'select',
498 'options' => self::getThumbSizes(),
499 'label-message' => 'thumbsize',
500 'section' => 'rendering/files',
501 );
502 }
503
504 static function datetimePreferences( $user, &$defaultPreferences ) {
505 global $wgLang;
506
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',
516 );
517 }
518
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 ) );
524
525 $defaultPreferences['nowserver'] =
526 array(
527 'type' => 'info',
528 'raw' => 1,
529 'label-message' => 'servertime',
530 'default' => $nowserver,
531 'section' => 'datetime/timeoffset',
532 );
533
534 $defaultPreferences['nowlocal'] =
535 array(
536 'type' => 'info',
537 'raw' => 1,
538 'label-message' => 'localtime',
539 'default' => $nowlocal,
540 'section' => 'datetime/timeoffset',
541 );
542
543 // Grab existing pref.
544 $tzOffset = $user->getOption( 'timecorrection' );
545 $tz = explode( '|', $tzOffset, 2 );
546
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 );
551 }
552
553 $defaultPreferences['timecorrection'] =
554 array(
555 'class' => 'HTMLSelectOrOtherField',
556 'label-message' => 'timezonelegend',
557 'options' => self::getTimezoneOptions(),
558 'default' => $tzSetting,
559 'section' => 'datetime/timeoffset',
560 );
561 }
562
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,
572 ),
573 'label-message' => 'tog-underline',
574 'section' => 'rendering/advancedrendering',
575 );
576
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;
581 }
582
583 $defaultPreferences['stubthreshold'] =
584 array(
585 'type' => 'selectorother',
586 'section' => 'rendering/advancedrendering',
587 'options' => $stubThresholdOptions,
588 'label' => wfMsg( 'stub-threshold' ), // Raw HTML message. Yay?
589 );
590 $defaultPreferences['highlightbroken'] =
591 array(
592 'type' => 'toggle',
593 'section' => 'rendering/advancedrendering',
594 'label' => wfMsg( 'tog-highlightbroken' ), // Raw HTML
595 );
596 $defaultPreferences['showtoc'] =
597 array(
598 'type' => 'toggle',
599 'section' => 'rendering/advancedrendering',
600 'label-message' => 'tog-showtoc',
601 );
602 $defaultPreferences['nocache'] =
603 array(
604 'type' => 'toggle',
605 'label-message' => 'tog-nocache',
606 'section' => 'rendering/advancedrendering',
607 );
608 $defaultPreferences['showhiddencats'] =
609 array(
610 'type' => 'toggle',
611 'section' => 'rendering/advancedrendering',
612 'label-message' => 'tog-showhiddencats'
613 );
614 $defaultPreferences['showjumplinks'] =
615 array(
616 'type' => 'toggle',
617 'section' => 'rendering/advancedrendering',
618 'label-message' => 'tog-showjumplinks',
619 );
620 $defaultPreferences['justify'] =
621 array(
622 'type' => 'toggle',
623 'section' => 'rendering/advancedrendering',
624 'label-message' => 'tog-justify',
625 );
626 $defaultPreferences['numberheadings'] =
627 array(
628 'type' => 'toggle',
629 'section' => 'rendering/advancedrendering',
630 'label-message' => 'tog-numberheadings',
631 );
632 }
633
634 static function editingPreferences( $user, &$defaultPreferences ) {
635 global $wgUseExternalEditor, $wgLivePreview;
636
637 ## Editing #####################################
638 $defaultPreferences['cols'] =
639 array(
640 'type' => 'int',
641 'label-message' => 'columns',
642 'section' => 'editing/textboxsize',
643 'min' => 4,
644 'max' => 1000,
645 );
646 $defaultPreferences['rows'] =
647 array(
648 'type' => 'int',
649 'label-message' => 'rows',
650 'section' => 'editing/textboxsize',
651 'min' => 4,
652 'max' => 1000,
653 );
654
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',
665 )
666 );
667 $defaultPreferences['previewontop'] =
668 array(
669 'type' => 'toggle',
670 'section' => 'editing/advancedediting',
671 'label-message' => 'tog-previewontop',
672 );
673 $defaultPreferences['previewonfirst'] =
674 array(
675 'type' => 'toggle',
676 'section' => 'editing/advancedediting',
677 'label-message' => 'tog-previewonfirst',
678 );
679 $defaultPreferences['editsection'] =
680 array(
681 'type' => 'toggle',
682 'section' => 'editing/advancedediting',
683 'label-message' => 'tog-editsection',
684 );
685 $defaultPreferences['editsectiononrightclick'] =
686 array(
687 'type' => 'toggle',
688 'section' => 'editing/advancedediting',
689 'label-message' => 'tog-editsectiononrightclick',
690 );
691 $defaultPreferences['editondblclick'] =
692 array(
693 'type' => 'toggle',
694 'section' => 'editing/advancedediting',
695 'label-message' => 'tog-editondblclick',
696 );
697 $defaultPreferences['editwidth'] =
698 array(
699 'type' => 'toggle',
700 'section' => 'editing/advancedediting',
701 'label-message' => 'tog-editwidth',
702 );
703 $defaultPreferences['showtoolbar'] =
704 array(
705 'type' => 'toggle',
706 'section' => 'editing/advancedediting',
707 'label-message' => 'tog-showtoolbar',
708 );
709 $defaultPreferences['minordefault'] =
710 array(
711 'type' => 'toggle',
712 'section' => 'editing/advancedediting',
713 'label-message' => 'tog-minordefault',
714 );
715
716 if ( $wgUseExternalEditor ) {
717 $defaultPreferences['externaleditor'] =
718 array(
719 'type' => 'toggle',
720 'section' => 'editing/advancedediting',
721 'label-message' => 'tog-externaleditor',
722 );
723 $defaultPreferences['externaldiff'] =
724 array(
725 'type' => 'toggle',
726 'section' => 'editing/advancedediting',
727 'label-message' => 'tog-externaldiff',
728 );
729 }
730
731 $defaultPreferences['forceeditsummary'] =
732 array(
733 'type' => 'toggle',
734 'section' => 'editing/advancedediting',
735 'label-message' => 'tog-forceeditsummary',
736 );
737 if ( $wgLivePreview ) {
738 $defaultPreferences['uselivepreview'] =
739 array(
740 'type' => 'toggle',
741 'section' => 'editing/advancedediting',
742 'label-message' => 'tog-uselivepreview',
743 );
744 }
745 }
746
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 ) ) ) ),
758 );
759 $defaultPreferences['rclimit'] =
760 array(
761 'type' => 'int',
762 'label-message' => 'recentchangescount',
763 'help-message' => 'prefs-help-recentchangescount',
764 'section' => 'rc/display',
765 );
766 $defaultPreferences['usenewrc'] =
767 array(
768 'type' => 'toggle',
769 'label-message' => 'tog-usenewrc',
770 'section' => 'rc/advancedrc',
771 );
772 $defaultPreferences['hideminor'] =
773 array(
774 'type' => 'toggle',
775 'label-message' => 'tog-hideminor',
776 'section' => 'rc/advancedrc',
777 );
778
779 global $wgUseRCPatrol;
780 if( $wgUseRCPatrol ) {
781 $defaultPreferences['hidepatrolled'] =
782 array(
783 'type' => 'toggle',
784 'section' => 'rc/advancedrc',
785 'label-message' => 'tog-hidepatrolled',
786 );
787 $defaultPreferences['newpageshidepatrolled'] =
788 array(
789 'type' => 'toggle',
790 'section' => 'rc/advancedrc',
791 'label-message' => 'tog-newpageshidepatrolled',
792 );
793 }
794
795 global $wgRCShowWatchingUsers;
796 if( $wgRCShowWatchingUsers ) {
797 $defaultPreferences['shownumberswatching'] =
798 array(
799 'type' => 'toggle',
800 'section' => 'rc/advancedrc',
801 'label-message' => 'tog-shownumberswatching',
802 );
803 }
804 }
805
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',
817 );
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',
826 );
827 $defaultPreferences['extendwatchlist'] =
828 array(
829 'type' => 'toggle',
830 'section' => 'watchlist/advancedwatchlist',
831 'label-message' => 'tog-extendwatchlist',
832 );
833 $defaultPreferences['watchlisthideminor'] =
834 array(
835 'type' => 'toggle',
836 'section' => 'watchlist/advancedwatchlist',
837 'label-message' => 'tog-watchlisthideminor',
838 );
839 $defaultPreferences['watchlisthidebots'] =
840 array(
841 'type' => 'toggle',
842 'section' => 'watchlist/advancedwatchlist',
843 'label-message' => 'tog-watchlisthidebots',
844 );
845 $defaultPreferences['watchlisthideown'] =
846 array(
847 'type' => 'toggle',
848 'section' => 'watchlist/advancedwatchlist',
849 'label-message' => 'tog-watchlisthideown',
850 );
851 $defaultPreferences['watchlisthideanons'] =
852 array(
853 'type' => 'toggle',
854 'section' => 'watchlist/advancedwatchlist',
855 'label-message' => 'tog-watchlisthideanons',
856 );
857 $defaultPreferences['watchlisthideliu'] =
858 array(
859 'type' => 'toggle',
860 'section' => 'watchlist/advancedwatchlist',
861 'label-message' => 'tog-watchlisthideliu',
862 );
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 )
872 );
873 }
874
875 if ( $wgUseRCPatrol ) {
876 $defaultPreferences['watchlisthidepatrolled'] =
877 array(
878 'type' => 'toggle',
879 'section' => 'watchlist/advancedwatchlist',
880 'label-message' => 'tog-watchlisthidepatrolled',
881 );
882 }
883
884 $watchTypes = array(
885 'edit' => 'watchdefault',
886 'move' => 'watchmoves',
887 'delete' => 'watchdeletion'
888 );
889
890 // Kinda hacky
891 if( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) {
892 $watchTypes['read'] = 'watchcreations';
893 }
894
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",
901 );
902 }
903 }
904 }
905
906 static function searchPreferences( $user, &$defaultPreferences ) {
907 global $wgContLang;
908
909 ## Search #####################################
910 $defaultPreferences['searchlimit'] =
911 array(
912 'type' => 'int',
913 'label-message' => 'resultsperpage',
914 'section' => 'searchoptions/display',
915 'min' => 0,
916 );
917 $defaultPreferences['contextlines'] =
918 array(
919 'type' => 'int',
920 'label-message' => 'contextlines',
921 'section' => 'searchoptions/display',
922 'min' => 0,
923 );
924 $defaultPreferences['contextchars'] =
925 array(
926 'type' => 'int',
927 'label-message' => 'contextchars',
928 'section' => 'searchoptions/display',
929 'min' => 0,
930 );
931 global $wgEnableMWSuggest;
932 if( $wgEnableMWSuggest ) {
933 $defaultPreferences['disablesuggest'] =
934 array(
935 'type' => 'toggle',
936 'label-message' => 'mwsuggest-disable',
937 'section' => 'searchoptions/display',
938 );
939 }
940
941 $defaultPreferences['searcheverything'] =
942 array(
943 'type' => 'toggle',
944 'label-message' => 'searcheverything-enable',
945 'section' => 'searchoptions/advancedsearchoptions',
946 );
947
948 // Searchable namespaces back-compat with old format
949 $searchableNamespaces = SearchEngine::searchableNamespaces();
950
951 $nsOptions = array();
952 foreach( $wgContLang->getNamespaces() as $ns => $name ) {
953 if( $ns < 0 ) continue;
954 $displayNs = str_replace( '_', ' ', $name );
955
956 if( !$displayNs ) $displayNs = wfMsg( 'blanknamespace' );
957
958 $displayNs = htmlspecialchars( $displayNs );
959 $nsOptions[$displayNs] = $ns;
960 }
961
962 $defaultPreferences['searchnamespaces'] =
963 array(
964 'type' => 'multiselect',
965 'label-message' => 'defaultns',
966 'options' => $nsOptions,
967 'section' => 'searchoptions/advancedsearchoptions',
968 'prefix' => 'searchNs',
969 );
970 }
971
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',
979 );
980 $defaultPreferences['norollbackdiff'] =
981 array(
982 'type' => 'toggle',
983 'section' => 'misc/diffs',
984 'label-message' => 'tog-norollbackdiff',
985 );
986
987 // Stuff from Language::getExtraUserToggles()
988 global $wgContLang;
989
990 $toggles = $wgContLang->getExtraUserToggles();
991
992 foreach( $toggles as $toggle ) {
993 $defaultPreferences[$toggle] =
994 array(
995 'type' => 'toggle',
996 'section' => 'personal/i18n',
997 'label-message' => "tog-$toggle",
998 );
999 }
1000 }
1001
1002 static function generateSkinOptions( $user ) {
1003 global $wgDefaultSkin;
1004 $ret = array();
1005
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 );
1018 }
1019 }
1020 asort( $validSkinNames );
1021 $sk = $user->getSkin();
1022
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)";
1032 }
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)";
1037 }
1038 if( $skinkey == $wgDefaultSkin )
1039 $sn .= ' (' . wfMsgHtml( 'default' ) . ')';
1040 $display = "$sn $previewlink{$extraLinks}";
1041 $ret[$display] = $skinkey;
1042 }
1043
1044 return $ret;
1045 }
1046
1047 static function getDateOptions() {
1048 global $wgLang;
1049 $dateopts = $wgLang->getDatePreferences();
1050
1051 $ret = array();
1052
1053 if( $dateopts ) {
1054 if ( !in_array( 'default', $dateopts ) ) {
1055 $dateopts[] = 'default'; // Make sure default is always valid
1056 // Bug 19237
1057 }
1058
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 ) );
1066 }
1067 $ret[$formatted] = $key;
1068 }
1069 }
1070 return $ret;
1071 }
1072
1073 static function getImageSizes() {
1074 global $wgImageLimits;
1075
1076 $ret = array();
1077
1078 foreach ( $wgImageLimits as $index => $limits ) {
1079 $display = "{$limits[0]}×{$limits[1]}" . wfMsg( 'unit-pixel' );
1080 $ret[$display] = $index;
1081 }
1082
1083 return $ret;
1084 }
1085
1086 static function getThumbSizes() {
1087 global $wgThumbLimits;
1088
1089 $ret = array();
1090
1091 foreach ( $wgThumbLimits as $index => $size ) {
1092 $display = $size . wfMsg( 'unit-pixel' );
1093 $ret[$display] = $index;
1094 }
1095
1096 return $ret;
1097 }
1098
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 )
1106 )
1107 );
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;
1113 }
1114 }
1115
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 );
1123 }
1124
1125 return $signature;
1126 }
1127
1128 static function validateEmail( $email, $alldata ) {
1129 if ( $email && !User::isValidEmailAddr( $email ) ) {
1130 return wfMsgExt( 'invalidemailaddress', 'parseinline' );
1131 }
1132
1133 global $wgEmailConfirmToEdit;
1134 if( $wgEmailConfirmToEdit && !$email ) {
1135 return wfMsgExt( 'noemailtitle', 'parseinline' );
1136 }
1137 return true;
1138 }
1139
1140 static function getFormObject( $user ) {
1141 $formDescriptor = Preferences::getPreferences( $user );
1142 $htmlForm = new PreferencesForm( $formDescriptor, 'prefs' );
1143
1144 $htmlForm->setSubmitText( wfMsg( 'saveprefs' ) );
1145 $htmlForm->setTitle( SpecialPage::getTitleFor( 'Preferences' ) );
1146 $htmlForm->setSubmitID( 'prefsubmit' );
1147 $htmlForm->setSubmitCallback( array( 'Preferences', 'tryFormSubmit' ) );
1148
1149 return $htmlForm;
1150 }
1151
1152 static function getTimezoneOptions() {
1153 $opt = array();
1154
1155 global $wgLocalTZoffset;
1156
1157 $opt[wfMsg( 'timezoneuseserverdefault' )] = "System|$wgLocalTZoffset";
1158 $opt[wfMsg( 'timezoneuseoffset' )] = 'other';
1159 $opt[wfMsg( 'guesstimezone' )] = 'guess';
1160
1161 if ( function_exists( 'timezone_identifiers_list' ) ) {
1162 # Read timezone list
1163 $tzs = timezone_identifiers_list();
1164 sort( $tzs );
1165
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 );
1178
1179 $prefill = array_fill_keys( array_values( $tzRegions ), array() );
1180 $opt = array_merge( $opt, $prefill );
1181
1182 $now = date_create( 'now' );
1183
1184 foreach ( $tzs as $tz ) {
1185 $z = explode( '/', $tz, 2 );
1186
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;
1192
1193 # Localize region
1194 $z[0] = $tzRegions[$z[0]];
1195
1196 $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
1197
1198 $display = str_replace( '_', ' ', $z[0] . '/' . $z[1] );
1199 $value = "ZoneInfo|$minDiff|$tz";
1200
1201 $opt[$z[0]][$display] = $value;
1202 }
1203 }
1204 return $opt;
1205 }
1206
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;
1223 }
1224
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;
1230 }
1231 }
1232
1233 static function tryFormSubmit( $formData, $entryPoint = 'internal' ) {
1234 global $wgUser, $wgEmailAuthentication, $wgEnableEmail;
1235
1236 $result = true;
1237
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 );
1243 }
1244 }
1245
1246 // Stuff that shouldn't be saved as a preference.
1247 $saveBlacklist = array(
1248 'realname',
1249 'emailaddress',
1250 );
1251
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';
1269 }
1270 }
1271 } else {
1272 $wgUser->setEmail( $newadr );
1273 }
1274 if( $oldadr != $newadr ) {
1275 wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
1276 }
1277 }
1278
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 );
1284 }
1285
1286 foreach( $saveBlacklist as $b )
1287 unset( $formData[$b] );
1288
1289 // Keeps old preferences from interfering due to back-compat
1290 // code, etc.
1291 $wgUser->resetOptions();
1292
1293 foreach( $formData as $key => $value ) {
1294 $wgUser->setOption( $key, $value );
1295 }
1296
1297 $wgUser->saveSettings();
1298
1299 return $result;
1300 }
1301
1302 public static function tryUISubmit( $formData ) {
1303 $res = self::tryFormSubmit( $formData, 'ui' );
1304
1305 if( $res ) {
1306 $urlOptions = array( 'success' );
1307 if( $res === 'eauth' )
1308 $urlOptions[] = 'eauth';
1309
1310 $queryString = implode( '&', $urlOptions );
1311
1312 $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( $queryString );
1313 global $wgOut;
1314 $wgOut->redirect( $url );
1315 }
1316
1317 return true;
1318 }
1319
1320 public static function loadOldSearchNs( $user ) {
1321 $searchableNamespaces = SearchEngine::searchableNamespaces();
1322 // Back compat with old format
1323 $arr = array();
1324
1325 foreach( $searchableNamespaces as $ns => $name ) {
1326 if( $user->getOption( 'searchNs' . $ns ) ) {
1327 $arr[] = $ns;
1328 }
1329 }
1330
1331 return $arr;
1332 }
1333 }
1334
1335 /** Some tweaks to allow js prefs to work */
1336 class PreferencesForm extends HTMLForm {
1337
1338 function wrapForm( $html ) {
1339 $html = Xml::tags( 'div', array( 'id' => 'preferences' ), $html );
1340
1341 return parent::wrapForm( $html );
1342 }
1343
1344 function getButtons() {
1345 $html = parent::getButtons();
1346
1347 global $wgUser;
1348
1349 $sk = $wgUser->getSkin();
1350 $t = SpecialPage::getTitleFor( 'Preferences', 'reset' );
1351
1352 $html .= "\n" . $sk->link( $t, wfMsgHtml( 'restoreprefs' ) );
1353
1354 $html = Xml::tags( 'div', array( 'class' => 'mw-prefs-buttons' ), $html );
1355
1356 return $html;
1357 }
1358
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;
1367
1368 foreach( $options as $opt ) {
1369 $data["$prefix$opt"] = in_array( $opt, $data[$fieldname] );
1370 }
1371
1372 unset( $data[$fieldname] );
1373 }
1374 }
1375
1376 return $data;
1377 }
1378 }