Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 5
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 onSaveUserOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 onPreferencesFormPreSave
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 localPreferencesFormPreSave
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3namespace GlobalPreferences;
4
5use MediaWiki\HTMLForm\HTMLForm;
6use MediaWiki\Output\Hook\BeforePageDisplayHook;
7use MediaWiki\Output\OutputPage;
8use MediaWiki\Preferences\Hook\PreferencesFormPreSaveHook;
9use MediaWiki\Skin\Skin;
10use MediaWiki\User\Options\Hook\SaveUserOptionsHook;
11use MediaWiki\User\Options\UserOptionsLookup;
12use MediaWiki\User\Options\UserOptionsManager;
13use MediaWiki\User\User;
14use MediaWiki\User\UserIdentity;
15
16class Hooks implements
17    BeforePageDisplayHook,
18    SaveUserOptionsHook,
19    PreferencesFormPreSaveHook
20    {
21
22    public function __construct(
23        private readonly GlobalPreferencesFactory $preferencesFactory,
24        private readonly UserOptionsManager $userOptionsManager,
25    ) {
26    }
27
28    /**
29     * Allows last minute changes to the output page, e.g. adding of CSS or JavaScript by extensions.
30     * @link https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
31     * @param OutputPage $out The output page.
32     * @param Skin $skin The skin. Not used.
33     */
34    public function onBeforePageDisplay( $out, $skin ): void {
35        if ( $out->getTitle()->isSpecial( 'Preferences' ) ) {
36            // Add module styles and scripts separately
37            // so non-JS users get the styles quicker and to avoid a FOUC.
38            $out->addModuleStyles( 'ext.GlobalPreferences.local-nojs' );
39            $out->addModules( 'ext.GlobalPreferences.local' );
40        }
41    }
42
43    /**
44     * When saving a user's options, remove any global ones and never save any on the Global
45     * Preferences page. Global options are saved separately, in the PreferencesFormPreSave hook.
46     *
47     * @see https://www.mediawiki.org/wiki/Manual:Hooks/SaveUserOptions
48     * @param UserIdentity $user The user.
49     * @param string[] &$modifiedOptions The user's options that were modified.
50     * @param string[] $originalOptions The original options.
51     * @return bool False if nothing changed, true otherwise.
52     */
53    public function onSaveUserOptions( UserIdentity $user, array &$modifiedOptions, array $originalOptions ) {
54        if ( $this->preferencesFactory->onGlobalPrefsPage() ) {
55            // It shouldn't be possible to save local options here,
56            // but never save on this page anyways.
57            return false;
58        }
59
60        $this->preferencesFactory->handleLocalPreferencesChange( $user, $modifiedOptions, $originalOptions );
61
62        return true;
63    }
64
65    /**
66     * @link https://www.mediawiki.org/wiki/Manual:Hooks/PreferencesFormPreSave
67     * @param array $formData An associative array containing the data from the preferences form.
68     * @param HTMLForm $form The HTMLForm object that represents the preferences form.
69     * @param User $user The User object that can be used to change the user's preferences.
70     * @param bool &$result The boolean return value of the Preferences::tryFormSubmit method.
71     * @param array $oldUserOptions Array with user's old options (before save)
72     * @return bool|void True or no return value to continue or false to abort
73     */
74    public function onPreferencesFormPreSave( $formData, $form, $user, &$result, $oldUserOptions ) {
75        if ( !$this->preferencesFactory->onGlobalPrefsPage( $form ) ) {
76            return $this->localPreferencesFormPreSave( $formData, $user );
77        }
78        return true;
79    }
80
81    /**
82     * Process PreferencesFormPreSave for Special:Preferences
83     * Handles CheckMatrix
84     *
85     * @param array $formData Associative array of [ preference name => value ]
86     * @param User $user Current user
87     * @return bool Hook return value
88     */
89    private function localPreferencesFormPreSave( array $formData, User $user ): bool {
90        foreach ( $formData as $pref => $value ) {
91            if ( !GlobalPreferencesFactory::isLocalPrefName( $pref ) ) {
92                continue;
93            }
94            // Determine the real name of the preference.
95            $suffixLen = strlen( UserOptionsLookup::LOCAL_EXCEPTION_SUFFIX );
96            $realName = substr( $pref, 0, -$suffixLen );
97            if ( isset( $formData[$realName] ) ) {
98                // Not a CheckMatrix field
99                continue;
100            }
101            $checkMatrix = preg_grep( "/^$realName-/", array_keys( $formData ) );
102            foreach ( $checkMatrix as $check ) {
103                $localExceptionName = $check . UserOptionsLookup::LOCAL_EXCEPTION_SUFFIX;
104                $this->userOptionsManager->setOption( $user, $localExceptionName, $value );
105            }
106        }
107        return true;
108    }
109}