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