Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 199 |
|
0.00% |
0 / 20 |
CRAP | |
0.00% |
0 / 1 |
PreferencesFormOOUI | |
0.00% |
0 / 199 |
|
0.00% |
0 / 20 |
1640 | |
0.00% |
0 / 1 |
setModifiedUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getModifiedUser | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
isPrivateInfoEditable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setPrivateInfoEditable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
areOptionsEditable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setOptionsEditable | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getExtraSuccessRedirectParameters | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
wrapForm | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
filterDataForSubmit | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
wrapFieldSetSection | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
isMobileLayout | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
addFields | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
72 | |||
getBody | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
6 | |||
getLegend | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getPreferenceSections | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
createMobilePreferencesForm | |
0.00% |
0 / 42 |
|
0.00% |
0 / 1 |
12 | |||
getIconNames | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
createMobileDescription | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
createContentMobile | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
2 | |||
createDesktopPreferencesForm | |
0.00% |
0 / 44 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | use MediaWiki\HTMLForm\Field\HTMLCheckField; |
22 | use MediaWiki\HTMLForm\Field\HTMLToggleSwitchField; |
23 | use MediaWiki\HTMLForm\HTMLNestedFilterable; |
24 | use MediaWiki\HTMLForm\OOUIHTMLForm; |
25 | use MediaWiki\User\User; |
26 | |
27 | /** |
28 | * Form to edit user preferences. |
29 | * |
30 | * @since 1.32 |
31 | */ |
32 | class PreferencesFormOOUI extends OOUIHTMLForm { |
33 | /** @var bool Override default value from HTMLForm */ |
34 | protected $mSubSectionBeforeFields = false; |
35 | |
36 | /** @var User|null */ |
37 | private $modifiedUser; |
38 | |
39 | /** @var bool */ |
40 | private $privateInfoEditable = true; |
41 | |
42 | /** @var bool */ |
43 | private $optionsEditable = true; |
44 | |
45 | /** @var bool */ |
46 | private $useMobileLayout; |
47 | |
48 | /** |
49 | * @param User $user |
50 | */ |
51 | public function setModifiedUser( $user ) { |
52 | $this->modifiedUser = $user; |
53 | } |
54 | |
55 | /** |
56 | * @return User |
57 | */ |
58 | public function getModifiedUser() { |
59 | if ( $this->modifiedUser === null ) { |
60 | return $this->getUser(); |
61 | } else { |
62 | return $this->modifiedUser; |
63 | } |
64 | } |
65 | |
66 | /** |
67 | * @return bool |
68 | */ |
69 | public function isPrivateInfoEditable() { |
70 | return $this->privateInfoEditable; |
71 | } |
72 | |
73 | /** |
74 | * Whether the |
75 | * @param bool $editable |
76 | */ |
77 | public function setPrivateInfoEditable( $editable ) { |
78 | $this->privateInfoEditable = $editable; |
79 | $this->suppressDefaultSubmit( !$this->privateInfoEditable && !$this->optionsEditable ); |
80 | } |
81 | |
82 | /** |
83 | * @return bool |
84 | */ |
85 | public function areOptionsEditable() { |
86 | return $this->optionsEditable; |
87 | } |
88 | |
89 | /** |
90 | * @param bool $optionsEditable |
91 | */ |
92 | public function setOptionsEditable( $optionsEditable ) { |
93 | $this->optionsEditable = $optionsEditable; |
94 | $this->suppressDefaultSubmit( !$this->privateInfoEditable && !$this->optionsEditable ); |
95 | } |
96 | |
97 | /** |
98 | * Get extra parameters for the query string when redirecting after |
99 | * successful save. |
100 | * |
101 | * @return array |
102 | */ |
103 | public function getExtraSuccessRedirectParameters() { |
104 | return []; |
105 | } |
106 | |
107 | public function wrapForm( $html ) { |
108 | $html = Xml::tags( 'div', [ 'id' => 'preferences' ], $html ); |
109 | |
110 | return parent::wrapForm( $html ); |
111 | } |
112 | |
113 | /** |
114 | * Separate multi-option preferences into multiple preferences, since we |
115 | * have to store them separately |
116 | * @param array $data |
117 | * @return array |
118 | */ |
119 | public function filterDataForSubmit( $data ) { |
120 | foreach ( $this->mFlatFields as $fieldname => $field ) { |
121 | if ( $field instanceof HTMLNestedFilterable ) { |
122 | $info = $field->mParams; |
123 | $prefix = $info['prefix'] ?? $fieldname; |
124 | foreach ( $field->filterDataForSubmit( $data[$fieldname] ) as $key => $value ) { |
125 | $data["$prefix$key"] = $value; |
126 | } |
127 | unset( $data[$fieldname] ); |
128 | } |
129 | } |
130 | |
131 | return $data; |
132 | } |
133 | |
134 | protected function wrapFieldSetSection( $legend, $section, $attributes, $isRoot ) { |
135 | $layout = parent::wrapFieldSetSection( $legend, $section, $attributes, $isRoot ); |
136 | |
137 | $layout->addClasses( [ 'mw-prefs-fieldset-wrapper' ] ); |
138 | $layout->removeClasses( [ 'oo-ui-panelLayout-framed' ] ); |
139 | |
140 | return $layout; |
141 | } |
142 | |
143 | private function isMobileLayout() { |
144 | if ( $this->useMobileLayout === null ) { |
145 | $skin = $this->getSkin(); |
146 | $this->useMobileLayout = false; |
147 | $this->getHookRunner()->onPreferencesGetLayout( $this->useMobileLayout, |
148 | $skin->getSkinName(), [ 'isResponsive' => $skin->isResponsive() ] ); |
149 | } |
150 | return $this->useMobileLayout; |
151 | } |
152 | |
153 | /** |
154 | * @inheritDoc |
155 | */ |
156 | public function addFields( $descriptor ) { |
157 | // Replace checkbox fields with toggle switchs on Special:Preferences |
158 | if ( $this->isMobileLayout() && $this->getTitle()->isSpecial( 'Preferences' ) ) { |
159 | foreach ( $descriptor as $_ => &$info ) { |
160 | if ( isset( $info['type'] ) && in_array( $info['type'], [ 'check', 'toggle' ] ) ) { |
161 | unset( $info['type'] ); |
162 | $info['class'] = HTMLToggleSwitchField::class; |
163 | } elseif ( isset( $info['class'] ) && $info['class'] === HTMLCheckField::class ) { |
164 | $info['class'] = HTMLToggleSwitchField::class; |
165 | } |
166 | } |
167 | } |
168 | return parent::addFields( $descriptor ); |
169 | } |
170 | |
171 | /** |
172 | * Get the whole body of the form. |
173 | * @return string |
174 | */ |
175 | public function getBody() { |
176 | if ( $this->isMobileLayout() ) { |
177 | // Import the icons used in the mobile view |
178 | $this->getOutput()->addModuleStyles( |
179 | [ |
180 | 'oojs-ui.styles.icons-user', |
181 | 'oojs-ui.styles.icons-editing-core', |
182 | 'oojs-ui.styles.icons-editing-advanced', |
183 | 'oojs-ui.styles.icons-wikimediaui', |
184 | 'oojs-ui.styles.icons-content', |
185 | 'oojs-ui.styles.icons-moderation', |
186 | 'oojs-ui.styles.icons-interactions', |
187 | 'oojs-ui.styles.icons-movement', |
188 | 'oojs-ui.styles.icons-wikimedia', |
189 | 'oojs-ui.styles.icons-media', |
190 | 'oojs-ui.styles.icons-accessibility', |
191 | 'oojs-ui.styles.icons-layout', |
192 | ] |
193 | ); |
194 | $form = $this->createMobilePreferencesForm(); |
195 | } else { |
196 | $form = $this->createDesktopPreferencesForm(); |
197 | } |
198 | |
199 | $header = $this->formatFormHeader(); |
200 | |
201 | return $header . $form; |
202 | } |
203 | |
204 | /** |
205 | * Get the "<legend>" for a given section key. Normally this is the |
206 | * prefs-$key message but we'll allow extensions to override it. |
207 | * @param string $key |
208 | * @return string |
209 | */ |
210 | public function getLegend( $key ) { |
211 | $legend = parent::getLegend( $key ); |
212 | $this->getHookRunner()->onPreferencesGetLegend( $this, $key, $legend ); |
213 | return $legend; |
214 | } |
215 | |
216 | /** |
217 | * Get the keys of each top level preference section. |
218 | * @return string[] List of section keys |
219 | */ |
220 | public function getPreferenceSections() { |
221 | return array_keys( array_filter( $this->mFieldTree, 'is_array' ) ); |
222 | } |
223 | |
224 | /** |
225 | * Create the preferences form for a mobile layout. |
226 | * @return OOUI\Tag |
227 | */ |
228 | private function createMobilePreferencesForm() { |
229 | $sectionButtons = []; |
230 | $sectionContents = []; |
231 | $iconNames = $this->getIconNames(); |
232 | |
233 | foreach ( $this->mFieldTree as $key => $val ) { |
234 | if ( !is_array( $val ) ) { |
235 | wfDebug( __METHOD__ . " encountered a field not attached to a section: '$key'" ); |
236 | continue; |
237 | } |
238 | $label = $this->getLegend( $key ); |
239 | $content = |
240 | $this->getHeaderHtml( $key ) . |
241 | $this->displaySection( |
242 | $val, |
243 | "", |
244 | "mw-prefsection-$key-" |
245 | ) . |
246 | $this->getFooterHtml( $key ); |
247 | |
248 | // Creating the header section |
249 | $label = ( new OOUI\Tag( 'div' ) )->appendContent( |
250 | ( new OOUI\Tag( 'h5' ) )->appendContent( $label )->addClasses( [ 'mw-prefs-title' ] ), |
251 | $this->createMobileDescription( $key ) |
252 | ); |
253 | $contentDiv = $this->createContentMobile( $key, $label, $content ); |
254 | |
255 | $sectionButton = new OOUI\ButtonWidget( [ |
256 | 'id' => 'mw-mobile-prefs-' . $key, |
257 | 'icon' => $iconNames[ $key ] ?? 'settings', |
258 | 'label' => new OOUI\HtmlSnippet( $label->toString() ), |
259 | 'data' => $key, |
260 | 'classes' => [ 'mw-mobile-prefsection' ], |
261 | 'framed' => false, |
262 | ] ); |
263 | $sectionButtons[] = $sectionButton; |
264 | $sectionContents[] = $contentDiv; |
265 | } |
266 | |
267 | $buttonGroup = new OOUI\ButtonGroupWidget( [ |
268 | 'classes' => [ 'mw-mobile-prefs-sections' ], |
269 | 'infusable' => true, |
270 | ] ); |
271 | $buttonGroup->addItems( $sectionButtons ); |
272 | $form = ( new OOUI\Tag( 'div' ) ) |
273 | ->setAttributes( [ 'id' => 'mw-prefs-container' ] ) |
274 | ->addClasses( [ 'mw-mobile-prefs-container' ] ) |
275 | ->appendContent( $buttonGroup ) |
276 | ->appendContent( $sectionContents ); |
277 | |
278 | return $form; |
279 | } |
280 | |
281 | /** |
282 | * Get the icon names for each mobile preference section. |
283 | * @return array |
284 | */ |
285 | private function getIconNames() { |
286 | $iconNames = [ |
287 | 'personal' => 'userAvatar', |
288 | 'rendering' => 'palette', |
289 | 'editing' => 'edit', |
290 | 'rc' => 'recentChanges', |
291 | 'watchlist' => 'watchlist', |
292 | 'searchoptions' => 'search', |
293 | 'misc' => '', |
294 | ]; |
295 | $hookIcons = []; |
296 | // Get icons from extensions that have their own sections |
297 | $this->getHookRunner()->onPreferencesGetIcon( $hookIcons ); |
298 | $iconNames += $hookIcons; |
299 | |
300 | return $iconNames; |
301 | } |
302 | |
303 | /** |
304 | * Creates a description tag for each section of the mobile layout. |
305 | * @param string $key |
306 | * @return OOUI\Tag |
307 | */ |
308 | private function createMobileDescription( $key ) { |
309 | $prefDescriptionMsg = $this->msg( "prefs-description-" . $key ); |
310 | $prefDescription = $prefDescriptionMsg->exists() ? $prefDescriptionMsg->text() : ""; |
311 | $prefDescriptionElement = ( new OOUI\Tag( 'p' ) ) |
312 | ->appendContent( $prefDescription ) |
313 | ->addClasses( [ 'mw-prefs-description' ] ); |
314 | |
315 | return $prefDescriptionElement; |
316 | } |
317 | |
318 | /** |
319 | * Creates the contents for each section of the mobile layout. |
320 | * @param string $key |
321 | * @param string $label |
322 | * @param string $content |
323 | * @return OOUI\Tag |
324 | */ |
325 | private function createContentMobile( $key, $label, $content ) { |
326 | $contentDiv = ( new OOUI\Tag( 'div' ) ); |
327 | $contentDiv->addClasses( [ |
328 | 'mw-prefs-content-page', |
329 | 'mw-prefs-section-fieldset', |
330 | ] ); |
331 | $contentDiv->setAttributes( [ |
332 | 'id' => 'mw-mobile-prefs-' . $key |
333 | ] ); |
334 | $contentBody = ( new OOUI\Tag( 'div' ) ) |
335 | ->addClasses( [ 'mw-htmlform-autoinfuse-lazy' ] ) |
336 | ->setAttributes( [ |
337 | 'id' => 'mw-mobile-prefs-' . $key . '-content' |
338 | ] ); |
339 | $contentHeader = ( new OOUI\Tag( 'div' ) )->setAttributes( [ |
340 | 'id' => 'mw-mobile-prefs-' . $key . '-head' |
341 | ] ); |
342 | $contentHeader->addClasses( [ 'mw-prefs-content-head' ] ); |
343 | $contentHeaderTitle = ( new OOUI\Tag( 'h5' ) )->setAttributes( [ |
344 | 'id' => 'mw-mobile-prefs-' . $key . '-title', |
345 | ] ); |
346 | $contentHeaderTitle->appendContent( $label )->addClasses( [ 'mw-prefs-header-title' ] ); |
347 | $formContent = new OOUI\Widget( [ |
348 | 'content' => new OOUI\HtmlSnippet( $content ) |
349 | ] ); |
350 | $hiddenForm = ( new OOUI\Tag( 'div' ) )->appendContent( $formContent ); |
351 | $contentHeader->appendContent( $contentHeaderTitle ); |
352 | $contentBody->appendContent( $contentHeader ); |
353 | $contentBody->appendContent( $hiddenForm ); |
354 | $contentDiv->appendContent( $contentBody ); |
355 | |
356 | return $contentDiv; |
357 | } |
358 | |
359 | /** |
360 | * Create the preferences form for a desktop layout. |
361 | * @return OOUI\PanelLayout |
362 | */ |
363 | private function createDesktopPreferencesForm() { |
364 | $tabPanels = []; |
365 | foreach ( $this->mFieldTree as $key => $val ) { |
366 | if ( !is_array( $val ) ) { |
367 | wfDebug( __METHOD__ . " encountered a field not attached to a section: '$key'" ); |
368 | continue; |
369 | } |
370 | $label = $this->getLegend( $key ); |
371 | $content = |
372 | $this->getHeaderHtml( $key ) . |
373 | $this->displaySection( |
374 | $val, |
375 | "", |
376 | "mw-prefsection-$key-" |
377 | ) . |
378 | $this->getFooterHtml( $key ); |
379 | |
380 | $tabPanels[] = new OOUI\TabPanelLayout( 'mw-prefsection-' . $key, [ |
381 | 'classes' => [ 'mw-htmlform-autoinfuse-lazy' ], |
382 | 'label' => $label, |
383 | 'content' => new OOUI\FieldsetLayout( [ |
384 | 'classes' => [ 'mw-prefs-section-fieldset' ], |
385 | 'id' => "mw-prefsection-$key", |
386 | 'label' => $label, |
387 | 'items' => [ |
388 | new OOUI\Widget( [ |
389 | 'content' => new OOUI\HtmlSnippet( $content ) |
390 | ] ), |
391 | ], |
392 | ] ), |
393 | 'expanded' => false, |
394 | 'framed' => true, |
395 | ] ); |
396 | } |
397 | |
398 | $indexLayout = new OOUI\IndexLayout( [ |
399 | 'infusable' => true, |
400 | 'expanded' => false, |
401 | 'autoFocus' => false, |
402 | 'classes' => [ 'mw-prefs-tabs' ], |
403 | ] ); |
404 | $indexLayout->addTabPanels( $tabPanels ); |
405 | |
406 | $form = new OOUI\PanelLayout( [ |
407 | 'framed' => true, |
408 | 'expanded' => false, |
409 | 'classes' => [ 'mw-prefs-tabs-wrapper' ], |
410 | 'content' => $indexLayout |
411 | ] ); |
412 | |
413 | return $form; |
414 | } |
415 | } |