This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Form to edit user preferences. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | */ |
||
22 | use MediaWiki\Auth\AuthManager; |
||
23 | use MediaWiki\Auth\PasswordAuthenticationRequest; |
||
24 | |||
25 | /** |
||
26 | * We're now using the HTMLForm object with some customisation to generate the |
||
27 | * Preferences form. This object handles generic submission, CSRF protection, |
||
28 | * layout and other logic in a reusable manner. We subclass it as a PreferencesForm |
||
29 | * to make some minor customisations. |
||
30 | * |
||
31 | * In order to generate the form, the HTMLForm object needs an array structure |
||
32 | * detailing the form fields available, and that's what this class is for. Each |
||
33 | * element of the array is a basic property-list, including the type of field, |
||
34 | * the label it is to be given in the form, callbacks for validation and |
||
35 | * 'filtering', and other pertinent information. Note that the 'default' field |
||
36 | * is named for generic forms, and does not represent the preference's default |
||
37 | * (which is stored in $wgDefaultUserOptions), but the default for the form |
||
38 | * field, which should be whatever the user has set for that preference. There |
||
39 | * is no need to override it unless you have some special storage logic (for |
||
40 | * instance, those not presently stored as options, but which are best set from |
||
41 | * the user preferences view). |
||
42 | * |
||
43 | * Field types are implemented as subclasses of the generic HTMLFormField |
||
44 | * object, and typically implement at least getInputHTML, which generates the |
||
45 | * HTML for the input field to be placed in the table. |
||
46 | * |
||
47 | * Once fields have been retrieved and validated, submission logic is handed |
||
48 | * over to the tryUISubmit static method of this class. |
||
49 | */ |
||
50 | class Preferences { |
||
51 | /** @var array */ |
||
52 | protected static $defaultPreferences = null; |
||
53 | |||
54 | /** @var array */ |
||
55 | protected static $saveFilters = [ |
||
56 | 'timecorrection' => [ 'Preferences', 'filterTimezoneInput' ], |
||
57 | 'cols' => [ 'Preferences', 'filterIntval' ], |
||
58 | 'rows' => [ 'Preferences', 'filterIntval' ], |
||
59 | 'rclimit' => [ 'Preferences', 'filterIntval' ], |
||
60 | 'wllimit' => [ 'Preferences', 'filterIntval' ], |
||
61 | 'searchlimit' => [ 'Preferences', 'filterIntval' ], |
||
62 | ]; |
||
63 | |||
64 | // Stuff that shouldn't be saved as a preference. |
||
65 | private static $saveBlacklist = [ |
||
66 | 'realname', |
||
67 | 'emailaddress', |
||
68 | ]; |
||
69 | |||
70 | /** |
||
71 | * @return array |
||
72 | */ |
||
73 | static function getSaveBlacklist() { |
||
74 | return self::$saveBlacklist; |
||
75 | } |
||
76 | |||
77 | /** |
||
78 | * @throws MWException |
||
79 | * @param User $user |
||
80 | * @param IContextSource $context |
||
81 | * @return array|null |
||
82 | */ |
||
83 | static function getPreferences( $user, IContextSource $context ) { |
||
84 | if ( self::$defaultPreferences ) { |
||
85 | return self::$defaultPreferences; |
||
86 | } |
||
87 | |||
88 | $defaultPreferences = []; |
||
89 | |||
90 | self::profilePreferences( $user, $context, $defaultPreferences ); |
||
91 | self::skinPreferences( $user, $context, $defaultPreferences ); |
||
92 | self::datetimePreferences( $user, $context, $defaultPreferences ); |
||
93 | self::filesPreferences( $user, $context, $defaultPreferences ); |
||
94 | self::renderingPreferences( $user, $context, $defaultPreferences ); |
||
95 | self::editingPreferences( $user, $context, $defaultPreferences ); |
||
96 | self::rcPreferences( $user, $context, $defaultPreferences ); |
||
97 | self::watchlistPreferences( $user, $context, $defaultPreferences ); |
||
98 | self::searchPreferences( $user, $context, $defaultPreferences ); |
||
99 | self::miscPreferences( $user, $context, $defaultPreferences ); |
||
100 | |||
101 | Hooks::run( 'GetPreferences', [ $user, &$defaultPreferences ] ); |
||
102 | |||
103 | self::loadPreferenceValues( $user, $context, $defaultPreferences ); |
||
104 | self::$defaultPreferences = $defaultPreferences; |
||
105 | return $defaultPreferences; |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Loads existing values for a given array of preferences |
||
110 | * @throws MWException |
||
111 | * @param User $user |
||
112 | * @param IContextSource $context |
||
113 | * @param array $defaultPreferences Array to load values for |
||
114 | * @return array|null |
||
115 | */ |
||
116 | static function loadPreferenceValues( $user, $context, &$defaultPreferences ) { |
||
117 | # # Remove preferences that wikis don't want to use |
||
118 | foreach ( $context->getConfig()->get( 'HiddenPrefs' ) as $pref ) { |
||
119 | if ( isset( $defaultPreferences[$pref] ) ) { |
||
120 | unset( $defaultPreferences[$pref] ); |
||
121 | } |
||
122 | } |
||
123 | |||
124 | # # Make sure that form fields have their parent set. See bug 41337. |
||
125 | $dummyForm = new HTMLForm( [], $context ); |
||
126 | |||
127 | $disable = !$user->isAllowed( 'editmyoptions' ); |
||
128 | |||
129 | $defaultOptions = User::getDefaultOptions(); |
||
130 | # # Prod in defaults from the user |
||
131 | foreach ( $defaultPreferences as $name => &$info ) { |
||
132 | $prefFromUser = self::getOptionFromUser( $name, $info, $user ); |
||
133 | if ( $disable && !in_array( $name, self::$saveBlacklist ) ) { |
||
134 | $info['disabled'] = 'disabled'; |
||
135 | } |
||
136 | $field = HTMLForm::loadInputFromParameters( $name, $info, $dummyForm ); // For validation |
||
137 | $globalDefault = isset( $defaultOptions[$name] ) |
||
138 | ? $defaultOptions[$name] |
||
139 | : null; |
||
140 | |||
141 | // If it validates, set it as the default |
||
142 | if ( isset( $info['default'] ) ) { |
||
143 | // Already set, no problem |
||
144 | continue; |
||
145 | } elseif ( !is_null( $prefFromUser ) && // Make sure we're not just pulling nothing |
||
146 | $field->validate( $prefFromUser, $user->getOptions() ) === true ) { |
||
147 | $info['default'] = $prefFromUser; |
||
148 | } elseif ( $field->validate( $globalDefault, $user->getOptions() ) === true ) { |
||
149 | $info['default'] = $globalDefault; |
||
150 | } else { |
||
151 | throw new MWException( "Global default '$globalDefault' is invalid for field $name" ); |
||
152 | } |
||
153 | } |
||
154 | |||
155 | return $defaultPreferences; |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Pull option from a user account. Handles stuff like array-type preferences. |
||
160 | * |
||
161 | * @param string $name |
||
162 | * @param array $info |
||
163 | * @param User $user |
||
164 | * @return array|string |
||
165 | */ |
||
166 | static function getOptionFromUser( $name, $info, $user ) { |
||
167 | $val = $user->getOption( $name ); |
||
168 | |||
169 | // Handling for multiselect preferences |
||
170 | View Code Duplication | if ( ( isset( $info['type'] ) && $info['type'] == 'multiselect' ) || |
|
171 | ( isset( $info['class'] ) && $info['class'] == 'HTMLMultiSelectField' ) ) { |
||
172 | $options = HTMLFormField::flattenOptions( $info['options'] ); |
||
173 | $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; |
||
174 | $val = []; |
||
175 | |||
176 | foreach ( $options as $value ) { |
||
177 | if ( $user->getOption( "$prefix$value" ) ) { |
||
178 | $val[] = $value; |
||
179 | } |
||
180 | } |
||
181 | } |
||
182 | |||
183 | // Handling for checkmatrix preferences |
||
184 | if ( ( isset( $info['type'] ) && $info['type'] == 'checkmatrix' ) || |
||
185 | ( isset( $info['class'] ) && $info['class'] == 'HTMLCheckMatrix' ) ) { |
||
186 | $columns = HTMLFormField::flattenOptions( $info['columns'] ); |
||
187 | $rows = HTMLFormField::flattenOptions( $info['rows'] ); |
||
188 | $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $name; |
||
189 | $val = []; |
||
190 | |||
191 | foreach ( $columns as $column ) { |
||
192 | foreach ( $rows as $row ) { |
||
193 | if ( $user->getOption( "$prefix$column-$row" ) ) { |
||
194 | $val[] = "$column-$row"; |
||
195 | } |
||
196 | } |
||
197 | } |
||
198 | } |
||
199 | |||
200 | return $val; |
||
201 | } |
||
202 | |||
203 | /** |
||
204 | * @param User $user |
||
205 | * @param IContextSource $context |
||
206 | * @param array $defaultPreferences |
||
207 | * @return void |
||
208 | */ |
||
209 | static function profilePreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
210 | global $wgContLang, $wgParser; |
||
211 | |||
212 | $authManager = AuthManager::singleton(); |
||
213 | $config = $context->getConfig(); |
||
214 | // retrieving user name for GENDER and misc. |
||
215 | $userName = $user->getName(); |
||
216 | |||
217 | # # User info ##################################### |
||
218 | // Information panel |
||
219 | $defaultPreferences['username'] = [ |
||
220 | 'type' => 'info', |
||
221 | 'label-message' => [ 'username', $userName ], |
||
222 | 'default' => $userName, |
||
223 | 'section' => 'personal/info', |
||
224 | ]; |
||
225 | |||
226 | # Get groups to which the user belongs |
||
227 | $userEffectiveGroups = $user->getEffectiveGroups(); |
||
228 | $userGroups = $userMembers = []; |
||
229 | foreach ( $userEffectiveGroups as $ueg ) { |
||
230 | if ( $ueg == '*' ) { |
||
231 | // Skip the default * group, seems useless here |
||
232 | continue; |
||
233 | } |
||
234 | $groupName = User::getGroupName( $ueg ); |
||
235 | $userGroups[] = User::makeGroupLinkHTML( $ueg, $groupName ); |
||
236 | |||
237 | $memberName = User::getGroupMember( $ueg, $userName ); |
||
238 | $userMembers[] = User::makeGroupLinkHTML( $ueg, $memberName ); |
||
239 | } |
||
240 | asort( $userGroups ); |
||
241 | asort( $userMembers ); |
||
242 | |||
243 | $lang = $context->getLanguage(); |
||
244 | |||
245 | $defaultPreferences['usergroups'] = [ |
||
246 | 'type' => 'info', |
||
247 | 'label' => $context->msg( 'prefs-memberingroups' )->numParams( |
||
248 | count( $userGroups ) )->params( $userName )->parse(), |
||
249 | 'default' => $context->msg( 'prefs-memberingroups-type' ) |
||
250 | ->rawParams( $lang->commaList( $userGroups ), $lang->commaList( $userMembers ) ) |
||
251 | ->escaped(), |
||
252 | 'raw' => true, |
||
253 | 'section' => 'personal/info', |
||
254 | ]; |
||
255 | |||
256 | $editCount = Linker::link( SpecialPage::getTitleFor( "Contributions", $userName ), |
||
257 | $lang->formatNum( $user->getEditCount() ) ); |
||
258 | |||
259 | $defaultPreferences['editcount'] = [ |
||
260 | 'type' => 'info', |
||
261 | 'raw' => true, |
||
262 | 'label-message' => 'prefs-edits', |
||
263 | 'default' => $editCount, |
||
264 | 'section' => 'personal/info', |
||
265 | ]; |
||
266 | |||
267 | if ( $user->getRegistration() ) { |
||
0 ignored issues
–
show
|
|||
268 | $displayUser = $context->getUser(); |
||
269 | $userRegistration = $user->getRegistration(); |
||
270 | $defaultPreferences['registrationdate'] = [ |
||
271 | 'type' => 'info', |
||
272 | 'label-message' => 'prefs-registration', |
||
273 | 'default' => $context->msg( |
||
274 | 'prefs-registration-date-time', |
||
275 | $lang->userTimeAndDate( $userRegistration, $displayUser ), |
||
276 | $lang->userDate( $userRegistration, $displayUser ), |
||
277 | $lang->userTime( $userRegistration, $displayUser ) |
||
278 | )->parse(), |
||
279 | 'section' => 'personal/info', |
||
280 | ]; |
||
281 | } |
||
282 | |||
283 | $canViewPrivateInfo = $user->isAllowed( 'viewmyprivateinfo' ); |
||
284 | $canEditPrivateInfo = $user->isAllowed( 'editmyprivateinfo' ); |
||
285 | |||
286 | // Actually changeable stuff |
||
287 | $defaultPreferences['realname'] = [ |
||
288 | // (not really "private", but still shouldn't be edited without permission) |
||
289 | 'type' => $canEditPrivateInfo && $authManager->allowsPropertyChange( 'realname' ) |
||
290 | ? 'text' : 'info', |
||
291 | 'default' => $user->getRealName(), |
||
292 | 'section' => 'personal/info', |
||
293 | 'label-message' => 'yourrealname', |
||
294 | 'help-message' => 'prefs-help-realname', |
||
295 | ]; |
||
296 | |||
297 | if ( $canEditPrivateInfo && $authManager->allowsAuthenticationDataChange( |
||
298 | new PasswordAuthenticationRequest(), false )->isGood() |
||
299 | ) { |
||
300 | $link = Linker::link( SpecialPage::getTitleFor( 'ChangePassword' ), |
||
301 | $context->msg( 'prefs-resetpass' )->escaped(), [], |
||
302 | [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ); |
||
303 | |||
304 | $defaultPreferences['password'] = [ |
||
305 | 'type' => 'info', |
||
306 | 'raw' => true, |
||
307 | 'default' => $link, |
||
308 | 'label-message' => 'yourpassword', |
||
309 | 'section' => 'personal/info', |
||
310 | ]; |
||
311 | } |
||
312 | // Only show prefershttps if secure login is turned on |
||
313 | if ( $config->get( 'SecureLogin' ) && wfCanIPUseHTTPS( $context->getRequest()->getIP() ) ) { |
||
314 | $defaultPreferences['prefershttps'] = [ |
||
315 | 'type' => 'toggle', |
||
316 | 'label-message' => 'tog-prefershttps', |
||
317 | 'help-message' => 'prefs-help-prefershttps', |
||
318 | 'section' => 'personal/info' |
||
319 | ]; |
||
320 | } |
||
321 | |||
322 | // Language |
||
323 | $languages = Language::fetchLanguageNames( null, 'mw' ); |
||
324 | $languageCode = $config->get( 'LanguageCode' ); |
||
325 | if ( !array_key_exists( $languageCode, $languages ) ) { |
||
326 | $languages[$languageCode] = $languageCode; |
||
327 | } |
||
328 | ksort( $languages ); |
||
329 | |||
330 | $options = []; |
||
331 | foreach ( $languages as $code => $name ) { |
||
332 | $display = wfBCP47( $code ) . ' - ' . $name; |
||
333 | $options[$display] = $code; |
||
334 | } |
||
335 | $defaultPreferences['language'] = [ |
||
336 | 'type' => 'select', |
||
337 | 'section' => 'personal/i18n', |
||
338 | 'options' => $options, |
||
339 | 'label-message' => 'yourlanguage', |
||
340 | ]; |
||
341 | |||
342 | $defaultPreferences['gender'] = [ |
||
343 | 'type' => 'radio', |
||
344 | 'section' => 'personal/i18n', |
||
345 | 'options' => [ |
||
346 | $context->msg( 'parentheses' ) |
||
347 | ->params( $context->msg( 'gender-unknown' )->plain() ) |
||
348 | ->escaped() => 'unknown', |
||
349 | $context->msg( 'gender-female' )->escaped() => 'female', |
||
350 | $context->msg( 'gender-male' )->escaped() => 'male', |
||
351 | ], |
||
352 | 'label-message' => 'yourgender', |
||
353 | 'help-message' => 'prefs-help-gender', |
||
354 | ]; |
||
355 | |||
356 | // see if there are multiple language variants to choose from |
||
357 | if ( !$config->get( 'DisableLangConversion' ) ) { |
||
358 | foreach ( LanguageConverter::$languagesWithVariants as $langCode ) { |
||
359 | if ( $langCode == $wgContLang->getCode() ) { |
||
360 | $variants = $wgContLang->getVariants(); |
||
361 | |||
362 | if ( count( $variants ) <= 1 ) { |
||
363 | continue; |
||
364 | } |
||
365 | |||
366 | $variantArray = []; |
||
367 | foreach ( $variants as $v ) { |
||
368 | $v = str_replace( '_', '-', strtolower( $v ) ); |
||
369 | $variantArray[$v] = $lang->getVariantname( $v, false ); |
||
370 | } |
||
371 | |||
372 | $options = []; |
||
373 | foreach ( $variantArray as $code => $name ) { |
||
374 | $display = wfBCP47( $code ) . ' - ' . $name; |
||
375 | $options[$display] = $code; |
||
376 | } |
||
377 | |||
378 | $defaultPreferences['variant'] = [ |
||
379 | 'label-message' => 'yourvariant', |
||
380 | 'type' => 'select', |
||
381 | 'options' => $options, |
||
382 | 'section' => 'personal/i18n', |
||
383 | 'help-message' => 'prefs-help-variant', |
||
384 | ]; |
||
385 | } else { |
||
386 | $defaultPreferences["variant-$langCode"] = [ |
||
387 | 'type' => 'api', |
||
388 | ]; |
||
389 | } |
||
390 | } |
||
391 | } |
||
392 | |||
393 | // Stuff from Language::getExtraUserToggles() |
||
394 | // FIXME is this dead code? $extraUserToggles doesn't seem to be defined for any language |
||
395 | $toggles = $wgContLang->getExtraUserToggles(); |
||
396 | |||
397 | foreach ( $toggles as $toggle ) { |
||
398 | $defaultPreferences[$toggle] = [ |
||
399 | 'type' => 'toggle', |
||
400 | 'section' => 'personal/i18n', |
||
401 | 'label-message' => "tog-$toggle", |
||
402 | ]; |
||
403 | } |
||
404 | |||
405 | // show a preview of the old signature first |
||
406 | $oldsigWikiText = $wgParser->preSaveTransform( |
||
407 | '~~~', |
||
408 | $context->getTitle(), |
||
409 | $user, |
||
410 | ParserOptions::newFromContext( $context ) |
||
411 | ); |
||
412 | $oldsigHTML = $context->getOutput()->parseInline( $oldsigWikiText, true, true ); |
||
413 | $defaultPreferences['oldsig'] = [ |
||
414 | 'type' => 'info', |
||
415 | 'raw' => true, |
||
416 | 'label-message' => 'tog-oldsig', |
||
417 | 'default' => $oldsigHTML, |
||
418 | 'section' => 'personal/signature', |
||
419 | ]; |
||
420 | $defaultPreferences['nickname'] = [ |
||
421 | 'type' => $authManager->allowsPropertyChange( 'nickname' ) ? 'text' : 'info', |
||
422 | 'maxlength' => $config->get( 'MaxSigChars' ), |
||
423 | 'label-message' => 'yournick', |
||
424 | 'validation-callback' => [ 'Preferences', 'validateSignature' ], |
||
425 | 'section' => 'personal/signature', |
||
426 | 'filter-callback' => [ 'Preferences', 'cleanSignature' ], |
||
427 | ]; |
||
428 | $defaultPreferences['fancysig'] = [ |
||
429 | 'type' => 'toggle', |
||
430 | 'label-message' => 'tog-fancysig', |
||
431 | // show general help about signature at the bottom of the section |
||
432 | 'help-message' => 'prefs-help-signature', |
||
433 | 'section' => 'personal/signature' |
||
434 | ]; |
||
435 | |||
436 | # # Email stuff |
||
437 | |||
438 | if ( $config->get( 'EnableEmail' ) ) { |
||
439 | if ( $canViewPrivateInfo ) { |
||
440 | $helpMessages[] = $config->get( 'EmailConfirmToEdit' ) |
||
0 ignored issues
–
show
Coding Style
Comprehensibility
introduced
by
$helpMessages was never initialized. Although not strictly required by PHP, it is generally a good practice to add $helpMessages = array(); before regardless.
Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code. Let’s take a look at an example: foreach ($collection as $item) {
$myArray['foo'] = $item->getFoo();
if ($item->hasBar()) {
$myArray['bar'] = $item->getBar();
}
// do something with $myArray
}
As you can see in this example, the array This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop. ![]() |
|||
441 | ? 'prefs-help-email-required' |
||
442 | : 'prefs-help-email'; |
||
443 | |||
444 | if ( $config->get( 'EnableUserEmail' ) ) { |
||
445 | // additional messages when users can send email to each other |
||
446 | $helpMessages[] = 'prefs-help-email-others'; |
||
447 | } |
||
448 | |||
449 | $emailAddress = $user->getEmail() ? htmlspecialchars( $user->getEmail() ) : ''; |
||
450 | if ( $canEditPrivateInfo && $authManager->allowsPropertyChange( 'emailaddress' ) ) { |
||
451 | $link = Linker::link( |
||
452 | SpecialPage::getTitleFor( 'ChangeEmail' ), |
||
453 | $context->msg( $user->getEmail() ? 'prefs-changeemail' : 'prefs-setemail' )->escaped(), |
||
454 | [], |
||
455 | [ 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ] ); |
||
456 | |||
457 | $emailAddress .= $emailAddress == '' ? $link : ( |
||
458 | $context->msg( 'word-separator' )->escaped() |
||
459 | . $context->msg( 'parentheses' )->rawParams( $link )->escaped() |
||
460 | ); |
||
461 | } |
||
462 | |||
463 | $defaultPreferences['emailaddress'] = [ |
||
464 | 'type' => 'info', |
||
465 | 'raw' => true, |
||
466 | 'default' => $emailAddress, |
||
467 | 'label-message' => 'youremail', |
||
468 | 'section' => 'personal/email', |
||
469 | 'help-messages' => $helpMessages, |
||
470 | # 'cssclass' chosen below |
||
471 | ]; |
||
472 | } |
||
473 | |||
474 | $disableEmailPrefs = false; |
||
475 | |||
476 | if ( $config->get( 'EmailAuthentication' ) ) { |
||
477 | $emailauthenticationclass = 'mw-email-not-authenticated'; |
||
0 ignored issues
–
show
$emailauthenticationclass is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the ![]() |
|||
478 | if ( $user->getEmail() ) { |
||
479 | if ( $user->getEmailAuthenticationTimestamp() ) { |
||
480 | // date and time are separate parameters to facilitate localisation. |
||
481 | // $time is kept for backward compat reasons. |
||
482 | // 'emailauthenticated' is also used in SpecialConfirmemail.php |
||
483 | $displayUser = $context->getUser(); |
||
484 | $emailTimestamp = $user->getEmailAuthenticationTimestamp(); |
||
485 | $time = $lang->userTimeAndDate( $emailTimestamp, $displayUser ); |
||
486 | $d = $lang->userDate( $emailTimestamp, $displayUser ); |
||
487 | $t = $lang->userTime( $emailTimestamp, $displayUser ); |
||
488 | $emailauthenticated = $context->msg( 'emailauthenticated', |
||
489 | $time, $d, $t )->parse() . '<br />'; |
||
490 | $disableEmailPrefs = false; |
||
491 | $emailauthenticationclass = 'mw-email-authenticated'; |
||
492 | } else { |
||
493 | $disableEmailPrefs = true; |
||
494 | $emailauthenticated = $context->msg( 'emailnotauthenticated' )->parse() . '<br />' . |
||
495 | Linker::linkKnown( |
||
496 | SpecialPage::getTitleFor( 'Confirmemail' ), |
||
497 | $context->msg( 'emailconfirmlink' )->escaped() |
||
498 | ) . '<br />'; |
||
499 | $emailauthenticationclass = "mw-email-not-authenticated"; |
||
500 | } |
||
501 | } else { |
||
502 | $disableEmailPrefs = true; |
||
503 | $emailauthenticated = $context->msg( 'noemailprefs' )->escaped(); |
||
504 | $emailauthenticationclass = 'mw-email-none'; |
||
505 | } |
||
506 | |||
507 | if ( $canViewPrivateInfo ) { |
||
508 | $defaultPreferences['emailauthentication'] = [ |
||
509 | 'type' => 'info', |
||
510 | 'raw' => true, |
||
511 | 'section' => 'personal/email', |
||
512 | 'label-message' => 'prefs-emailconfirm-label', |
||
513 | 'default' => $emailauthenticated, |
||
514 | # Apply the same CSS class used on the input to the message: |
||
515 | 'cssclass' => $emailauthenticationclass, |
||
516 | ]; |
||
517 | } |
||
518 | } |
||
519 | |||
520 | if ( $config->get( 'EnableUserEmail' ) && $user->isAllowed( 'sendemail' ) ) { |
||
521 | $defaultPreferences['disablemail'] = [ |
||
522 | 'type' => 'toggle', |
||
523 | 'invert' => true, |
||
524 | 'section' => 'personal/email', |
||
525 | 'label-message' => 'allowemail', |
||
526 | 'disabled' => $disableEmailPrefs, |
||
527 | ]; |
||
528 | $defaultPreferences['ccmeonemails'] = [ |
||
529 | 'type' => 'toggle', |
||
530 | 'section' => 'personal/email', |
||
531 | 'label-message' => 'tog-ccmeonemails', |
||
532 | 'disabled' => $disableEmailPrefs, |
||
533 | ]; |
||
534 | } |
||
535 | |||
536 | View Code Duplication | if ( $config->get( 'EnotifWatchlist' ) ) { |
|
537 | $defaultPreferences['enotifwatchlistpages'] = [ |
||
538 | 'type' => 'toggle', |
||
539 | 'section' => 'personal/email', |
||
540 | 'label-message' => 'tog-enotifwatchlistpages', |
||
541 | 'disabled' => $disableEmailPrefs, |
||
542 | ]; |
||
543 | } |
||
544 | View Code Duplication | if ( $config->get( 'EnotifUserTalk' ) ) { |
|
545 | $defaultPreferences['enotifusertalkpages'] = [ |
||
546 | 'type' => 'toggle', |
||
547 | 'section' => 'personal/email', |
||
548 | 'label-message' => 'tog-enotifusertalkpages', |
||
549 | 'disabled' => $disableEmailPrefs, |
||
550 | ]; |
||
551 | } |
||
552 | if ( $config->get( 'EnotifUserTalk' ) || $config->get( 'EnotifWatchlist' ) ) { |
||
553 | View Code Duplication | if ( $config->get( 'EnotifMinorEdits' ) ) { |
|
554 | $defaultPreferences['enotifminoredits'] = [ |
||
555 | 'type' => 'toggle', |
||
556 | 'section' => 'personal/email', |
||
557 | 'label-message' => 'tog-enotifminoredits', |
||
558 | 'disabled' => $disableEmailPrefs, |
||
559 | ]; |
||
560 | } |
||
561 | |||
562 | View Code Duplication | if ( $config->get( 'EnotifRevealEditorAddress' ) ) { |
|
563 | $defaultPreferences['enotifrevealaddr'] = [ |
||
564 | 'type' => 'toggle', |
||
565 | 'section' => 'personal/email', |
||
566 | 'label-message' => 'tog-enotifrevealaddr', |
||
567 | 'disabled' => $disableEmailPrefs, |
||
568 | ]; |
||
569 | } |
||
570 | } |
||
571 | } |
||
572 | } |
||
573 | |||
574 | /** |
||
575 | * @param User $user |
||
576 | * @param IContextSource $context |
||
577 | * @param array $defaultPreferences |
||
578 | * @return void |
||
579 | */ |
||
580 | static function skinPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
581 | # # Skin ##################################### |
||
582 | |||
583 | // Skin selector, if there is at least one valid skin |
||
584 | $skinOptions = self::generateSkinOptions( $user, $context ); |
||
585 | View Code Duplication | if ( $skinOptions ) { |
|
586 | $defaultPreferences['skin'] = [ |
||
587 | 'type' => 'radio', |
||
588 | 'options' => $skinOptions, |
||
589 | 'label' => ' ', |
||
590 | 'section' => 'rendering/skin', |
||
591 | ]; |
||
592 | } |
||
593 | |||
594 | $config = $context->getConfig(); |
||
595 | $allowUserCss = $config->get( 'AllowUserCss' ); |
||
596 | $allowUserJs = $config->get( 'AllowUserJs' ); |
||
597 | # Create links to user CSS/JS pages for all skins |
||
598 | # This code is basically copied from generateSkinOptions(). It'd |
||
599 | # be nice to somehow merge this back in there to avoid redundancy. |
||
600 | if ( $allowUserCss || $allowUserJs ) { |
||
601 | $linkTools = []; |
||
602 | $userName = $user->getName(); |
||
603 | |||
604 | View Code Duplication | if ( $allowUserCss ) { |
|
605 | $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/common.css' ); |
||
606 | $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() ); |
||
0 ignored issues
–
show
It seems like
$cssPage defined by \Title::makeTitleSafe(NS...erName . '/common.css') on line 605 can be null ; however, Linker::link() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
607 | } |
||
608 | |||
609 | View Code Duplication | if ( $allowUserJs ) { |
|
610 | $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/common.js' ); |
||
611 | $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() ); |
||
0 ignored issues
–
show
It seems like
$jsPage defined by \Title::makeTitleSafe(NS...serName . '/common.js') on line 610 can be null ; however, Linker::link() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
612 | } |
||
613 | |||
614 | $defaultPreferences['commoncssjs'] = [ |
||
615 | 'type' => 'info', |
||
616 | 'raw' => true, |
||
617 | 'default' => $context->getLanguage()->pipeList( $linkTools ), |
||
618 | 'label-message' => 'prefs-common-css-js', |
||
619 | 'section' => 'rendering/skin', |
||
620 | ]; |
||
621 | } |
||
622 | } |
||
623 | |||
624 | /** |
||
625 | * @param User $user |
||
626 | * @param IContextSource $context |
||
627 | * @param array $defaultPreferences |
||
628 | */ |
||
629 | static function filesPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
630 | # # Files ##################################### |
||
631 | $defaultPreferences['imagesize'] = [ |
||
632 | 'type' => 'select', |
||
633 | 'options' => self::getImageSizes( $context ), |
||
634 | 'label-message' => 'imagemaxsize', |
||
635 | 'section' => 'rendering/files', |
||
636 | ]; |
||
637 | $defaultPreferences['thumbsize'] = [ |
||
638 | 'type' => 'select', |
||
639 | 'options' => self::getThumbSizes( $context ), |
||
640 | 'label-message' => 'thumbsize', |
||
641 | 'section' => 'rendering/files', |
||
642 | ]; |
||
643 | } |
||
644 | |||
645 | /** |
||
646 | * @param User $user |
||
647 | * @param IContextSource $context |
||
648 | * @param array $defaultPreferences |
||
649 | * @return void |
||
650 | */ |
||
651 | static function datetimePreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
652 | # # Date and time ##################################### |
||
653 | $dateOptions = self::getDateOptions( $context ); |
||
654 | View Code Duplication | if ( $dateOptions ) { |
|
655 | $defaultPreferences['date'] = [ |
||
656 | 'type' => 'radio', |
||
657 | 'options' => $dateOptions, |
||
658 | 'label' => ' ', |
||
659 | 'section' => 'rendering/dateformat', |
||
660 | ]; |
||
661 | } |
||
662 | |||
663 | // Info |
||
664 | $now = wfTimestampNow(); |
||
665 | $lang = $context->getLanguage(); |
||
666 | $nowlocal = Xml::element( 'span', [ 'id' => 'wpLocalTime' ], |
||
667 | $lang->userTime( $now, $user ) ); |
||
668 | $nowserver = $lang->userTime( $now, $user, |
||
669 | [ 'format' => false, 'timecorrection' => false ] ) . |
||
670 | Html::hidden( 'wpServerTime', (int)substr( $now, 8, 2 ) * 60 + (int)substr( $now, 10, 2 ) ); |
||
671 | |||
672 | $defaultPreferences['nowserver'] = [ |
||
673 | 'type' => 'info', |
||
674 | 'raw' => 1, |
||
675 | 'label-message' => 'servertime', |
||
676 | 'default' => $nowserver, |
||
677 | 'section' => 'rendering/timeoffset', |
||
678 | ]; |
||
679 | |||
680 | $defaultPreferences['nowlocal'] = [ |
||
681 | 'type' => 'info', |
||
682 | 'raw' => 1, |
||
683 | 'label-message' => 'localtime', |
||
684 | 'default' => $nowlocal, |
||
685 | 'section' => 'rendering/timeoffset', |
||
686 | ]; |
||
687 | |||
688 | // Grab existing pref. |
||
689 | $tzOffset = $user->getOption( 'timecorrection' ); |
||
690 | $tz = explode( '|', $tzOffset, 3 ); |
||
691 | |||
692 | $tzOptions = self::getTimezoneOptions( $context ); |
||
693 | |||
694 | $tzSetting = $tzOffset; |
||
695 | if ( count( $tz ) > 1 && $tz[0] == 'Offset' ) { |
||
696 | $minDiff = $tz[1]; |
||
697 | $tzSetting = sprintf( '%+03d:%02d', floor( $minDiff / 60 ), abs( $minDiff ) % 60 ); |
||
698 | } elseif ( count( $tz ) > 1 && $tz[0] == 'ZoneInfo' && |
||
699 | !in_array( $tzOffset, HTMLFormField::flattenOptions( $tzOptions ) ) |
||
700 | ) { |
||
701 | # Timezone offset can vary with DST |
||
702 | $userTZ = timezone_open( $tz[2] ); |
||
703 | if ( $userTZ !== false ) { |
||
704 | $minDiff = floor( timezone_offset_get( $userTZ, date_create( 'now' ) ) / 60 ); |
||
705 | $tzSetting = "ZoneInfo|$minDiff|{$tz[2]}"; |
||
706 | } |
||
707 | } |
||
708 | |||
709 | $defaultPreferences['timecorrection'] = [ |
||
710 | 'class' => 'HTMLSelectOrOtherField', |
||
711 | 'label-message' => 'timezonelegend', |
||
712 | 'options' => $tzOptions, |
||
713 | 'default' => $tzSetting, |
||
714 | 'size' => 20, |
||
715 | 'section' => 'rendering/timeoffset', |
||
716 | ]; |
||
717 | } |
||
718 | |||
719 | /** |
||
720 | * @param User $user |
||
721 | * @param IContextSource $context |
||
722 | * @param array $defaultPreferences |
||
723 | */ |
||
724 | static function renderingPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
725 | # # Diffs #################################### |
||
726 | $defaultPreferences['diffonly'] = [ |
||
727 | 'type' => 'toggle', |
||
728 | 'section' => 'rendering/diffs', |
||
729 | 'label-message' => 'tog-diffonly', |
||
730 | ]; |
||
731 | $defaultPreferences['norollbackdiff'] = [ |
||
732 | 'type' => 'toggle', |
||
733 | 'section' => 'rendering/diffs', |
||
734 | 'label-message' => 'tog-norollbackdiff', |
||
735 | ]; |
||
736 | |||
737 | # # Page Rendering ############################## |
||
738 | if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) { |
||
739 | $defaultPreferences['underline'] = [ |
||
740 | 'type' => 'select', |
||
741 | 'options' => [ |
||
742 | $context->msg( 'underline-never' )->text() => 0, |
||
743 | $context->msg( 'underline-always' )->text() => 1, |
||
744 | $context->msg( 'underline-default' )->text() => 2, |
||
745 | ], |
||
746 | 'label-message' => 'tog-underline', |
||
747 | 'section' => 'rendering/advancedrendering', |
||
748 | ]; |
||
749 | } |
||
750 | |||
751 | $stubThresholdValues = [ 50, 100, 500, 1000, 2000, 5000, 10000 ]; |
||
752 | $stubThresholdOptions = [ $context->msg( 'stub-threshold-disabled' )->text() => 0 ]; |
||
753 | foreach ( $stubThresholdValues as $value ) { |
||
754 | $stubThresholdOptions[$context->msg( 'size-bytes', $value )->text()] = $value; |
||
755 | } |
||
756 | |||
757 | $defaultPreferences['stubthreshold'] = [ |
||
758 | 'type' => 'select', |
||
759 | 'section' => 'rendering/advancedrendering', |
||
760 | 'options' => $stubThresholdOptions, |
||
761 | // This is not a raw HTML message; label-raw is needed for the manual <a></a> |
||
762 | 'label-raw' => $context->msg( 'stub-threshold' )->rawParams( |
||
763 | '<a href="#" class="stub">' . |
||
764 | $context->msg( 'stub-threshold-sample-link' )->parse() . |
||
765 | '</a>' )->parse(), |
||
766 | ]; |
||
767 | |||
768 | $defaultPreferences['showhiddencats'] = [ |
||
769 | 'type' => 'toggle', |
||
770 | 'section' => 'rendering/advancedrendering', |
||
771 | 'label-message' => 'tog-showhiddencats' |
||
772 | ]; |
||
773 | |||
774 | $defaultPreferences['numberheadings'] = [ |
||
775 | 'type' => 'toggle', |
||
776 | 'section' => 'rendering/advancedrendering', |
||
777 | 'label-message' => 'tog-numberheadings', |
||
778 | ]; |
||
779 | } |
||
780 | |||
781 | /** |
||
782 | * @param User $user |
||
783 | * @param IContextSource $context |
||
784 | * @param array $defaultPreferences |
||
785 | */ |
||
786 | static function editingPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
787 | # # Editing ##################################### |
||
788 | $defaultPreferences['editsectiononrightclick'] = [ |
||
789 | 'type' => 'toggle', |
||
790 | 'section' => 'editing/advancedediting', |
||
791 | 'label-message' => 'tog-editsectiononrightclick', |
||
792 | ]; |
||
793 | $defaultPreferences['editondblclick'] = [ |
||
794 | 'type' => 'toggle', |
||
795 | 'section' => 'editing/advancedediting', |
||
796 | 'label-message' => 'tog-editondblclick', |
||
797 | ]; |
||
798 | |||
799 | if ( $context->getConfig()->get( 'AllowUserCssPrefs' ) ) { |
||
800 | $defaultPreferences['editfont'] = [ |
||
801 | 'type' => 'select', |
||
802 | 'section' => 'editing/editor', |
||
803 | 'label-message' => 'editfont-style', |
||
804 | 'options' => [ |
||
805 | $context->msg( 'editfont-default' )->text() => 'default', |
||
806 | $context->msg( 'editfont-monospace' )->text() => 'monospace', |
||
807 | $context->msg( 'editfont-sansserif' )->text() => 'sans-serif', |
||
808 | $context->msg( 'editfont-serif' )->text() => 'serif', |
||
809 | ] |
||
810 | ]; |
||
811 | } |
||
812 | $defaultPreferences['cols'] = [ |
||
813 | 'type' => 'int', |
||
814 | 'label-message' => 'columns', |
||
815 | 'section' => 'editing/editor', |
||
816 | 'min' => 4, |
||
817 | 'max' => 1000, |
||
818 | ]; |
||
819 | $defaultPreferences['rows'] = [ |
||
820 | 'type' => 'int', |
||
821 | 'label-message' => 'rows', |
||
822 | 'section' => 'editing/editor', |
||
823 | 'min' => 4, |
||
824 | 'max' => 1000, |
||
825 | ]; |
||
826 | if ( $user->isAllowed( 'minoredit' ) ) { |
||
827 | $defaultPreferences['minordefault'] = [ |
||
828 | 'type' => 'toggle', |
||
829 | 'section' => 'editing/editor', |
||
830 | 'label-message' => 'tog-minordefault', |
||
831 | ]; |
||
832 | } |
||
833 | $defaultPreferences['forceeditsummary'] = [ |
||
834 | 'type' => 'toggle', |
||
835 | 'section' => 'editing/editor', |
||
836 | 'label-message' => 'tog-forceeditsummary', |
||
837 | ]; |
||
838 | $defaultPreferences['useeditwarning'] = [ |
||
839 | 'type' => 'toggle', |
||
840 | 'section' => 'editing/editor', |
||
841 | 'label-message' => 'tog-useeditwarning', |
||
842 | ]; |
||
843 | $defaultPreferences['showtoolbar'] = [ |
||
844 | 'type' => 'toggle', |
||
845 | 'section' => 'editing/editor', |
||
846 | 'label-message' => 'tog-showtoolbar', |
||
847 | ]; |
||
848 | |||
849 | $defaultPreferences['previewonfirst'] = [ |
||
850 | 'type' => 'toggle', |
||
851 | 'section' => 'editing/preview', |
||
852 | 'label-message' => 'tog-previewonfirst', |
||
853 | ]; |
||
854 | $defaultPreferences['previewontop'] = [ |
||
855 | 'type' => 'toggle', |
||
856 | 'section' => 'editing/preview', |
||
857 | 'label-message' => 'tog-previewontop', |
||
858 | ]; |
||
859 | $defaultPreferences['uselivepreview'] = [ |
||
860 | 'type' => 'toggle', |
||
861 | 'section' => 'editing/preview', |
||
862 | 'label-message' => 'tog-uselivepreview', |
||
863 | ]; |
||
864 | } |
||
865 | |||
866 | /** |
||
867 | * @param User $user |
||
868 | * @param IContextSource $context |
||
869 | * @param array $defaultPreferences |
||
870 | */ |
||
871 | static function rcPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
872 | $config = $context->getConfig(); |
||
873 | $rcMaxAge = $config->get( 'RCMaxAge' ); |
||
874 | # # RecentChanges ##################################### |
||
875 | $defaultPreferences['rcdays'] = [ |
||
876 | 'type' => 'float', |
||
877 | 'label-message' => 'recentchangesdays', |
||
878 | 'section' => 'rc/displayrc', |
||
879 | 'min' => 1, |
||
880 | 'max' => ceil( $rcMaxAge / ( 3600 * 24 ) ), |
||
881 | 'help' => $context->msg( 'recentchangesdays-max' )->numParams( |
||
882 | ceil( $rcMaxAge / ( 3600 * 24 ) ) )->escaped() |
||
883 | ]; |
||
884 | $defaultPreferences['rclimit'] = [ |
||
885 | 'type' => 'int', |
||
886 | 'label-message' => 'recentchangescount', |
||
887 | 'help-message' => 'prefs-help-recentchangescount', |
||
888 | 'section' => 'rc/displayrc', |
||
889 | ]; |
||
890 | $defaultPreferences['usenewrc'] = [ |
||
891 | 'type' => 'toggle', |
||
892 | 'label-message' => 'tog-usenewrc', |
||
893 | 'section' => 'rc/advancedrc', |
||
894 | ]; |
||
895 | $defaultPreferences['hideminor'] = [ |
||
896 | 'type' => 'toggle', |
||
897 | 'label-message' => 'tog-hideminor', |
||
898 | 'section' => 'rc/advancedrc', |
||
899 | ]; |
||
900 | |||
901 | if ( $config->get( 'RCWatchCategoryMembership' ) ) { |
||
902 | $defaultPreferences['hidecategorization'] = [ |
||
903 | 'type' => 'toggle', |
||
904 | 'label-message' => 'tog-hidecategorization', |
||
905 | 'section' => 'rc/advancedrc', |
||
906 | ]; |
||
907 | } |
||
908 | |||
909 | if ( $user->useRCPatrol() ) { |
||
910 | $defaultPreferences['hidepatrolled'] = [ |
||
911 | 'type' => 'toggle', |
||
912 | 'section' => 'rc/advancedrc', |
||
913 | 'label-message' => 'tog-hidepatrolled', |
||
914 | ]; |
||
915 | } |
||
916 | |||
917 | if ( $user->useNPPatrol() ) { |
||
918 | $defaultPreferences['newpageshidepatrolled'] = [ |
||
919 | 'type' => 'toggle', |
||
920 | 'section' => 'rc/advancedrc', |
||
921 | 'label-message' => 'tog-newpageshidepatrolled', |
||
922 | ]; |
||
923 | } |
||
924 | |||
925 | if ( $config->get( 'RCShowWatchingUsers' ) ) { |
||
926 | $defaultPreferences['shownumberswatching'] = [ |
||
927 | 'type' => 'toggle', |
||
928 | 'section' => 'rc/advancedrc', |
||
929 | 'label-message' => 'tog-shownumberswatching', |
||
930 | ]; |
||
931 | } |
||
932 | } |
||
933 | |||
934 | /** |
||
935 | * @param User $user |
||
936 | * @param IContextSource $context |
||
937 | * @param array $defaultPreferences |
||
938 | */ |
||
939 | static function watchlistPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
940 | $config = $context->getConfig(); |
||
941 | $watchlistdaysMax = ceil( $config->get( 'RCMaxAge' ) / ( 3600 * 24 ) ); |
||
942 | |||
943 | # # Watchlist ##################################### |
||
944 | if ( $user->isAllowed( 'editmywatchlist' ) ) { |
||
945 | $editWatchlistLinks = []; |
||
946 | $editWatchlistModes = [ |
||
947 | 'edit' => [ 'EditWatchlist', false ], |
||
948 | 'raw' => [ 'EditWatchlist', 'raw' ], |
||
949 | 'clear' => [ 'EditWatchlist', 'clear' ], |
||
950 | ]; |
||
951 | foreach ( $editWatchlistModes as $editWatchlistMode => $mode ) { |
||
952 | // Messages: prefs-editwatchlist-edit, prefs-editwatchlist-raw, prefs-editwatchlist-clear |
||
953 | $editWatchlistLinks[] = Linker::linkKnown( |
||
954 | SpecialPage::getTitleFor( $mode[0], $mode[1] ), |
||
0 ignored issues
–
show
|
|||
955 | $context->msg( "prefs-editwatchlist-{$editWatchlistMode}" )->parse() |
||
956 | ); |
||
957 | } |
||
958 | |||
959 | $defaultPreferences['editwatchlist'] = [ |
||
960 | 'type' => 'info', |
||
961 | 'raw' => true, |
||
962 | 'default' => $context->getLanguage()->pipeList( $editWatchlistLinks ), |
||
963 | 'label-message' => 'prefs-editwatchlist-label', |
||
964 | 'section' => 'watchlist/editwatchlist', |
||
965 | ]; |
||
966 | } |
||
967 | |||
968 | $defaultPreferences['watchlistdays'] = [ |
||
969 | 'type' => 'float', |
||
970 | 'min' => 0, |
||
971 | 'max' => $watchlistdaysMax, |
||
972 | 'section' => 'watchlist/displaywatchlist', |
||
973 | 'help' => $context->msg( 'prefs-watchlist-days-max' )->numParams( |
||
974 | $watchlistdaysMax )->escaped(), |
||
975 | 'label-message' => 'prefs-watchlist-days', |
||
976 | ]; |
||
977 | $defaultPreferences['wllimit'] = [ |
||
978 | 'type' => 'int', |
||
979 | 'min' => 0, |
||
980 | 'max' => 1000, |
||
981 | 'label-message' => 'prefs-watchlist-edits', |
||
982 | 'help' => $context->msg( 'prefs-watchlist-edits-max' )->escaped(), |
||
983 | 'section' => 'watchlist/displaywatchlist', |
||
984 | ]; |
||
985 | $defaultPreferences['extendwatchlist'] = [ |
||
986 | 'type' => 'toggle', |
||
987 | 'section' => 'watchlist/advancedwatchlist', |
||
988 | 'label-message' => 'tog-extendwatchlist', |
||
989 | ]; |
||
990 | $defaultPreferences['watchlisthideminor'] = [ |
||
991 | 'type' => 'toggle', |
||
992 | 'section' => 'watchlist/advancedwatchlist', |
||
993 | 'label-message' => 'tog-watchlisthideminor', |
||
994 | ]; |
||
995 | $defaultPreferences['watchlisthidebots'] = [ |
||
996 | 'type' => 'toggle', |
||
997 | 'section' => 'watchlist/advancedwatchlist', |
||
998 | 'label-message' => 'tog-watchlisthidebots', |
||
999 | ]; |
||
1000 | $defaultPreferences['watchlisthideown'] = [ |
||
1001 | 'type' => 'toggle', |
||
1002 | 'section' => 'watchlist/advancedwatchlist', |
||
1003 | 'label-message' => 'tog-watchlisthideown', |
||
1004 | ]; |
||
1005 | $defaultPreferences['watchlisthideanons'] = [ |
||
1006 | 'type' => 'toggle', |
||
1007 | 'section' => 'watchlist/advancedwatchlist', |
||
1008 | 'label-message' => 'tog-watchlisthideanons', |
||
1009 | ]; |
||
1010 | $defaultPreferences['watchlisthideliu'] = [ |
||
1011 | 'type' => 'toggle', |
||
1012 | 'section' => 'watchlist/advancedwatchlist', |
||
1013 | 'label-message' => 'tog-watchlisthideliu', |
||
1014 | ]; |
||
1015 | $defaultPreferences['watchlistreloadautomatically'] = [ |
||
1016 | 'type' => 'toggle', |
||
1017 | 'section' => 'watchlist/advancedwatchlist', |
||
1018 | 'label-message' => 'tog-watchlistreloadautomatically', |
||
1019 | ]; |
||
1020 | |||
1021 | if ( $config->get( 'RCWatchCategoryMembership' ) ) { |
||
1022 | $defaultPreferences['watchlisthidecategorization'] = [ |
||
1023 | 'type' => 'toggle', |
||
1024 | 'section' => 'watchlist/advancedwatchlist', |
||
1025 | 'label-message' => 'tog-watchlisthidecategorization', |
||
1026 | ]; |
||
1027 | } |
||
1028 | |||
1029 | if ( $user->useRCPatrol() ) { |
||
1030 | $defaultPreferences['watchlisthidepatrolled'] = [ |
||
1031 | 'type' => 'toggle', |
||
1032 | 'section' => 'watchlist/advancedwatchlist', |
||
1033 | 'label-message' => 'tog-watchlisthidepatrolled', |
||
1034 | ]; |
||
1035 | } |
||
1036 | |||
1037 | $watchTypes = [ |
||
1038 | 'edit' => 'watchdefault', |
||
1039 | 'move' => 'watchmoves', |
||
1040 | 'delete' => 'watchdeletion' |
||
1041 | ]; |
||
1042 | |||
1043 | // Kinda hacky |
||
1044 | if ( $user->isAllowed( 'createpage' ) || $user->isAllowed( 'createtalk' ) ) { |
||
1045 | $watchTypes['read'] = 'watchcreations'; |
||
1046 | } |
||
1047 | |||
1048 | if ( $user->isAllowed( 'rollback' ) ) { |
||
1049 | $watchTypes['rollback'] = 'watchrollback'; |
||
1050 | } |
||
1051 | |||
1052 | if ( $user->isAllowed( 'upload' ) ) { |
||
1053 | $watchTypes['upload'] = 'watchuploads'; |
||
1054 | } |
||
1055 | |||
1056 | foreach ( $watchTypes as $action => $pref ) { |
||
1057 | if ( $user->isAllowed( $action ) ) { |
||
1058 | // Messages: |
||
1059 | // tog-watchdefault, tog-watchmoves, tog-watchdeletion, tog-watchcreations, tog-watchuploads |
||
1060 | // tog-watchrollback |
||
1061 | $defaultPreferences[$pref] = [ |
||
1062 | 'type' => 'toggle', |
||
1063 | 'section' => 'watchlist/advancedwatchlist', |
||
1064 | 'label-message' => "tog-$pref", |
||
1065 | ]; |
||
1066 | } |
||
1067 | } |
||
1068 | |||
1069 | if ( $config->get( 'EnableAPI' ) ) { |
||
1070 | $defaultPreferences['watchlisttoken'] = [ |
||
1071 | 'type' => 'api', |
||
1072 | ]; |
||
1073 | $defaultPreferences['watchlisttoken-info'] = [ |
||
1074 | 'type' => 'info', |
||
1075 | 'section' => 'watchlist/tokenwatchlist', |
||
1076 | 'label-message' => 'prefs-watchlist-token', |
||
1077 | 'default' => $user->getTokenFromOption( 'watchlisttoken' ), |
||
1078 | 'help-message' => 'prefs-help-watchlist-token2', |
||
1079 | ]; |
||
1080 | } |
||
1081 | } |
||
1082 | |||
1083 | /** |
||
1084 | * @param User $user |
||
1085 | * @param IContextSource $context |
||
1086 | * @param array $defaultPreferences |
||
1087 | */ |
||
1088 | static function searchPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
1089 | foreach ( MWNamespace::getValidNamespaces() as $n ) { |
||
1090 | $defaultPreferences['searchNs' . $n] = [ |
||
1091 | 'type' => 'api', |
||
1092 | ]; |
||
1093 | } |
||
1094 | } |
||
1095 | |||
1096 | /** |
||
1097 | * Dummy, kept for backwards-compatibility. |
||
1098 | */ |
||
1099 | static function miscPreferences( $user, IContextSource $context, &$defaultPreferences ) { |
||
1100 | } |
||
1101 | |||
1102 | /** |
||
1103 | * @param User $user The User object |
||
1104 | * @param IContextSource $context |
||
1105 | * @return array Text/links to display as key; $skinkey as value |
||
1106 | */ |
||
1107 | static function generateSkinOptions( $user, IContextSource $context ) { |
||
1108 | $ret = []; |
||
1109 | |||
1110 | $mptitle = Title::newMainPage(); |
||
1111 | $previewtext = $context->msg( 'skin-preview' )->escaped(); |
||
1112 | |||
1113 | # Only show skins that aren't disabled in $wgSkipSkins |
||
1114 | $validSkinNames = Skin::getAllowedSkins(); |
||
1115 | |||
1116 | # Sort by UI skin name. First though need to update validSkinNames as sometimes |
||
1117 | # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI). |
||
1118 | foreach ( $validSkinNames as $skinkey => &$skinname ) { |
||
1119 | $msg = $context->msg( "skinname-{$skinkey}" ); |
||
1120 | if ( $msg->exists() ) { |
||
1121 | $skinname = htmlspecialchars( $msg->text() ); |
||
1122 | } |
||
1123 | } |
||
1124 | asort( $validSkinNames ); |
||
1125 | |||
1126 | $config = $context->getConfig(); |
||
1127 | $defaultSkin = $config->get( 'DefaultSkin' ); |
||
1128 | $allowUserCss = $config->get( 'AllowUserCss' ); |
||
1129 | $allowUserJs = $config->get( 'AllowUserJs' ); |
||
1130 | |||
1131 | $foundDefault = false; |
||
1132 | foreach ( $validSkinNames as $skinkey => $sn ) { |
||
1133 | $linkTools = []; |
||
1134 | |||
1135 | # Mark the default skin |
||
1136 | if ( strcasecmp( $skinkey, $defaultSkin ) === 0 ) { |
||
1137 | $linkTools[] = $context->msg( 'default' )->escaped(); |
||
1138 | $foundDefault = true; |
||
1139 | } |
||
1140 | |||
1141 | # Create preview link |
||
1142 | $mplink = htmlspecialchars( $mptitle->getLocalURL( [ 'useskin' => $skinkey ] ) ); |
||
1143 | $linkTools[] = "<a target='_blank' href=\"$mplink\">$previewtext</a>"; |
||
1144 | |||
1145 | # Create links to user CSS/JS pages |
||
1146 | View Code Duplication | if ( $allowUserCss ) { |
|
1147 | $cssPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.css' ); |
||
1148 | $linkTools[] = Linker::link( $cssPage, $context->msg( 'prefs-custom-css' )->escaped() ); |
||
0 ignored issues
–
show
It seems like
$cssPage defined by \Title::makeTitleSafe(NS.../' . $skinkey . '.css') on line 1147 can be null ; however, Linker::link() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1149 | } |
||
1150 | |||
1151 | View Code Duplication | if ( $allowUserJs ) { |
|
1152 | $jsPage = Title::makeTitleSafe( NS_USER, $user->getName() . '/' . $skinkey . '.js' ); |
||
1153 | $linkTools[] = Linker::link( $jsPage, $context->msg( 'prefs-custom-js' )->escaped() ); |
||
0 ignored issues
–
show
It seems like
$jsPage defined by \Title::makeTitleSafe(NS...'/' . $skinkey . '.js') on line 1152 can be null ; however, Linker::link() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1154 | } |
||
1155 | |||
1156 | $display = $sn . ' ' . $context->msg( 'parentheses' ) |
||
1157 | ->rawParams( $context->getLanguage()->pipeList( $linkTools ) ) |
||
1158 | ->escaped(); |
||
1159 | $ret[$display] = $skinkey; |
||
1160 | } |
||
1161 | |||
1162 | if ( !$foundDefault ) { |
||
1163 | // If the default skin is not available, things are going to break horribly because the |
||
1164 | // default value for skin selector will not be a valid value. Let's just not show it then. |
||
1165 | return []; |
||
1166 | } |
||
1167 | |||
1168 | return $ret; |
||
1169 | } |
||
1170 | |||
1171 | /** |
||
1172 | * @param IContextSource $context |
||
1173 | * @return array |
||
1174 | */ |
||
1175 | static function getDateOptions( IContextSource $context ) { |
||
1176 | $lang = $context->getLanguage(); |
||
1177 | $dateopts = $lang->getDatePreferences(); |
||
1178 | |||
1179 | $ret = []; |
||
1180 | |||
1181 | if ( $dateopts ) { |
||
1182 | if ( !in_array( 'default', $dateopts ) ) { |
||
1183 | $dateopts[] = 'default'; // Make sure default is always valid |
||
1184 | // Bug 19237 |
||
1185 | } |
||
1186 | |||
1187 | // FIXME KLUGE: site default might not be valid for user language |
||
1188 | global $wgDefaultUserOptions; |
||
1189 | if ( !in_array( $wgDefaultUserOptions['date'], $dateopts ) ) { |
||
1190 | $wgDefaultUserOptions['date'] = 'default'; |
||
1191 | } |
||
1192 | |||
1193 | $epoch = wfTimestampNow(); |
||
1194 | foreach ( $dateopts as $key ) { |
||
1195 | if ( $key == 'default' ) { |
||
1196 | $formatted = $context->msg( 'datedefault' )->escaped(); |
||
1197 | } else { |
||
1198 | $formatted = htmlspecialchars( $lang->timeanddate( $epoch, false, $key ) ); |
||
0 ignored issues
–
show
It seems like
$epoch defined by wfTimestampNow() on line 1193 can also be of type false ; however, Language::timeanddate() does only seem to accept string , did you maybe forget to handle an error condition?
This check looks for type mismatches where the missing type is Consider the follow example <?php
function getDate($date)
{
if ($date !== null) {
return new DateTime($date);
}
return false;
}
This function either returns a new ![]() |
|||
1199 | } |
||
1200 | $ret[$formatted] = $key; |
||
1201 | } |
||
1202 | } |
||
1203 | return $ret; |
||
1204 | } |
||
1205 | |||
1206 | /** |
||
1207 | * @param IContextSource $context |
||
1208 | * @return array |
||
1209 | */ |
||
1210 | static function getImageSizes( IContextSource $context ) { |
||
1211 | $ret = []; |
||
1212 | $pixels = $context->msg( 'unit-pixel' )->text(); |
||
1213 | |||
1214 | foreach ( $context->getConfig()->get( 'ImageLimits' ) as $index => $limits ) { |
||
1215 | $display = "{$limits[0]}×{$limits[1]}" . $pixels; |
||
1216 | $ret[$display] = $index; |
||
1217 | } |
||
1218 | |||
1219 | return $ret; |
||
1220 | } |
||
1221 | |||
1222 | /** |
||
1223 | * @param IContextSource $context |
||
1224 | * @return array |
||
1225 | */ |
||
1226 | static function getThumbSizes( IContextSource $context ) { |
||
1227 | $ret = []; |
||
1228 | $pixels = $context->msg( 'unit-pixel' )->text(); |
||
1229 | |||
1230 | foreach ( $context->getConfig()->get( 'ThumbLimits' ) as $index => $size ) { |
||
1231 | $display = $size . $pixels; |
||
1232 | $ret[$display] = $index; |
||
1233 | } |
||
1234 | |||
1235 | return $ret; |
||
1236 | } |
||
1237 | |||
1238 | /** |
||
1239 | * @param string $signature |
||
1240 | * @param array $alldata |
||
1241 | * @param HTMLForm $form |
||
1242 | * @return bool|string |
||
1243 | */ |
||
1244 | static function validateSignature( $signature, $alldata, $form ) { |
||
1245 | global $wgParser; |
||
1246 | $maxSigChars = $form->getConfig()->get( 'MaxSigChars' ); |
||
1247 | if ( mb_strlen( $signature ) > $maxSigChars ) { |
||
1248 | return Xml::element( 'span', [ 'class' => 'error' ], |
||
1249 | $form->msg( 'badsiglength' )->numParams( $maxSigChars )->text() ); |
||
1250 | } elseif ( isset( $alldata['fancysig'] ) && |
||
1251 | $alldata['fancysig'] && |
||
1252 | $wgParser->validateSig( $signature ) === false |
||
1253 | ) { |
||
1254 | return Xml::element( |
||
1255 | 'span', |
||
1256 | [ 'class' => 'error' ], |
||
1257 | $form->msg( 'badsig' )->text() |
||
1258 | ); |
||
1259 | } else { |
||
1260 | return true; |
||
1261 | } |
||
1262 | } |
||
1263 | |||
1264 | /** |
||
1265 | * @param string $signature |
||
1266 | * @param array $alldata |
||
1267 | * @param HTMLForm $form |
||
1268 | * @return string |
||
1269 | */ |
||
1270 | static function cleanSignature( $signature, $alldata, $form ) { |
||
1271 | if ( isset( $alldata['fancysig'] ) && $alldata['fancysig'] ) { |
||
1272 | global $wgParser; |
||
1273 | $signature = $wgParser->cleanSig( $signature ); |
||
1274 | } else { |
||
1275 | // When no fancy sig used, make sure ~{3,5} get removed. |
||
1276 | $signature = Parser::cleanSigInSig( $signature ); |
||
1277 | } |
||
1278 | |||
1279 | return $signature; |
||
1280 | } |
||
1281 | |||
1282 | /** |
||
1283 | * @param User $user |
||
1284 | * @param IContextSource $context |
||
1285 | * @param string $formClass |
||
1286 | * @param array $remove Array of items to remove |
||
1287 | * @return PreferencesForm|HtmlForm |
||
1288 | */ |
||
1289 | static function getFormObject( |
||
1290 | $user, |
||
1291 | IContextSource $context, |
||
1292 | $formClass = 'PreferencesForm', |
||
1293 | array $remove = [] |
||
1294 | ) { |
||
1295 | $formDescriptor = Preferences::getPreferences( $user, $context ); |
||
1296 | if ( count( $remove ) ) { |
||
1297 | $removeKeys = array_flip( $remove ); |
||
1298 | $formDescriptor = array_diff_key( $formDescriptor, $removeKeys ); |
||
1299 | } |
||
1300 | |||
1301 | // Remove type=api preferences. They are not intended for rendering in the form. |
||
1302 | foreach ( $formDescriptor as $name => $info ) { |
||
0 ignored issues
–
show
The expression
$formDescriptor of type array|null is not guaranteed to be traversable. How about adding an additional type check?
There are different options of fixing this problem.
![]() |
|||
1303 | if ( isset( $info['type'] ) && $info['type'] === 'api' ) { |
||
1304 | unset( $formDescriptor[$name] ); |
||
1305 | } |
||
1306 | } |
||
1307 | |||
1308 | /** |
||
1309 | * @var $htmlForm PreferencesForm |
||
1310 | */ |
||
1311 | $htmlForm = new $formClass( $formDescriptor, $context, 'prefs' ); |
||
1312 | |||
1313 | $htmlForm->setModifiedUser( $user ); |
||
1314 | $htmlForm->setId( 'mw-prefs-form' ); |
||
1315 | $htmlForm->setAutocomplete( 'off' ); |
||
1316 | $htmlForm->setSubmitText( $context->msg( 'saveprefs' )->text() ); |
||
1317 | # Used message keys: 'accesskey-preferences-save', 'tooltip-preferences-save' |
||
1318 | $htmlForm->setSubmitTooltip( 'preferences-save' ); |
||
1319 | $htmlForm->setSubmitID( 'prefsubmit' ); |
||
1320 | $htmlForm->setSubmitCallback( [ 'Preferences', 'tryFormSubmit' ] ); |
||
1321 | |||
1322 | return $htmlForm; |
||
1323 | } |
||
1324 | |||
1325 | /** |
||
1326 | * @param IContextSource $context |
||
1327 | * @return array |
||
1328 | */ |
||
1329 | static function getTimezoneOptions( IContextSource $context ) { |
||
1330 | $opt = []; |
||
1331 | |||
1332 | $localTZoffset = $context->getConfig()->get( 'LocalTZoffset' ); |
||
1333 | $timeZoneList = self::getTimeZoneList( $context->getLanguage() ); |
||
1334 | |||
1335 | $timestamp = MWTimestamp::getLocalInstance(); |
||
1336 | // Check that the LocalTZoffset is the same as the local time zone offset |
||
1337 | if ( $localTZoffset == $timestamp->format( 'Z' ) / 60 ) { |
||
1338 | $timezoneName = $timestamp->getTimezone()->getName(); |
||
1339 | // Localize timezone |
||
1340 | if ( isset( $timeZoneList[$timezoneName] ) ) { |
||
1341 | $timezoneName = $timeZoneList[$timezoneName]['name']; |
||
1342 | } |
||
1343 | $server_tz_msg = $context->msg( |
||
1344 | 'timezoneuseserverdefault', |
||
1345 | $timezoneName |
||
1346 | )->text(); |
||
1347 | } else { |
||
1348 | $tzstring = sprintf( |
||
1349 | '%+03d:%02d', |
||
1350 | floor( $localTZoffset / 60 ), |
||
1351 | abs( $localTZoffset ) % 60 |
||
1352 | ); |
||
1353 | $server_tz_msg = $context->msg( 'timezoneuseserverdefault', $tzstring )->text(); |
||
1354 | } |
||
1355 | $opt[$server_tz_msg] = "System|$localTZoffset"; |
||
1356 | $opt[$context->msg( 'timezoneuseoffset' )->text()] = 'other'; |
||
1357 | $opt[$context->msg( 'guesstimezone' )->text()] = 'guess'; |
||
1358 | |||
1359 | foreach ( $timeZoneList as $timeZoneInfo ) { |
||
1360 | $region = $timeZoneInfo['region']; |
||
1361 | if ( !isset( $opt[$region] ) ) { |
||
1362 | $opt[$region] = []; |
||
1363 | } |
||
1364 | $opt[$region][$timeZoneInfo['name']] = $timeZoneInfo['timecorrection']; |
||
1365 | } |
||
1366 | return $opt; |
||
1367 | } |
||
1368 | |||
1369 | /** |
||
1370 | * @param string $value |
||
1371 | * @param array $alldata |
||
1372 | * @return int |
||
1373 | */ |
||
1374 | static function filterIntval( $value, $alldata ) { |
||
1375 | return intval( $value ); |
||
1376 | } |
||
1377 | |||
1378 | /** |
||
1379 | * @param string $tz |
||
1380 | * @param array $alldata |
||
1381 | * @return string |
||
1382 | */ |
||
1383 | static function filterTimezoneInput( $tz, $alldata ) { |
||
1384 | $data = explode( '|', $tz, 3 ); |
||
1385 | switch ( $data[0] ) { |
||
1386 | case 'ZoneInfo': |
||
1387 | case 'System': |
||
1388 | return $tz; |
||
1389 | default: |
||
1390 | $data = explode( ':', $tz, 2 ); |
||
1391 | View Code Duplication | if ( count( $data ) == 2 ) { |
|
1392 | $data[0] = intval( $data[0] ); |
||
1393 | $data[1] = intval( $data[1] ); |
||
1394 | $minDiff = abs( $data[0] ) * 60 + $data[1]; |
||
1395 | if ( $data[0] < 0 ) { |
||
1396 | $minDiff = - $minDiff; |
||
1397 | } |
||
1398 | } else { |
||
1399 | $minDiff = intval( $data[0] ) * 60; |
||
1400 | } |
||
1401 | |||
1402 | # Max is +14:00 and min is -12:00, see: |
||
1403 | # https://en.wikipedia.org/wiki/Timezone |
||
1404 | $minDiff = min( $minDiff, 840 ); # 14:00 |
||
1405 | $minDiff = max( $minDiff, - 720 ); # -12:00 |
||
1406 | return 'Offset|' . $minDiff; |
||
1407 | } |
||
1408 | } |
||
1409 | |||
1410 | /** |
||
1411 | * Handle the form submission if everything validated properly |
||
1412 | * |
||
1413 | * @param array $formData |
||
1414 | * @param PreferencesForm $form |
||
1415 | * @return bool|Status|string |
||
1416 | */ |
||
1417 | static function tryFormSubmit( $formData, $form ) { |
||
1418 | $user = $form->getModifiedUser(); |
||
1419 | $hiddenPrefs = $form->getConfig()->get( 'HiddenPrefs' ); |
||
1420 | $result = true; |
||
1421 | |||
1422 | if ( !$user->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { |
||
1423 | return Status::newFatal( 'mypreferencesprotected' ); |
||
1424 | } |
||
1425 | |||
1426 | // Filter input |
||
1427 | foreach ( array_keys( $formData ) as $name ) { |
||
1428 | if ( isset( self::$saveFilters[$name] ) ) { |
||
1429 | $formData[$name] = |
||
1430 | call_user_func( self::$saveFilters[$name], $formData[$name], $formData ); |
||
1431 | } |
||
1432 | } |
||
1433 | |||
1434 | // Fortunately, the realname field is MUCH simpler |
||
1435 | // (not really "private", but still shouldn't be edited without permission) |
||
1436 | |||
1437 | if ( !in_array( 'realname', $hiddenPrefs ) |
||
1438 | && $user->isAllowed( 'editmyprivateinfo' ) |
||
1439 | && array_key_exists( 'realname', $formData ) |
||
1440 | ) { |
||
1441 | $realName = $formData['realname']; |
||
1442 | $user->setRealName( $realName ); |
||
1443 | } |
||
1444 | |||
1445 | if ( $user->isAllowed( 'editmyoptions' ) ) { |
||
1446 | foreach ( self::$saveBlacklist as $b ) { |
||
1447 | unset( $formData[$b] ); |
||
1448 | } |
||
1449 | |||
1450 | # If users have saved a value for a preference which has subsequently been disabled |
||
1451 | # via $wgHiddenPrefs, we don't want to destroy that setting in case the preference |
||
1452 | # is subsequently re-enabled |
||
1453 | foreach ( $hiddenPrefs as $pref ) { |
||
1454 | # If the user has not set a non-default value here, the default will be returned |
||
1455 | # and subsequently discarded |
||
1456 | $formData[$pref] = $user->getOption( $pref, null, true ); |
||
1457 | } |
||
1458 | |||
1459 | // Keep old preferences from interfering due to back-compat code, etc. |
||
1460 | $user->resetOptions( 'unused', $form->getContext() ); |
||
1461 | |||
1462 | foreach ( $formData as $key => $value ) { |
||
1463 | $user->setOption( $key, $value ); |
||
1464 | } |
||
1465 | |||
1466 | Hooks::run( 'PreferencesFormPreSave', [ $formData, $form, $user, &$result ] ); |
||
1467 | } |
||
1468 | |||
1469 | MediaWiki\Auth\AuthManager::callLegacyAuthPlugin( 'updateExternalDB', [ $user ] ); |
||
1470 | $user->saveSettings(); |
||
1471 | |||
1472 | return $result; |
||
1473 | } |
||
1474 | |||
1475 | /** |
||
1476 | * @param array $formData |
||
1477 | * @param PreferencesForm $form |
||
1478 | * @return Status |
||
1479 | */ |
||
1480 | public static function tryUISubmit( $formData, $form ) { |
||
1481 | $res = self::tryFormSubmit( $formData, $form ); |
||
1482 | |||
1483 | if ( $res ) { |
||
1484 | $urlOptions = []; |
||
1485 | |||
1486 | if ( $res === 'eauth' ) { |
||
1487 | $urlOptions['eauth'] = 1; |
||
1488 | } |
||
1489 | |||
1490 | $urlOptions += $form->getExtraSuccessRedirectParameters(); |
||
1491 | |||
1492 | $url = $form->getTitle()->getFullURL( $urlOptions ); |
||
1493 | |||
1494 | $context = $form->getContext(); |
||
1495 | // Set session data for the success message |
||
1496 | $context->getRequest()->getSession()->set( 'specialPreferencesSaveSuccess', 1 ); |
||
1497 | |||
1498 | $context->getOutput()->redirect( $url ); |
||
1499 | } |
||
1500 | |||
1501 | return Status::newGood(); |
||
1502 | } |
||
1503 | |||
1504 | /** |
||
1505 | * Get a list of all time zones |
||
1506 | * @param Language $language Language used for the localized names |
||
1507 | * @return array A list of all time zones. The system name of the time zone is used as key and |
||
1508 | * the value is an array which contains localized name, the timecorrection value used for |
||
1509 | * preferences and the region |
||
1510 | * @since 1.26 |
||
1511 | */ |
||
1512 | public static function getTimeZoneList( Language $language ) { |
||
1513 | $identifiers = DateTimeZone::listIdentifiers(); |
||
1514 | if ( $identifiers === false ) { |
||
1515 | return []; |
||
1516 | } |
||
1517 | sort( $identifiers ); |
||
1518 | |||
1519 | $tzRegions = [ |
||
1520 | 'Africa' => wfMessage( 'timezoneregion-africa' )->inLanguage( $language )->text(), |
||
1521 | 'America' => wfMessage( 'timezoneregion-america' )->inLanguage( $language )->text(), |
||
1522 | 'Antarctica' => wfMessage( 'timezoneregion-antarctica' )->inLanguage( $language )->text(), |
||
1523 | 'Arctic' => wfMessage( 'timezoneregion-arctic' )->inLanguage( $language )->text(), |
||
1524 | 'Asia' => wfMessage( 'timezoneregion-asia' )->inLanguage( $language )->text(), |
||
1525 | 'Atlantic' => wfMessage( 'timezoneregion-atlantic' )->inLanguage( $language )->text(), |
||
1526 | 'Australia' => wfMessage( 'timezoneregion-australia' )->inLanguage( $language )->text(), |
||
1527 | 'Europe' => wfMessage( 'timezoneregion-europe' )->inLanguage( $language )->text(), |
||
1528 | 'Indian' => wfMessage( 'timezoneregion-indian' )->inLanguage( $language )->text(), |
||
1529 | 'Pacific' => wfMessage( 'timezoneregion-pacific' )->inLanguage( $language )->text(), |
||
1530 | ]; |
||
1531 | asort( $tzRegions ); |
||
1532 | |||
1533 | $timeZoneList = []; |
||
1534 | |||
1535 | $now = new DateTime(); |
||
1536 | |||
1537 | foreach ( $identifiers as $identifier ) { |
||
1538 | $parts = explode( '/', $identifier, 2 ); |
||
1539 | |||
1540 | // DateTimeZone::listIdentifiers() returns a number of |
||
1541 | // backwards-compatibility entries. This filters them out of the |
||
1542 | // list presented to the user. |
||
1543 | if ( count( $parts ) !== 2 || !array_key_exists( $parts[0], $tzRegions ) ) { |
||
1544 | continue; |
||
1545 | } |
||
1546 | |||
1547 | // Localize region |
||
1548 | $parts[0] = $tzRegions[$parts[0]]; |
||
1549 | |||
1550 | $dateTimeZone = new DateTimeZone( $identifier ); |
||
1551 | $minDiff = floor( $dateTimeZone->getOffset( $now ) / 60 ); |
||
1552 | |||
1553 | $display = str_replace( '_', ' ', $parts[0] . '/' . $parts[1] ); |
||
1554 | $value = "ZoneInfo|$minDiff|$identifier"; |
||
1555 | |||
1556 | $timeZoneList[$identifier] = [ |
||
1557 | 'name' => $display, |
||
1558 | 'timecorrection' => $value, |
||
1559 | 'region' => $parts[0], |
||
1560 | ]; |
||
1561 | } |
||
1562 | |||
1563 | return $timeZoneList; |
||
1564 | } |
||
1565 | } |
||
1566 | |||
1567 | /** Some tweaks to allow js prefs to work */ |
||
1568 | class PreferencesForm extends HTMLForm { |
||
1569 | // Override default value from HTMLForm |
||
1570 | protected $mSubSectionBeforeFields = false; |
||
1571 | |||
1572 | private $modifiedUser; |
||
1573 | |||
1574 | /** |
||
1575 | * @param User $user |
||
1576 | */ |
||
1577 | public function setModifiedUser( $user ) { |
||
1578 | $this->modifiedUser = $user; |
||
1579 | } |
||
1580 | |||
1581 | /** |
||
1582 | * @return User |
||
1583 | */ |
||
1584 | public function getModifiedUser() { |
||
1585 | if ( $this->modifiedUser === null ) { |
||
1586 | return $this->getUser(); |
||
1587 | } else { |
||
1588 | return $this->modifiedUser; |
||
1589 | } |
||
1590 | } |
||
1591 | |||
1592 | /** |
||
1593 | * Get extra parameters for the query string when redirecting after |
||
1594 | * successful save. |
||
1595 | * |
||
1596 | * @return array |
||
1597 | */ |
||
1598 | public function getExtraSuccessRedirectParameters() { |
||
1599 | return []; |
||
1600 | } |
||
1601 | |||
1602 | /** |
||
1603 | * @param string $html |
||
1604 | * @return string |
||
1605 | */ |
||
1606 | function wrapForm( $html ) { |
||
1607 | $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html ); |
||
1608 | |||
1609 | return parent::wrapForm( $html ); |
||
1610 | } |
||
1611 | |||
1612 | /** |
||
1613 | * @return string |
||
1614 | */ |
||
1615 | function getButtons() { |
||
1616 | $attrs = [ 'id' => 'mw-prefs-restoreprefs' ]; |
||
1617 | |||
1618 | if ( !$this->getModifiedUser()->isAllowedAny( 'editmyprivateinfo', 'editmyoptions' ) ) { |
||
1619 | return ''; |
||
1620 | } |
||
1621 | |||
1622 | $html = parent::getButtons(); |
||
1623 | |||
1624 | if ( $this->getModifiedUser()->isAllowed( 'editmyoptions' ) ) { |
||
1625 | $t = SpecialPage::getTitleFor( 'Preferences', 'reset' ); |
||
1626 | |||
1627 | $html .= "\n" . Linker::link( $t, $this->msg( 'restoreprefs' )->escaped(), |
||
1628 | Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ) ); |
||
1629 | |||
1630 | $html = Xml::tags( 'div', [ 'class' => 'mw-prefs-buttons' ], $html ); |
||
1631 | } |
||
1632 | |||
1633 | return $html; |
||
1634 | } |
||
1635 | |||
1636 | /** |
||
1637 | * Separate multi-option preferences into multiple preferences, since we |
||
1638 | * have to store them separately |
||
1639 | * @param array $data |
||
1640 | * @return array |
||
1641 | */ |
||
1642 | function filterDataForSubmit( $data ) { |
||
1643 | foreach ( $this->mFlatFields as $fieldname => $field ) { |
||
1644 | if ( $field instanceof HTMLNestedFilterable ) { |
||
1645 | $info = $field->mParams; |
||
1646 | $prefix = isset( $info['prefix'] ) ? $info['prefix'] : $fieldname; |
||
1647 | foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) { |
||
1648 | $data["$prefix$key"] = $value; |
||
1649 | } |
||
1650 | unset( $data[$fieldname] ); |
||
1651 | } |
||
1652 | } |
||
1653 | |||
1654 | return $data; |
||
1655 | } |
||
1656 | |||
1657 | /** |
||
1658 | * Get the whole body of the form. |
||
1659 | * @return string |
||
1660 | */ |
||
1661 | function getBody() { |
||
1662 | return $this->displaySection( $this->mFieldTree, '', 'mw-prefsection-' ); |
||
1663 | } |
||
1664 | |||
1665 | /** |
||
1666 | * Get the "<legend>" for a given section key. Normally this is the |
||
1667 | * prefs-$key message but we'll allow extensions to override it. |
||
1668 | * @param string $key |
||
1669 | * @return string |
||
1670 | */ |
||
1671 | function getLegend( $key ) { |
||
1672 | $legend = parent::getLegend( $key ); |
||
1673 | Hooks::run( 'PreferencesGetLegend', [ $this, $key, &$legend ] ); |
||
1674 | return $legend; |
||
1675 | } |
||
1676 | |||
1677 | /** |
||
1678 | * Get the keys of each top level preference section. |
||
1679 | * @return array of section keys |
||
1680 | */ |
||
1681 | function getPreferenceSections() { |
||
1682 | return array_keys( array_filter( $this->mFieldTree, 'is_array' ) ); |
||
1683 | } |
||
1684 | } |
||
1685 |
In PHP, under loose comparison (like
==
, or!=
, orswitch
conditions), values of different types might be equal.For
string
values, the empty string''
is a special case, in particular the following results might be unexpected: