Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
30.99% |
22 / 71 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
30.99% |
22 / 71 |
|
0.00% |
0 / 8 |
325.84 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onBeforePageDisplay | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
onLoadUserOptions | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
90 | |||
onSaveUserOptions | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onPreferencesFormPreSave | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
localPreferencesFormPreSave | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
onDeleteUnknownPreferences | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onApiOptions | |
95.65% |
22 / 23 |
|
0.00% |
0 / 1 |
8 |
1 | <?php |
2 | |
3 | namespace GlobalPreferences; |
4 | |
5 | use ApiOptions; |
6 | use HTMLForm; |
7 | use MediaWiki\Api\Hook\ApiOptionsHook; |
8 | use MediaWiki\Config\Config; |
9 | use MediaWiki\Hook\BeforePageDisplayHook; |
10 | use MediaWiki\Hook\DeleteUnknownPreferencesHook; |
11 | use MediaWiki\Logger\LoggerFactory; |
12 | use MediaWiki\Output\OutputPage; |
13 | use MediaWiki\Preferences\Hook\PreferencesFormPreSaveHook; |
14 | use MediaWiki\Preferences\PreferencesFactory; |
15 | use MediaWiki\User\Options\Hook\LoadUserOptionsHook; |
16 | use MediaWiki\User\Options\Hook\SaveUserOptionsHook; |
17 | use MediaWiki\User\Options\UserOptionsLookup; |
18 | use MediaWiki\User\Options\UserOptionsManager; |
19 | use MediaWiki\User\User; |
20 | use MediaWiki\User\UserIdentity; |
21 | use Message; |
22 | use Skin; |
23 | use Wikimedia\Rdbms\IDatabase; |
24 | |
25 | class Hooks implements |
26 | BeforePageDisplayHook, |
27 | LoadUserOptionsHook, |
28 | SaveUserOptionsHook, |
29 | PreferencesFormPreSaveHook, |
30 | DeleteUnknownPreferencesHook, |
31 | ApiOptionsHook |
32 | { |
33 | |
34 | /** @var GlobalPreferencesFactory */ |
35 | private $preferencesFactory; |
36 | |
37 | /** @var UserOptionsManager */ |
38 | private $userOptionsManager; |
39 | |
40 | /** @var UserOptionsLookup */ |
41 | private $userOptionsLookup; |
42 | |
43 | /** @var Config */ |
44 | private $config; |
45 | |
46 | /** |
47 | * @param PreferencesFactory $preferencesFactory |
48 | * @param UserOptionsManager $userOptionsManager |
49 | * @param UserOptionsLookup $userOptionsLookup |
50 | * @param Config $config |
51 | */ |
52 | public function __construct( |
53 | PreferencesFactory $preferencesFactory, |
54 | UserOptionsManager $userOptionsManager, |
55 | UserOptionsLookup $userOptionsLookup, |
56 | Config $config |
57 | ) { |
58 | $this->preferencesFactory = $preferencesFactory; |
59 | $this->userOptionsManager = $userOptionsManager; |
60 | $this->userOptionsLookup = $userOptionsLookup; |
61 | $this->config = $config; |
62 | } |
63 | |
64 | /** |
65 | * Allows last minute changes to the output page, e.g. adding of CSS or JavaScript by extensions. |
66 | * @link https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay |
67 | * @param OutputPage $out The output page. |
68 | * @param Skin $skin The skin. Not used. |
69 | */ |
70 | public function onBeforePageDisplay( $out, $skin ): void { |
71 | if ( $out->getTitle()->isSpecial( 'Preferences' ) ) { |
72 | // Add module styles and scripts separately |
73 | // so non-JS users get the styles quicker and to avoid a FOUC. |
74 | $out->addModuleStyles( 'ext.GlobalPreferences.local-nojs' ); |
75 | $out->addModules( 'ext.GlobalPreferences.local' ); |
76 | } |
77 | } |
78 | |
79 | /** |
80 | * Load global preferences. |
81 | * @link https://www.mediawiki.org/wiki/Manual:Hooks/LoadUserOptions |
82 | * @param UserIdentity $user The user for whom options are being loaded. |
83 | * @param array &$options The user's options; can be modified. |
84 | */ |
85 | public function onLoadUserOptions( UserIdentity $user, array &$options ): void { |
86 | if ( !$this->preferencesFactory->isUserGlobalized( $user ) ) { |
87 | // Not a global user. |
88 | return; |
89 | } |
90 | |
91 | $logger = LoggerFactory::getInstance( 'preferences' ); |
92 | $logger->debug( |
93 | 'Loading global options for user \'{user}\'', |
94 | [ 'user' => $user->getName() ] |
95 | ); |
96 | // Overwrite all options that have a global counterpart. |
97 | $globalPrefs = $this->preferencesFactory->getGlobalPreferencesValues( $user ); |
98 | if ( $globalPrefs === false ) { |
99 | return; |
100 | } |
101 | foreach ( $globalPrefs as $optName => $globalValue ) { |
102 | // Don't overwrite if it has a local exception. |
103 | $localExceptionName = $optName . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; |
104 | if ( isset( $options[ $localExceptionName ] ) && $options[ $localExceptionName ] ) { |
105 | continue; |
106 | } |
107 | |
108 | // FIXME: temporary plug for T201340: DB might have rows for deglobalized |
109 | // Echo notifications. Don't allow these through if the main checkbox is not checked. |
110 | if ( !( $globalPrefs['echo-subscriptions'] ?? false ) |
111 | && strpos( $optName, 'echo-subscriptions-' ) === 0 |
112 | ) { |
113 | continue; |
114 | } |
115 | |
116 | // Convert '0' to 0. PHP's boolean conversion considers them both false, |
117 | // but e.g. JavaScript considers the former as true. |
118 | if ( $globalValue === '0' ) { |
119 | $globalValue = 0; |
120 | } |
121 | $options[ $optName ] = $globalValue; |
122 | } |
123 | } |
124 | |
125 | /** |
126 | * When saving a user's options, remove any global ones and never save any on the Global |
127 | * Preferences page. Global options are saved separately, in the PreferencesFormPreSave hook. |
128 | * |
129 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/SaveUserOptions |
130 | * @param UserIdentity $user The user. |
131 | * @param string[] &$modifiedOptions The user's options that were modified. |
132 | * @param string[] $originalOptions The original options. |
133 | * @return bool False if nothing changed, true otherwise. |
134 | */ |
135 | public function onSaveUserOptions( UserIdentity $user, array &$modifiedOptions, array $originalOptions ) { |
136 | if ( $this->preferencesFactory->onGlobalPrefsPage() ) { |
137 | // It shouldn't be possible to save local options here, |
138 | // but never save on this page anyways. |
139 | return false; |
140 | } |
141 | |
142 | $this->preferencesFactory->handleLocalPreferencesChange( $user, $modifiedOptions, $originalOptions ); |
143 | |
144 | return true; |
145 | } |
146 | |
147 | /** |
148 | * @link https://www.mediawiki.org/wiki/Manual:Hooks/PreferencesFormPreSave |
149 | * @param array $formData An associative array containing the data from the preferences form. |
150 | * @param HTMLForm $form The HTMLForm object that represents the preferences form. |
151 | * @param User $user The User object that can be used to change the user's preferences. |
152 | * @param bool &$result The boolean return value of the Preferences::tryFormSubmit method. |
153 | * @param array $oldUserOptions Array with user's old options (before save) |
154 | * @return bool|void True or no return value to continue or false to abort |
155 | */ |
156 | public function onPreferencesFormPreSave( $formData, $form, $user, &$result, $oldUserOptions ) { |
157 | if ( !$this->preferencesFactory->onGlobalPrefsPage( $form ) ) { |
158 | return $this->localPreferencesFormPreSave( $formData, $user ); |
159 | } |
160 | return true; |
161 | } |
162 | |
163 | /** |
164 | * Process PreferencesFormPreSave for Special:Preferences |
165 | * Handles CheckMatrix |
166 | * |
167 | * @param array $formData Associative array of [ preference name => value ] |
168 | * @param User $user Current user |
169 | * @return bool Hook return value |
170 | */ |
171 | private function localPreferencesFormPreSave( array $formData, User $user ): bool { |
172 | foreach ( $formData as $pref => $value ) { |
173 | if ( !GlobalPreferencesFactory::isLocalPrefName( $pref ) ) { |
174 | continue; |
175 | } |
176 | // Determine the real name of the preference. |
177 | $suffixLen = strlen( GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX ); |
178 | $realName = substr( $pref, 0, -$suffixLen ); |
179 | if ( isset( $formData[$realName] ) ) { |
180 | // Not a CheckMatrix field |
181 | continue; |
182 | } |
183 | $checkMatrix = preg_grep( "/^$realName-/", array_keys( $formData ) ); |
184 | foreach ( $checkMatrix as $check ) { |
185 | $localExceptionName = $check . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; |
186 | $this->userOptionsManager->setOption( $user, $localExceptionName, $value ); |
187 | } |
188 | } |
189 | return true; |
190 | } |
191 | |
192 | /** |
193 | * Prevent local exception preferences from being cleaned up. |
194 | * @link https://www.mediawiki.org/wiki/Manual:Hooks/DeleteUnknownPreferences |
195 | * @param string[] &$where Array of where clause conditions to add to. |
196 | * @param IDatabase $db |
197 | */ |
198 | public function onDeleteUnknownPreferences( &$where, $db ) { |
199 | $like = $db->buildLike( $db->anyString(), GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX ); |
200 | $where[] = "up_property NOT $like"; |
201 | } |
202 | |
203 | /** |
204 | * @param ApiOptions $apiModule Calling ApiOptions object |
205 | * @param User $user User object whose preferences are being changed |
206 | * @param array $changes Associative array of preference name => value |
207 | * @param string[] $resetKinds Array of strings specifying which options kinds to reset |
208 | * See User::resetOptions() and User::getOptionKinds() for possible values. |
209 | * @return bool|void True or no return value to continue or false to abort |
210 | */ |
211 | public function onApiOptions( $apiModule, $user, $changes, $resetKinds ) { |
212 | // Only hook to the core module but not to our code that inherits from it |
213 | if ( $apiModule->getModuleName() !== 'options' ) { |
214 | return; |
215 | } |
216 | |
217 | $globalPrefs = $this->preferencesFactory->getGlobalPreferencesValues( $user ); |
218 | |
219 | $toWarn = []; |
220 | foreach ( array_keys( $changes ) as $preference ) { |
221 | if ( GlobalPreferencesFactory::isLocalPrefName( $preference ) ) { |
222 | continue; |
223 | } |
224 | $exceptionName = $preference . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; |
225 | if ( !$this->userOptionsLookup->getOption( $user, $exceptionName ) ) { |
226 | if ( $globalPrefs && array_key_exists( $preference, $globalPrefs ) ) { |
227 | $toWarn[] = $preference; |
228 | } |
229 | } |
230 | } |
231 | if ( $toWarn ) { |
232 | $toWarn = array_map( static function ( $str ) { |
233 | return wfEscapeWikiText( "`$str`" ); |
234 | }, $toWarn ); |
235 | $apiModule->addWarning( |
236 | [ |
237 | 'apiwarn-globally-overridden', |
238 | Message::listParam( $toWarn ), |
239 | count( $toWarn ), |
240 | ], |
241 | 'globally-overridden' |
242 | ); |
243 | } |
244 | } |
245 | } |