www.fgks.org   »   [go: up one dir, main page]

blob: 5c9b8684effe79bc6462bb572d6e95c23f38760e [file] [log] [blame]
<?php
/**
* General hook definitions
*
* This file is part of the CentralNotice Extension to MediaWiki
* https://www.mediawiki.org/wiki/Extension:CentralNotice
*
* @file
* @ingroup Extensions
*
* @section LICENSE
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
class CentralNoticeHooks {
/**
* Conditional configuration
*/
public static function onRegistration() {
global $wgHooks, $wgNoticeInfrastructure, $wgSpecialPages,
$wgCentralNoticeLoader, $wgNoticeUseTranslateExtension,
$wgAvailableRights, $wgGroupPermissions, $wgCentralDBname, $wgDBname;
// Default for a standalone wiki is that the CN tables are in the main database.
if ( !$wgCentralDBname ) {
$wgCentralDBname = $wgDBname;
}
// If CentralNotice banners should be shown on this wiki, load the components we need for
// showing banners. For discussion of banner loading strategies, see
// http://wikitech.wikimedia.org/view/CentralNotice/Optimizing_banner_loading
if ( $wgCentralNoticeLoader ) {
$wgHooks['MakeGlobalVariablesScript'][] =
'CentralNoticeHooks::onMakeGlobalVariablesScript';
$wgHooks['BeforePageDisplay'][] = 'CentralNoticeHooks::onBeforePageDisplay';
$wgHooks['SiteNoticeAfter'][] = 'CentralNoticeHooks::onSiteNoticeAfter';
$wgHooks['ResourceLoaderGetConfigVars'][] =
'CentralNoticeHooks::onResourceLoaderGetConfigVars';
}
// If this is the wiki that hosts the management interface, load further components
if ( $wgNoticeInfrastructure ) {
if ( $wgNoticeUseTranslateExtension ) {
$wgHooks['TranslatePostInitGroups'][] = 'BannerMessageGroup::registerGroupHook';
$wgHooks['TranslateEventMessageGroupStateChange'][] =
'BannerMessageGroup::updateBannerGroupStateHook';
}
$wgSpecialPages['CentralNotice'] = 'CentralNotice';
$wgSpecialPages['NoticeTemplate'] = 'SpecialNoticeTemplate';
$wgSpecialPages['BannerAllocation'] = 'SpecialBannerAllocation';
$wgSpecialPages['CentralNoticeLogs'] = 'SpecialCentralNoticeLogs';
$wgSpecialPages['CentralNoticeBanners'] = 'SpecialCentralNoticeBanners';
// Register user rights for editing
$wgAvailableRights[] = 'centralnotice-admin';
// Only sysops can make change
$wgGroupPermissions['sysop']['centralnotice-admin'] = true;
}
}
/**
* Initialization: set default values for some config globals. Invoked via
* $wgExtensionFunctions.
*/
public static function initCentralNotice() {
global $wgCentralBannerRecorder, $wgCentralSelectedBannerDispatcher,
$wgCentralSelectedMobileBannerDispatcher;
// Defaults for infrastructure wiki URLs
if ( !$wgCentralBannerRecorder ) {
$wgCentralBannerRecorder =
SpecialPage::getTitleFor( 'RecordImpression' )->getLocalUrl();
}
if ( !$wgCentralSelectedBannerDispatcher ) {
$wgCentralSelectedBannerDispatcher =
SpecialPage::getTitleFor( 'BannerLoader' )->getLocalUrl();
}
if ( !$wgCentralSelectedMobileBannerDispatcher && class_exists( 'MobileContext' ) ) {
$wgCentralSelectedMobileBannerDispatcher = $wgCentralSelectedBannerDispatcher;
}
}
/**
* Tell the UserMerge extension where we store user ids
* @param array[] &$updateFields
* @return true
*/
public static function onUserMergeAccountFields( &$updateFields ) {
global $wgNoticeInfrastructure;
if ( $wgNoticeInfrastructure ) {
// array( tableName, idField, textField )
$updateFields[] = [ 'cn_notice_log', 'notlog_user_id' ];
$updateFields[] = [ 'cn_template_log', 'tmplog_user_id' ];
}
return true;
}
/**
* CanonicalNamespaces hook; adds the CentralNotice namespaces if this is an infrastructure
* wiki, and if CentralNotice is configured to use the Translate extension.
*
* We do this here because there are initialization problems wrt Translate and MW core if
* the language object is initialized before all namespaces are registered -- which would
* be the case if we just used the wgExtensionFunctions hook system.
*
* @param array &$namespaces Modifiable list of namespaces -- similar to $wgExtraNamespaces
*
* @return bool True if the hook completed successfully.
*/
public static function onCanonicalNamespaces( &$namespaces ) {
global $wgExtraNamespaces, $wgNamespacesWithSubpages, $wgTranslateMessageNamespaces;
global $wgNoticeUseTranslateExtension, $wgNoticeInfrastructure;
// TODO XXX Old doc copied from legacy follows, verify accuracy!
// When using the group review feature of translate; this
// will be the namespace ID for the banner staging area -- ie: banners
// here are world editable and will not be moved to the MW namespace
// until they are in $wgNoticeTranslateDeployStates
// TODO This may be unnecessary. Must coordinate with extension.json
if ( !defined( 'NS_CN_BANNER' ) ) {
define( 'NS_CN_BANNER', 866 );
define( 'NS_CN_BANNER_TALK', 867 );
}
if ( $wgNoticeInfrastructure && $wgNoticeUseTranslateExtension ) {
$wgExtraNamespaces[NS_CN_BANNER] = 'CNBanner';
$wgTranslateMessageNamespaces[] = NS_CN_BANNER;
$wgExtraNamespaces[NS_CN_BANNER_TALK] = 'CNBanner_talk';
$wgNamespacesWithSubpages[NS_CN_BANNER_TALK] = true;
$namespaces[NS_CN_BANNER] = 'CNBanner';
$namespaces[NS_CN_BANNER_TALK] = 'CNBanner_talk';
}
return true;
}
/**
* BeforePageDisplay hook handler
* This function adds the startUp and geoIP modules to the page as needed,
* and if there is a forced banner preview, add CSP headers and violation
* reporting javascript.
*
* @param OutputPage $out
* @param Skin $skin
* @return bool
*/
public static function onBeforePageDisplay( $out, $skin ) {
global $wgCentralHost, $wgServer, $wgRequest, $wgCentralNoticeContentSecurityPolicy;
// Always add geoIP
// TODO Separate geoIP from CentralNotice
$out->addModules( 'ext.centralNotice.geoIP' );
// If we're on a special page, editing, viewing history or a diff, bow out now
// This is to reduce chance of bad misclicks from delayed banner loading
if ( $out->getTitle()->inNamespace( NS_SPECIAL ) ||
( $wgRequest->getText( 'action' ) === 'edit' ) ||
( $wgRequest->getText( 'action' ) === 'history' ) ||
$wgRequest->getCheck( 'diff' )
) {
return true;
}
// Insert DNS prefetch for banner loading
if ( $wgCentralHost && $wgCentralHost !== $wgServer ) {
$out->addHeadItem(
'cn-dns-prefetch',
'<link rel="dns-prefetch" href="' . htmlspecialchars( $wgCentralHost ) . '" />'
);
}
// Insert the startup module
$out->addModules( 'ext.centralNotice.startUp' );
// FIXME: as soon as I80f6f469ba4c0b60 is available in core, get rid
// of $wgCentralNoticeContentSecurityPolicy and use their stuff.
if (
$wgCentralNoticeContentSecurityPolicy &&
$wgRequest->getVal( 'banner' )
) {
$wgRequest->response()->header(
"content-security-policy: $wgCentralNoticeContentSecurityPolicy"
);
$out->addModules( 'ext.centralNotice.cspViolationAlert' );
}
return true;
}
/**
* MakeGlobalVariablesScript hook handler
* This function sets the pseudo-global JavaScript variables that are used by CentralNotice
*
* @param array &$vars
* @return bool
*/
public static function onMakeGlobalVariablesScript( &$vars ) {
// Using global $wgUser for compatibility with 1.18
global $wgNoticeProject, $wgCentralNoticeCookiesToDelete,
$wgCentralNoticeCategoriesUsingLegacy,
$wgCentralNoticeGeoIPBackgroundLookupModule,
$wgUser, $wgMemc;
// FIXME Is this no longer used anywhere in JS following the switch to
// client-side banner selection? If so, remove it.
$vars[ 'wgNoticeProject' ] = $wgNoticeProject;
$vars[ 'wgCentralNoticeCookiesToDelete' ] = $wgCentralNoticeCookiesToDelete;
$vars[ 'wgCentralNoticeCategoriesUsingLegacy' ] =
$wgCentralNoticeCategoriesUsingLegacy;
// No need to provide this variable if it's null, because mw.config.get()
// will return null if it's not there.
if ( $wgCentralNoticeGeoIPBackgroundLookupModule ) {
$vars[ 'wgCentralNoticeGeoIPBackgroundLookupModule' ] =
$wgCentralNoticeGeoIPBackgroundLookupModule;
}
// Output the user's registration date, total edit count, and past year's edit count.
// This is useful for banners that need to be targeted to specific types of users.
// Only do this for logged-in users, keeping anonymous user output equal (for Squid-cache).
if ( $wgUser->isLoggedIn() ) {
$cacheKey = wfMemcKey( 'CentralNotice', 'UserData', $wgUser->getId() );
$userData = $wgMemc->get( $cacheKey );
// Cached?
if ( !$userData ) {
// Exclude bots
if ( $wgUser->isAllowed( 'bot' ) ) {
$userData = false;
} else {
$userData = [];
// Add the user's registration date (MediaWiki timestamp)
$registrationDate = $wgUser->getRegistration() ? $wgUser->getRegistration() : 0;
$userData[ 'registration' ] = $registrationDate;
}
// Cache the data for 7 days
$wgMemc->set( $cacheKey, $userData, 7 * 86400 );
}
// Set the variable that will be output to the page
$vars[ 'wgNoticeUserData' ] = $userData;
}
return true;
}
/**
* SiteNoticeAfter hook handler
* This function outputs the siteNotice div that the banners are loaded into.
*
* @param string &$notice
* @return bool
*/
public static function onSiteNoticeAfter( &$notice ) {
// Ensure that the div including #siteNotice is actually included!
$notice = "<!-- CentralNotice -->$notice";
return true;
}
/**
* ResourceLoaderGetConfigVars hook handler
* Send php config vars to js via ResourceLoader
*
* @param array &$vars variables to be added to the output of the startup module
* @return bool
*/
public static function onResourceLoaderGetConfigVars( &$vars ) {
global $wgNoticeXXCountries,
$wgNoticeInfrastructure, $wgCentralBannerRecorder,
$wgNoticeNumberOfBuckets, $wgNoticeBucketExpiry,
$wgNoticeNumberOfControllerBuckets, $wgNoticeCookieDurations,
$wgNoticeHideUrls, $wgCentralNoticeSampleRate,
$wgCentralNoticeImpressionEventSampleRate,
$wgCentralSelectedBannerDispatcher, $wgCentralSelectedMobileBannerDispatcher,
$wgCentralNoticePerCampaignBucketExtension, $wgCentralNoticeCampaignMixins;
// TODO Check if the following comment still applies
// Making these calls too soon will causes issues with the namespace localisation cache.
// This seems to be just right. We require them at all because MW will 302 page requests
// made to non localised namespaces which results in wasteful extra calls.
// Set infrastructure URL variables, which change between mobile/desktop
if ( class_exists( 'MobileContext' ) ) {
$mc = MobileContext::singleton();
$displayMobile = $mc->shouldDisplayMobileView();
} else {
$displayMobile = false;
}
if ( $displayMobile ) {
$wgCentralBannerRecorder = $mc->getMobileUrl( $wgCentralBannerRecorder );
$bannerDispatcher = $wgCentralSelectedMobileBannerDispatcher;
} else {
$bannerDispatcher = $wgCentralSelectedBannerDispatcher;
}
$vars[ 'wgCentralNoticeActiveBannerDispatcher' ] = $bannerDispatcher;
// TODO Temporary setting to support cached javascript following deploy; remove.
$vars[ 'wgCentralSelectedBannerDispatcher' ] = $bannerDispatcher;
$vars[ 'wgCentralBannerRecorder' ] = $wgCentralBannerRecorder;
$vars[ 'wgCentralNoticeSampleRate' ] = $wgCentralNoticeSampleRate;
$vars[ 'wgCentralNoticeImpressionEventSampleRate' ] =
$wgCentralNoticeImpressionEventSampleRate;
// TODO Remove, no longer used
$vars[ 'wgNoticeXXCountries' ] = $wgNoticeXXCountries;
$vars[ 'wgNoticeNumberOfBuckets' ] = $wgNoticeNumberOfBuckets;
$vars[ 'wgNoticeBucketExpiry' ] = $wgNoticeBucketExpiry;
$vars[ 'wgNoticeNumberOfControllerBuckets' ] = $wgNoticeNumberOfControllerBuckets;
$vars[ 'wgNoticeCookieDurations' ] = $wgNoticeCookieDurations;
$vars[ 'wgNoticeHideUrls' ] = $wgNoticeHideUrls;
$vars[ 'wgCentralNoticePerCampaignBucketExtension' ] =
$wgCentralNoticePerCampaignBucketExtension;
if ( $wgNoticeInfrastructure ) {
// Add campaign mixin defs for use in admin interface
$vars[ 'wgCentralNoticeCampaignMixins' ] = $wgCentralNoticeCampaignMixins;
}
return true;
}
/**
* ResourceLoaderTestModules hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderTestModules
*
* @param array &$testModules
* @param ResourceLoader $resourceLoader
* @return bool
*/
public static function onResourceLoaderTestModules( array &$testModules,
ResourceLoader $resourceLoader
) {
global $wgResourceModules, $wgAutoloadClasses;
// Set up test fixtures module, which is added as a dependency for all QUnit
// tests.
$testModules['qunit']['ext.centralNotice.testFixtures'] = [
'class' => 'CNTestFixturesResourceLoaderModule'
];
// These classes are only used here or in phpunit tests
$wgAutoloadClasses['CNTestFixturesResourceLoaderModule'] =
__DIR__ . '/tests/phpunit/CNTestFixturesResourceLoaderModule.php';
// Note: the following setting is repeated in efCentralNoticeUnitTests()
$wgAutoloadClasses['CentralNoticeTestFixtures'] =
__DIR__ . '/tests/phpunit/CentralNoticeTestFixtures.php';
$testModuleBoilerplate = [
'localBasePath' => __DIR__,
'remoteExtPath' => 'CentralNotice',
];
// find test files for every RL module
$prefix = 'ext.centralNotice';
foreach ( $wgResourceModules as $key => $module ) {
if ( substr( $key, 0, strlen( $prefix ) ) ===
$prefix && isset( $module['scripts'] )
) {
$testFiles = [];
foreach ( ( (array)$module['scripts'] ) as $script ) {
$testFile = 'tests/qunit/' . $script;
$testFile = preg_replace( '/.js$/', '.tests.js', $testFile );
// if a test file exists for a given JS file, add it
if ( file_exists( __DIR__ . '/' . $testFile ) ) {
$testFiles[] = $testFile;
}
}
// if test files exist for given module, create a corresponding test
// module
if ( count( $testFiles ) > 0 ) {
$testModules['qunit']["$key.tests"] = $testModuleBoilerplate +
[
'dependencies' =>
[ $key, 'ext.centralNotice.testFixtures' ],
'scripts' => $testFiles,
];
}
}
}
return true;
}
}