Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 325 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
CentralNoticeHooks | |
0.00% |
0 / 325 |
|
0.00% |
0 / 15 |
3192 | |
0.00% |
0 / 1 |
onRegistration | |
0.00% |
0 / 194 |
|
0.00% |
0 / 1 |
72 | |||
addCascadingRestrictionRight | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
initCentralNotice | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
onCanonicalNamespaces | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
onBeforePageDisplay | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
110 | |||
onMakeGlobalVariablesScript | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
onSiteNoticeAfter | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onResourceLoaderGetConfigVars | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
20 | |||
onResourceLoaderRegisterModules | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
onChangeTagsListActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onListDefinedTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addDefinedTags | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onGetPreferences | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
12 | |||
onPreferencesGetIcon | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onSkinTemplateNavigation__Universal | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | |
3 | // phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
4 | |
5 | use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook; |
6 | use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook; |
7 | use MediaWiki\Hook\CanonicalNamespacesHook; |
8 | use MediaWiki\Hook\PreferencesGetIconHook; |
9 | use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
12 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook; |
13 | use MediaWiki\ResourceLoader\ResourceLoader; |
14 | |
15 | /** |
16 | * General hook definitions |
17 | * |
18 | * This file is part of the CentralNotice Extension to MediaWiki |
19 | * https://www.mediawiki.org/wiki/Extension:CentralNotice |
20 | * |
21 | * @file |
22 | * @ingroup Extensions |
23 | * |
24 | * @section LICENSE |
25 | * This program is free software; you can redistribute it and/or modify |
26 | * it under the terms of the GNU General Public License as published by |
27 | * the Free Software Foundation; either version 2 of the License, or |
28 | * (at your option) any later version. |
29 | * |
30 | * This program is distributed in the hope that it will be useful, |
31 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
32 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
33 | * GNU General Public License for more details. |
34 | * |
35 | * You should have received a copy of the GNU General Public License along |
36 | * with this program; if not, write to the Free Software Foundation, Inc., |
37 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
38 | * http://www.gnu.org/copyleft/gpl.html |
39 | */ |
40 | |
41 | class CentralNoticeHooks implements |
42 | CanonicalNamespacesHook, |
43 | ChangeTagsListActiveHook, |
44 | ListDefinedTagsHook, |
45 | SkinTemplateNavigation__UniversalHook, |
46 | ResourceLoaderRegisterModulesHook, |
47 | GetPreferencesHook, |
48 | PreferencesGetIconHook |
49 | { |
50 | |
51 | /** |
52 | * Conditional configuration |
53 | */ |
54 | public static function onRegistration() { |
55 | // @phpcs:ignore MediaWiki.Usage.DeprecatedGlobalVariables.Deprecated$wgHooks |
56 | global $wgHooks, $wgNoticeInfrastructure, $wgSpecialPages, |
57 | $wgCentralNoticeLoader, $wgNoticeUseTranslateExtension, |
58 | $wgAvailableRights, $wgGroupPermissions, $wgCentralDBname, |
59 | $wgDBname, $wgCentralNoticeAdminGroup, |
60 | $wgCentralNoticeMessageProtectRight, $wgResourceModules, |
61 | $wgDefaultUserOptions; |
62 | |
63 | // Default for a standalone wiki is that the CN tables are in the main database. |
64 | if ( !$wgCentralDBname ) { |
65 | $wgCentralDBname = $wgDBname; |
66 | } |
67 | |
68 | // If CentralNotice banners should be shown on this wiki, load the components we need for |
69 | // showing banners. For discussion of banner loading strategies, see |
70 | // http://wikitech.wikimedia.org/view/CentralNotice/Optimizing_banner_loading |
71 | if ( $wgCentralNoticeLoader ) { |
72 | $wgHooks['MakeGlobalVariablesScript'][] = |
73 | 'CentralNoticeHooks::onMakeGlobalVariablesScript'; |
74 | $wgHooks['BeforePageDisplay'][] = 'CentralNoticeHooks::onBeforePageDisplay'; |
75 | $wgHooks['SiteNoticeAfter'][] = 'CentralNoticeHooks::onSiteNoticeAfter'; |
76 | $wgHooks['ResourceLoaderGetConfigVars'][] = |
77 | 'CentralNoticeHooks::onResourceLoaderGetConfigVars'; |
78 | } |
79 | |
80 | // Set default user preferences for campaign type filtering. |
81 | // All types are on by default. |
82 | foreach ( CampaignType::getTypes() as $type ) { |
83 | $wgDefaultUserOptions[ $type->getPreferenceKey() ] = 1; |
84 | } |
85 | |
86 | // If this is the wiki that hosts the management interface, load further components |
87 | if ( $wgNoticeInfrastructure ) { |
88 | if ( $wgNoticeUseTranslateExtension ) { |
89 | $wgHooks['TranslatePostInitGroups'][] = 'BannerMessageGroup::registerGroupHook'; |
90 | $wgHooks['TranslateEventMessageGroupStateChange'][] = |
91 | 'BannerMessageGroup::updateBannerGroupStateHook'; |
92 | } |
93 | |
94 | $wgSpecialPages['CentralNotice'] = CentralNotice::class; |
95 | $wgSpecialPages['NoticeTemplate'] = SpecialNoticeTemplate::class; |
96 | $wgSpecialPages['BannerAllocation'] = SpecialBannerAllocation::class; |
97 | $wgSpecialPages['CentralNoticeLogs'] = SpecialCentralNoticeLogs::class; |
98 | $wgSpecialPages['CentralNoticeBanners'] = SpecialCentralNoticeBanners::class; |
99 | |
100 | $moduleTemplate = [ |
101 | 'localBasePath' => dirname( __DIR__ ) . '/resources', |
102 | 'remoteExtPath' => 'CentralNotice/resources', |
103 | ]; |
104 | $wgResourceModules += [ |
105 | 'ext.centralNotice.adminUi' => $moduleTemplate + [ |
106 | 'dependencies' => [ |
107 | 'jquery.ui', |
108 | 'mediawiki.jqueryMsg', |
109 | 'mediawiki.util', |
110 | 'mediawiki.Uri' |
111 | ], |
112 | 'scripts' => [ |
113 | 'vendor/jquery.ui.multiselect/ui.multiselect.js', |
114 | 'vendor/jquery.jstree/jstree.js', |
115 | 'infrastructure/centralnotice.js', |
116 | ], |
117 | 'styles' => [ |
118 | 'vendor/jquery.ui.multiselect/ui.multiselect.css', |
119 | 'vendor/jquery.jstree/themes/default/style.css', |
120 | 'infrastructure/ext.centralNotice.adminUi.css' |
121 | ], |
122 | 'messages' => [ |
123 | 'centralnotice-documentwrite-error', |
124 | 'centralnotice-close-title', |
125 | 'centralnotice-select-all', |
126 | 'centralnotice-remove-all', |
127 | 'centralnotice-items-selected', |
128 | 'centralnotice-geo-status' |
129 | ] |
130 | ], |
131 | 'ext.centralNotice.adminUi.campaignPager' => $moduleTemplate + [ |
132 | 'scripts' => 'infrastructure/ext.centralNotice.adminUi.campaignPager.js', |
133 | 'styles' => 'infrastructure/ext.centralNotice.adminUi.campaignPager.css' |
134 | ], |
135 | 'ext.centralNotice.adminUi.bannerManager' => $moduleTemplate + [ |
136 | 'dependencies' => [ |
137 | 'ext.centralNotice.adminUi', |
138 | 'jquery.ui', |
139 | 'mediawiki.Uri' |
140 | ], |
141 | 'scripts' => 'infrastructure/bannermanager.js', |
142 | 'styles' => 'infrastructure/bannermanager.css', |
143 | 'messages' => [ |
144 | 'centralnotice-add-notice-button', |
145 | 'centralnotice-add-notice-cancel-button', |
146 | 'centralnotice-archive-banner', |
147 | 'centralnotice-archive-banner-title', |
148 | 'centralnotice-archive-banner-confirm', |
149 | 'centralnotice-archive-banner-cancel', |
150 | 'centralnotice-add-new-banner-title', |
151 | 'centralnotice-delete-banner', |
152 | 'centralnotice-delete-banner-title', |
153 | 'centralnotice-delete-banner-confirm', |
154 | 'centralnotice-delete-banner-cancel' |
155 | ] |
156 | ], |
157 | 'ext.centralNotice.adminUi.bannerEditor' => $moduleTemplate + [ |
158 | 'dependencies' => [ |
159 | 'ext.centralNotice.adminUi', |
160 | 'jquery.ui', |
161 | 'ext.centralNotice.kvStore', |
162 | 'mediawiki.api', |
163 | 'mediawiki.Uri', |
164 | 'mediawiki.Title', |
165 | 'mediawiki.user', |
166 | ], |
167 | 'scripts' => 'infrastructure/bannereditor.js', |
168 | 'styles' => 'infrastructure/bannereditor.css', |
169 | 'messages' => [ |
170 | 'centralnotice-clone', |
171 | 'centralnotice-clone-notice', |
172 | 'centralnotice-clone-cancel', |
173 | 'centralnotice-archive-banner', |
174 | 'centralnotice-archive-banner-title', |
175 | 'centralnotice-archive-banner-confirm', |
176 | 'centralnotice-archive-banner-cancel', |
177 | 'centralnotice-delete-banner', |
178 | 'centralnotice-delete-banner-title', |
179 | 'centralnotice-delete-banner-confirm', |
180 | 'centralnotice-delete-banner-cancel', |
181 | 'centralnotice-banner-cdn-dialog-waiting-text', |
182 | 'centralnotice-banner-cdn-dialog-error', |
183 | 'centralnotice-banner-cdn-dialog-success', |
184 | 'centralnotice-fieldset-preview', |
185 | 'centralnotice-preview-page', |
186 | 'centralnotice-update-preview', |
187 | 'centralnotice-preview-loader-error-dialog-title', |
188 | ] |
189 | ], |
190 | 'ext.centralNotice.adminUi.campaignManager' => [ |
191 | 'localBasePath' => dirname( __DIR__ ), |
192 | 'remoteExtPath' => 'CentralNotice', |
193 | 'dependencies' => [ |
194 | 'ext.centralNotice.adminUi', |
195 | 'oojs-ui', |
196 | 'mediawiki.template', |
197 | 'mediawiki.template.mustache' |
198 | ], |
199 | 'scripts' => 'resources/infrastructure/campaignManager.js', |
200 | 'styles' => 'resources/infrastructure/campaignManager.css', |
201 | 'templates' => [ |
202 | 'campaignMixinParamControls.mustache' => 'templates/campaignMixinParamControls.mustache' |
203 | ], |
204 | 'messages' => [ |
205 | 'centralnotice-notice-mixins-int-required', |
206 | 'centralnotice-notice-mixins-float-required', |
207 | 'centralnotice-notice-mixins-out-of-bound', |
208 | 'centralnotice-banner-history-logger-rate', |
209 | 'centralnotice-banner-history-logger-rate-help', |
210 | 'centralnotice-banner-history-logger-max-entry-age', |
211 | 'centralnotice-banner-history-logger-max-entry-age-help', |
212 | 'centralnotice-banner-history-logger-max-entries', |
213 | 'centralnotice-banner-history-logger-max-entries-help', |
214 | 'centralnotice-banner-history-logger-wait-log-no-send-beacon', |
215 | 'centralnotice-banner-history-logger-wait-log-no-send-beacon-help', |
216 | 'centralnotice-set-record-impression-sample-rate', |
217 | 'centralnotice-custom-record-impression-sample-rate', |
218 | 'centralnotice-banners-not-guaranteed-to-display', |
219 | 'centralnotice-impression-diet-identifier', |
220 | 'centralnotice-impression-diet-identifier-help', |
221 | 'centralnotice-impression-diet-maximum-seen', |
222 | 'centralnotice-impression-diet-maximum-seen-help', |
223 | 'centralnotice-impression-diet-restart-cycle-delay', |
224 | 'centralnotice-impression-diet-restart-cycle-delay-help', |
225 | 'centralnotice-impression-diet-skip-initial', |
226 | 'centralnotice-impression-diet-skip-initial-help', |
227 | 'centralnotice-large-banner-limit-days', |
228 | 'centralnotice-large-banner-limit-days-help', |
229 | 'centralnotice-large-banner-limit-randomize', |
230 | 'centralnotice-large-banner-limit-randomize-help', |
231 | 'centralnotice-large-banner-limit-identifier', |
232 | 'centralnotice-large-banner-limit-identifier-help', |
233 | 'centralnotice-impression-events-sample-rate', |
234 | 'centralnotice-impression-events-sample-rate-help', |
235 | 'centralnotice-impression-events-sample-rate-field' |
236 | ] |
237 | ], |
238 | 'ext.centralNotice.adminUi.bannerSequence' => $moduleTemplate + [ |
239 | 'scripts' => 'infrastructure/ext.centralNotice.adminUi.bannerSequence.js', |
240 | 'styles' => 'infrastructure/ext.centralNotice.adminUi.bannerSequence.less', |
241 | 'dependencies' => [ |
242 | 'ext.centralNotice.adminUi.campaignManager', |
243 | 'oojs-ui', |
244 | 'oojs-ui.styles.icons-moderation', |
245 | 'mediawiki.jqueryMsg' |
246 | ], |
247 | 'messages' => [ |
248 | 'centralnotice-banner-sequence', |
249 | 'centralnotice-banner-sequence-days', |
250 | 'centralnotice-banner-sequence-days-error', |
251 | 'centralnotice-banner-sequence-days-help', |
252 | 'centralnotice-banner-sequence-help', |
253 | 'centralnotice-banner-sequence-bucket-seq', |
254 | 'centralnotice-banner-sequence-bucket-add-step', |
255 | 'centralnotice-banner-sequence-banner', |
256 | 'centralnotice-banner-sequence-page-views', |
257 | 'centralnotice-banner-sequence-skip-with-id', |
258 | 'centralnotice-banner-sequence-page-views-error', |
259 | 'centralnotice-banner-sequence-skip-with-id-error', |
260 | 'centralnotice-banner-sequence-banner-removed-error', |
261 | 'centralnotice-banner-sequence-no-banner', |
262 | 'centralnotice-banner-sequence-detailed-help' |
263 | ] |
264 | ], |
265 | ]; |
266 | |
267 | // Register user rights for editing |
268 | $wgAvailableRights[] = 'centralnotice-admin'; |
269 | |
270 | if ( $wgCentralNoticeAdminGroup ) { |
271 | // Grant admin permissions to this group |
272 | $wgGroupPermissions[$wgCentralNoticeAdminGroup]['centralnotice-admin'] = true; |
273 | } |
274 | |
275 | if ( !in_array( $wgCentralNoticeMessageProtectRight, $wgAvailableRights ) ) { |
276 | $wgAvailableRights[] = $wgCentralNoticeMessageProtectRight; |
277 | } |
278 | self::addCascadingRestrictionRight( $wgCentralNoticeMessageProtectRight ); |
279 | self::addCascadingRestrictionRight( 'centralnotice-admin' ); |
280 | } |
281 | } |
282 | |
283 | private static function addCascadingRestrictionRight( $right ) { |
284 | global $wgCascadingRestrictionLevels, $wgRestrictionLevels; |
285 | if ( !in_array( $right, $wgRestrictionLevels ) ) { |
286 | $wgRestrictionLevels[] = $right; |
287 | } |
288 | if ( !in_array( $right, $wgCascadingRestrictionLevels ) ) { |
289 | $wgCascadingRestrictionLevels[] = $right; |
290 | } |
291 | } |
292 | |
293 | /** |
294 | * Initialization: set default values for some config globals. Invoked via |
295 | * $wgExtensionFunctions. |
296 | */ |
297 | public static function initCentralNotice() { |
298 | global $wgCentralBannerRecorder, $wgCentralSelectedBannerDispatcher, |
299 | $wgCentralSelectedMobileBannerDispatcher; |
300 | |
301 | // Defaults for infrastructure wiki URLs |
302 | if ( !$wgCentralBannerRecorder ) { |
303 | $wgCentralBannerRecorder = |
304 | SpecialPage::getTitleFor( 'RecordImpression' )->getLocalUrl(); |
305 | } |
306 | |
307 | if ( !$wgCentralSelectedBannerDispatcher ) { |
308 | $wgCentralSelectedBannerDispatcher = |
309 | SpecialPage::getTitleFor( 'BannerLoader' )->getLocalUrl(); |
310 | } |
311 | |
312 | if ( !$wgCentralSelectedMobileBannerDispatcher && |
313 | ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) |
314 | ) { |
315 | $wgCentralSelectedMobileBannerDispatcher = $wgCentralSelectedBannerDispatcher; |
316 | } |
317 | } |
318 | |
319 | /** |
320 | * CanonicalNamespaces hook; adds the CentralNotice namespaces if this is an infrastructure |
321 | * wiki, and if CentralNotice is configured to use the Translate extension. |
322 | * |
323 | * We do this here because there are initialization problems wrt Translate and MW core if |
324 | * the language object is initialized before all namespaces are registered -- which would |
325 | * be the case if we just used the wgExtensionFunctions hook system. |
326 | * |
327 | * @param array &$namespaces Modifiable list of namespaces -- similar to $wgExtraNamespaces |
328 | */ |
329 | public function onCanonicalNamespaces( &$namespaces ) { |
330 | global $wgExtraNamespaces, $wgNamespacesWithSubpages, $wgTranslateMessageNamespaces; |
331 | global $wgNoticeUseTranslateExtension, $wgNoticeInfrastructure; |
332 | |
333 | // TODO XXX Old doc copied from legacy follows, verify accuracy! |
334 | // When using the group review feature of translate; this |
335 | // will be the namespace ID for the banner staging area -- ie: banners |
336 | // here are world editable and will not be moved to the MW namespace |
337 | // until they are in $wgNoticeTranslateDeployStates |
338 | |
339 | // TODO This may be unnecessary. Must coordinate with extension.json |
340 | if ( !defined( 'NS_CN_BANNER' ) ) { |
341 | define( 'NS_CN_BANNER', 866 ); |
342 | define( 'NS_CN_BANNER_TALK', 867 ); |
343 | } |
344 | |
345 | if ( $wgNoticeInfrastructure && $wgNoticeUseTranslateExtension ) { |
346 | $wgExtraNamespaces[NS_CN_BANNER] = 'CNBanner'; |
347 | $wgTranslateMessageNamespaces[] = NS_CN_BANNER; |
348 | |
349 | $wgExtraNamespaces[NS_CN_BANNER_TALK] = 'CNBanner_talk'; |
350 | $wgNamespacesWithSubpages[NS_CN_BANNER_TALK] = true; |
351 | |
352 | $namespaces[NS_CN_BANNER] = 'CNBanner'; |
353 | $namespaces[NS_CN_BANNER_TALK] = 'CNBanner_talk'; |
354 | } |
355 | } |
356 | |
357 | /** |
358 | * BeforePageDisplay hook handler |
359 | * This function adds the startUp and geoIP modules to the page as needed, |
360 | * and if there is a forced banner preview, add CSP headers and violation |
361 | * reporting javascript. |
362 | * |
363 | * @param OutputPage $out |
364 | * @param Skin $skin |
365 | * @return bool |
366 | */ |
367 | public static function onBeforePageDisplay( $out, $skin ) { |
368 | global $wgCentralHost, $wgServer, $wgCentralNoticeContentSecurityPolicy, |
369 | $wgCentralNoticeESITestString; |
370 | |
371 | // Add ESI test string (see T308799) |
372 | // It is expected that only HTML comments in the form of '<!--esi ...' will be |
373 | // injected here. |
374 | // TODO Remove this once ESI tests are complete. |
375 | if ( $wgCentralNoticeESITestString ) { |
376 | $out->addHTML( $wgCentralNoticeESITestString ); |
377 | } |
378 | |
379 | // Always add geoIP |
380 | // TODO Separate geoIP from CentralNotice |
381 | $out->addModules( 'ext.centralNotice.geoIP' ); |
382 | |
383 | $request = $skin->getRequest(); |
384 | // If we're on a special page, editing, viewing history or a diff, bow out now |
385 | // This is to reduce chance of bad misclicks from delayed banner loading |
386 | if ( $out->getTitle()->inNamespace( NS_SPECIAL ) || |
387 | ( $request->getText( 'action' ) === 'edit' ) || |
388 | ( $request->getText( 'action' ) === 'history' ) || |
389 | $request->getCheck( 'diff' ) |
390 | ) { |
391 | return true; |
392 | } |
393 | |
394 | // Insert DNS prefetch for banner loading |
395 | if ( $wgCentralHost && $wgCentralHost !== $wgServer ) { |
396 | $out->addHeadItem( |
397 | 'cn-dns-prefetch', |
398 | '<link rel="dns-prefetch" href="' . htmlspecialchars( $wgCentralHost ) . '" />' |
399 | ); |
400 | } |
401 | |
402 | // Insert the startup module |
403 | $out->addModules( 'ext.centralNotice.startUp' ); |
404 | |
405 | // FIXME: as soon as I80f6f469ba4c0b60 is available in core, get rid |
406 | // of $wgCentralNoticeContentSecurityPolicy and use their stuff. |
407 | if ( |
408 | $wgCentralNoticeContentSecurityPolicy && |
409 | $request->getVal( 'banner' ) |
410 | ) { |
411 | $request->response()->header( |
412 | "content-security-policy: $wgCentralNoticeContentSecurityPolicy" |
413 | ); |
414 | $out->addModules( 'ext.centralNotice.cspViolationAlert' ); |
415 | } |
416 | return true; |
417 | } |
418 | |
419 | /** |
420 | * MakeGlobalVariablesScript hook handler |
421 | * This function sets the pseudo-global JavaScript variables that are used by CentralNotice |
422 | * |
423 | * @param array &$vars |
424 | * @param OutputPage $out |
425 | * @return bool |
426 | */ |
427 | public static function onMakeGlobalVariablesScript( &$vars, $out ) { |
428 | global $wgNoticeProject, $wgCentralNoticeGeoIPBackgroundLookupModule; |
429 | |
430 | // FIXME Is this no longer used anywhere in JS following the switch to |
431 | // client-side banner selection? If so, remove it. |
432 | $vars[ 'wgNoticeProject' ] = $wgNoticeProject; |
433 | |
434 | // No need to provide this variable if it's null, because mw.config.get() |
435 | // will return null if it's not there. |
436 | if ( $wgCentralNoticeGeoIPBackgroundLookupModule ) { |
437 | $vars[ 'wgCentralNoticeGeoIPBackgroundLookupModule' ] = |
438 | $wgCentralNoticeGeoIPBackgroundLookupModule; |
439 | } |
440 | |
441 | // Output the user's registration date, total edit count, and past year's edit count. |
442 | // This is useful for banners that need to be targeted to specific types of users. |
443 | // Only do this for logged-in users, keeping anonymous user output equal (for Squid-cache). |
444 | $user = $out->getUser(); |
445 | if ( $user->isRegistered() ) { |
446 | if ( $user->isBot() ) { |
447 | $userData = false; |
448 | } else { |
449 | $userData = [ |
450 | // Add the user's registration date (TS_MW) |
451 | 'registration' => $user->getRegistration() ?: 0 |
452 | ]; |
453 | } |
454 | // Set the variable that will be output to the page |
455 | $vars[ 'wgNoticeUserData' ] = $userData; |
456 | } |
457 | |
458 | return true; |
459 | } |
460 | |
461 | /** |
462 | * SiteNoticeAfter hook handler |
463 | * This function outputs the siteNotice div that the banners are loaded into. |
464 | * |
465 | * @param string &$notice |
466 | * @return bool |
467 | */ |
468 | public static function onSiteNoticeAfter( &$notice ) { |
469 | // TODO Legacy comment below, likely inaccurate; check and fix |
470 | // Ensure that the div including #siteNotice is actually included |
471 | $notice = "<!-- CentralNotice -->$notice"; |
472 | |
473 | return true; |
474 | } |
475 | |
476 | /** |
477 | * ResourceLoaderGetConfigVars hook handler |
478 | * Send php config vars to js via ResourceLoader |
479 | * |
480 | * @param array &$vars variables to be added to the output of the startup module |
481 | * @return bool |
482 | */ |
483 | public static function onResourceLoaderGetConfigVars( &$vars ) { |
484 | global $wgNoticeInfrastructure, $wgCentralBannerRecorder, |
485 | $wgNoticeNumberOfBuckets, $wgNoticeBucketExpiry, |
486 | $wgNoticeNumberOfControllerBuckets, $wgNoticeCookieDurations, |
487 | $wgNoticeHideUrls, $wgCentralNoticeSampleRate, |
488 | $wgCentralNoticeImpressionEventSampleRate, |
489 | $wgCentralSelectedBannerDispatcher, $wgCentralSelectedMobileBannerDispatcher, |
490 | $wgCentralNoticePerCampaignBucketExtension, $wgCentralNoticeCampaignMixins, |
491 | $wgCentralNoticeMaxCampaignFallback; |
492 | |
493 | // TODO Check if the following comment still applies |
494 | // Making these calls too soon will causes issues with the namespace localisation cache. |
495 | // This seems to be just right. We require them at all because MW will 302 page requests |
496 | // made to non localised namespaces which results in wasteful extra calls. |
497 | |
498 | // Set infrastructure URL variables, which change between mobile/desktop |
499 | if ( class_exists( MobileContext::class ) ) { |
500 | $mc = MobileContext::singleton(); |
501 | $displayMobile = $mc->shouldDisplayMobileView(); |
502 | } else { |
503 | $displayMobile = false; |
504 | } |
505 | |
506 | if ( $displayMobile ) { |
507 | // @phan-suppress-next-line PhanPossiblyUndeclaredVariable |
508 | $wgCentralBannerRecorder = $mc->getMobileUrl( $wgCentralBannerRecorder ); |
509 | $bannerDispatcher = $wgCentralSelectedMobileBannerDispatcher; |
510 | } else { |
511 | $bannerDispatcher = $wgCentralSelectedBannerDispatcher; |
512 | } |
513 | |
514 | $vars[ 'wgCentralNoticeActiveBannerDispatcher' ] = $bannerDispatcher; |
515 | // TODO Temporary setting to support cached javascript following deploy; remove. |
516 | $vars[ 'wgCentralSelectedBannerDispatcher' ] = $bannerDispatcher; |
517 | $vars[ 'wgCentralBannerRecorder' ] = $wgCentralBannerRecorder; |
518 | $vars[ 'wgCentralNoticeSampleRate' ] = $wgCentralNoticeSampleRate; |
519 | |
520 | $vars[ 'wgCentralNoticeImpressionEventSampleRate' ] = |
521 | $wgCentralNoticeImpressionEventSampleRate; |
522 | |
523 | $vars[ 'wgNoticeNumberOfBuckets' ] = $wgNoticeNumberOfBuckets; |
524 | $vars[ 'wgNoticeBucketExpiry' ] = $wgNoticeBucketExpiry; |
525 | $vars[ 'wgNoticeNumberOfControllerBuckets' ] = $wgNoticeNumberOfControllerBuckets; |
526 | $vars[ 'wgNoticeCookieDurations' ] = $wgNoticeCookieDurations; |
527 | $vars[ 'wgNoticeHideUrls' ] = $wgNoticeHideUrls; |
528 | $vars[ 'wgCentralNoticeMaxCampaignFallback' ] = $wgCentralNoticeMaxCampaignFallback; |
529 | |
530 | $vars[ 'wgCentralNoticePerCampaignBucketExtension' ] = |
531 | $wgCentralNoticePerCampaignBucketExtension; |
532 | |
533 | if ( $wgNoticeInfrastructure ) { |
534 | // Add campaign mixin defs for use in admin interface |
535 | $vars[ 'wgCentralNoticeCampaignMixins' ] = $wgCentralNoticeCampaignMixins; |
536 | } |
537 | return true; |
538 | } |
539 | |
540 | /** |
541 | * Conditionally register resource loader modules. |
542 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules |
543 | * |
544 | * @param ResourceLoader $resourceLoader |
545 | */ |
546 | public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void { |
547 | global $wgEnableJavaScriptTest, $wgAutoloadClasses; |
548 | |
549 | if ( $wgEnableJavaScriptTest ) { |
550 | // These classes are only used here or in phpunit tests |
551 | $wgAutoloadClasses['CNTestFixturesResourceLoaderModule'] = |
552 | dirname( __DIR__ ) . '/tests/phpunit/CNTestFixturesResourceLoaderModule.php'; |
553 | $wgAutoloadClasses['CentralNoticeTestFixtures'] = |
554 | dirname( __DIR__ ) . '/tests/phpunit/CentralNoticeTestFixtures.php'; |
555 | |
556 | // Set up test fixtures module, which is added as a dependency for all QUnit |
557 | // tests. |
558 | $resourceLoader->register( 'ext.centralNotice.testFixtures', [ |
559 | 'class' => 'CNTestFixturesResourceLoaderModule' |
560 | ] ); |
561 | } |
562 | } |
563 | |
564 | /** |
565 | * Add tags defined by this extension to list of active tags. |
566 | * |
567 | * @param array &$tags List of defined or active tags |
568 | */ |
569 | public function onChangeTagsListActive( &$tags ) { |
570 | $this->addDefinedTags( $tags ); |
571 | } |
572 | |
573 | /** |
574 | * Add tags defined by this extension to list of defined tags. |
575 | * |
576 | * @param array &$tags List of defined or active tags |
577 | */ |
578 | public function onListDefinedTags( &$tags ) { |
579 | $this->addDefinedTags( $tags ); |
580 | } |
581 | |
582 | private function addDefinedTags( &$tags ): void { |
583 | $tags[] = 'centralnotice'; |
584 | $tags[] = 'centralnotice translation'; |
585 | } |
586 | |
587 | /** |
588 | * @param User $user |
589 | * @param array &$preferences |
590 | */ |
591 | public function onGetPreferences( $user, &$preferences ) { |
592 | // Explanatory text |
593 | $preferences['centralnotice-intro'] = [ |
594 | 'type' => 'info', |
595 | 'default' => wfMessage( 'centralnotice-user-prefs-intro' )->parseAsBlock(), |
596 | 'section' => 'centralnotice-banners', |
597 | 'raw' => true, |
598 | ]; |
599 | |
600 | foreach ( CampaignType::getTypes() as $type ) { |
601 | // This allows fallback languages while also showing something not-too- |
602 | // horrible if the config variable has types that don't have i18n |
603 | // messages. |
604 | // Note also that the value of 'label' will escaped prior to output. |
605 | $message = Message::newFromKey( $type->getMessageKey() ); |
606 | $label = $message->exists() ? $message->text() : $type->getId(); |
607 | |
608 | $preferences[ $type->getPreferenceKey() ] = [ |
609 | 'type' => 'toggle', |
610 | 'section' => 'centralnotice-banners/centralnotice-display-banner-types', |
611 | 'label' => $label, |
612 | 'disabled' => $type->getOnForAll() |
613 | ]; |
614 | } |
615 | } |
616 | |
617 | /** |
618 | * Add icon for Special:Preferences mobile layout |
619 | * |
620 | * @param array &$iconNames Array of icon names for their respective sections. |
621 | */ |
622 | public function onPreferencesGetIcon( &$iconNames ) { |
623 | $iconNames[ 'centralnotice-banners' ] = 'feedback'; |
624 | } |
625 | |
626 | /** |
627 | * Adds CentralNotice specific navigation tabs to the UI. |
628 | * Implementation of SkinTemplateNavigation::Universal hook. |
629 | * |
630 | * @param Skin $skin Reference to the Skin object |
631 | * @param array &$tabs Any current skin tabs |
632 | */ |
633 | public function onSkinTemplateNavigation__Universal( $skin, &$tabs ): void { |
634 | global $wgNoticeTabifyPages, $wgNoticeInfrastructure; |
635 | |
636 | // Only show tabs if this wiki is in infrastructure mode |
637 | if ( !$wgNoticeInfrastructure ) { |
638 | return; |
639 | } |
640 | |
641 | // Return if skin allows special pages to register natigation links (in which |
642 | // case this is handled by getShortDescription() and |
643 | // getAssociatedNavigationLinks()). |
644 | // See T315562, T313349. |
645 | if ( $skin->supportsMenu( 'associated-pages' ) ) { |
646 | return; |
647 | } |
648 | |
649 | $title = $skin->getTitle(); |
650 | |
651 | // Only add tabs to special pages |
652 | if ( !$title->isSpecialPage() ) { |
653 | return; |
654 | } |
655 | |
656 | [ $alias, $sub ] = MediaWikiServices::getInstance()->getSpecialPageFactory()-> |
657 | resolveAlias( $title->getText() ); |
658 | |
659 | if ( !array_key_exists( $alias, $wgNoticeTabifyPages ) ) { |
660 | return; |
661 | } |
662 | |
663 | // Clear the special page tab that's there already |
664 | $tabs['namespaces'] = []; |
665 | |
666 | // Now add our own |
667 | foreach ( $wgNoticeTabifyPages as $page => $keys ) { |
668 | $tabs[ $keys[ 'type' ] ][ $page ] = [ |
669 | 'text' => wfMessage( $keys[ 'message' ] )->parse(), |
670 | 'href' => SpecialPage::getTitleFor( $page )->getFullURL(), |
671 | 'class' => ( $alias === $page ) ? 'selected' : '', |
672 | ]; |
673 | } |
674 | } |
675 | } |