Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 10 |
CRAP | |
18.92% |
21 / 111 |
Hooks | |
0.00% |
0 / 1 |
|
0.00% |
0 / 10 |
892.86 | |
18.92% |
21 / 111 |
onBeforePageDisplay | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
onLoadUserOptions | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 21 |
|||
onSaveUserOptions | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
onPreferencesFormPreSave | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
localPreferencesFormPreSave | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 13 |
|||
onLoadExtensionSchemaUpdates | |
0.00% |
0 / 1 |
90 | |
0.00% |
0 / 20 |
|||
onMediaWikiServices | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 17 |
|||
onDeleteUnknownPreferences | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
onApiOptions | |
0.00% |
0 / 1 |
8 | |
95.45% |
21 / 22 |
|||
getPreferencesFactory | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
<?php | |
namespace GlobalPreferences; | |
use ApiOptions; | |
use DatabaseUpdater; | |
use HTMLForm; | |
use MediaWiki\Config\ServiceOptions; | |
use MediaWiki\Logger\LoggerFactory; | |
use MediaWiki\MediaWikiServices; | |
use MediaWiki\User\UserIdentity; | |
use Message; | |
use OutputPage; | |
use Skin; | |
use User; | |
use Wikimedia\Rdbms\IDatabase; | |
class Hooks { | |
/** | |
* Allows last minute changes to the output page, e.g. adding of CSS or JavaScript by extensions. | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay | |
* @param OutputPage &$out The output page. | |
* @param Skin &$skin The skin. Not used. | |
*/ | |
public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ): void { | |
if ( $out->getTitle()->isSpecial( 'Preferences' ) ) { | |
// Add module styles and scripts separately | |
// so non-JS users get the styles quicker and to avoid a FOUC. | |
$out->addModuleStyles( 'ext.GlobalPreferences.local-nojs' ); | |
$out->addModules( 'ext.GlobalPreferences.local' ); | |
} | |
} | |
/** | |
* Load global preferences. | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/LoadUserOptions | |
* @param UserIdentity $user The user for whom options are being loaded. | |
* @param array &$options The user's options; can be modified. | |
*/ | |
public static function onLoadUserOptions( UserIdentity $user, array &$options ) { | |
$globalPreferences = self::getPreferencesFactory(); | |
if ( !$globalPreferences->isUserGlobalized( $user ) ) { | |
// Not a global user. | |
return; | |
} | |
$logger = LoggerFactory::getInstance( 'preferences' ); | |
$logger->debug( | |
'Loading global options for user \'{user}\'', | |
[ 'user' => $user->getName() ] | |
); | |
// Overwrite all options that have a global counterpart. | |
$globalPrefs = $globalPreferences->getGlobalPreferencesValues( $user ); | |
if ( $globalPrefs === false ) { | |
return; | |
} | |
foreach ( $globalPrefs as $optName => $globalValue ) { | |
// Don't overwrite if it has a local exception. | |
$localExceptionName = $optName . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; | |
if ( isset( $options[ $localExceptionName ] ) && $options[ $localExceptionName ] ) { | |
continue; | |
} | |
// FIXME: temporary plug for T201340: DB might have rows for deglobalized | |
// Echo notifications. Don't allow these through if the main checkbox is not checked. | |
if ( !( $globalPrefs['echo-subscriptions'] ?? false ) | |
&& strpos( $optName, 'echo-subscriptions-' ) === 0 | |
) { | |
continue; | |
} | |
// Convert '0' to 0. PHP's boolean conversion considers them both false, | |
// but e.g. JavaScript considers the former as true. | |
if ( $globalValue === '0' ) { | |
$globalValue = 0; | |
} | |
$options[ $optName ] = $globalValue; | |
} | |
} | |
/** | |
* When saving a user's options, remove any global ones and never save any on the Global | |
* Preferences page. Global options are saved separately, in the PreferencesFormPreSave hook. | |
* | |
* @see https://www.mediawiki.org/wiki/Manual:Hooks/SaveUserOptions | |
* @param UserIdentity $user The user. | |
* @param string[] &$modifiedOptions The user's options that were modified. | |
* @param string[] $originalOptions The original options. | |
* @return bool False if nothing changed, true otherwise. | |
*/ | |
public static function onSaveUserOptions( UserIdentity $user, array &$modifiedOptions, array $originalOptions ) { | |
$preferencesFactory = self::getPreferencesFactory(); | |
if ( $preferencesFactory->onGlobalPrefsPage() ) { | |
// It shouldn't be possible to save local options here, | |
// but never save on this page anyways. | |
return false; | |
} | |
$preferencesFactory->handleLocalPreferencesChange( $user, $modifiedOptions, $originalOptions ); | |
return true; | |
} | |
/** | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/PreferencesFormPreSave | |
* @param array $formData An associative array containing the data from the preferences form. | |
* @param HTMLForm $form The HTMLForm object that represents the preferences form. | |
* @param User $user The User object that can be used to change the user's preferences. | |
* @param array &$result The boolean return value of the Preferences::tryFormSubmit method. | |
* @return bool | |
*/ | |
public static function onPreferencesFormPreSave( | |
array $formData, | |
HTMLForm $form, | |
User $user, | |
&$result | |
): bool { | |
$preferencesFactory = self::getPreferencesFactory(); | |
if ( !$preferencesFactory->onGlobalPrefsPage( $form ) ) { | |
return self::localPreferencesFormPreSave( $formData, $user ); | |
} | |
return true; | |
} | |
/** | |
* Process PreferencesFormPreSave for Special:Preferences | |
* Handles CheckMatrix | |
* | |
* @param array $formData Associative array of [ preference name => value ] | |
* @param User $user Current user | |
* @return bool Hook return value | |
*/ | |
private static function localPreferencesFormPreSave( array $formData, User $user ): bool { | |
$userOptionsManager = MediaWikiServices::getInstance()->getUserOptionsManager(); | |
foreach ( $formData as $pref => $value ) { | |
if ( !GlobalPreferencesFactory::isLocalPrefName( $pref ) ) { | |
continue; | |
} | |
// Determine the real name of the preference. | |
$suffixLen = strlen( GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX ); | |
$realName = substr( $pref, 0, -$suffixLen ); | |
if ( isset( $formData[$realName] ) ) { | |
// Not a CheckMatrix field | |
continue; | |
} | |
$checkMatrix = preg_grep( "/^$realName-/", array_keys( $formData ) ); | |
foreach ( $checkMatrix as $check ) { | |
$localExceptionName = $check . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; | |
$userOptionsManager->setOption( $user, $localExceptionName, $value ); | |
} | |
} | |
return true; | |
} | |
/** | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/LoadExtensionSchemaUpdates | |
* @param DatabaseUpdater $updater The database updater. | |
*/ | |
public static function onLoadExtensionSchemaUpdates( DatabaseUpdater $updater ): void { | |
$config = MediaWikiServices::getInstance()->getMainConfig(); | |
$dBname = $config->get( 'DBname' ); | |
$sharedDB = $config->get( 'SharedDB' ); | |
// During install, extension registry config is not loaded - T198330 | |
$globalPreferencesDB = $config->has( 'GlobalPreferencesDB' ) | |
? $config->get( 'GlobalPreferencesDB' ) | |
: null; | |
// Only add the global_preferences table to the $wgGlobalPreferencesDB or the $wgSharedDB, | |
// unless neither of them is set. See also \GlobalPreferences\Storage::getDatabase(). | |
if ( ( $globalPreferencesDB === null && $sharedDB === null ) | |
|| $dBname === $globalPreferencesDB | |
|| ( $globalPreferencesDB === null && $dBname === $sharedDB ) | |
) { | |
$type = $updater->getDB()->getType(); | |
$sqlPath = dirname( __DIR__ ) . '/sql'; | |
$updater->addExtensionTable( 'global_preferences', "$sqlPath/$type/tables-generated.sql" ); | |
if ( $type === 'mysql' || $type === 'sqlite' ) { | |
$updater->dropExtensionIndex( 'global_preferences', | |
'global_preferences_user_property', | |
"$sqlPath/patch_primary_index.sql" | |
); | |
$updater->modifyExtensionField( 'global_preferences', | |
'gp_user', | |
"$sqlPath/$type/patch-gp_user-unsigned.sql" | |
); | |
} | |
} | |
} | |
/** | |
* Replace the PreferencesFactory service with the GlobalPreferencesFactory. | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiServices | |
* @param MediaWikiServices $services The services object to use. | |
*/ | |
public static function onMediaWikiServices( MediaWikiServices $services ): void { | |
$services->redefineService( 'PreferencesFactory', static function ( MediaWikiServices $services ) { | |
$mainConfig = $services->getMainConfig(); | |
$config = new ServiceOptions( GlobalPreferencesFactory::CONSTRUCTOR_OPTIONS, | |
$mainConfig | |
); | |
$factory = new GlobalPreferencesFactory( | |
$config, | |
$services->getContentLanguage(), | |
$services->getAuthManager(), | |
$services->getLinkRendererFactory()->create(), | |
$services->getNamespaceInfo(), | |
$services->getPermissionManager(), | |
$services->getLanguageConverterFactory()->getLanguageConverter(), | |
$services->getLanguageNameUtils(), | |
$services->getHookContainer(), | |
$services->getUserOptionsLookup() | |
); | |
$factory->setLogger( LoggerFactory::getInstance( 'preferences' ) ); | |
$factory->setAutoGlobals( $mainConfig->get( 'GlobalPreferencesAutoPrefs' ) ); | |
return $factory; | |
} ); | |
} | |
/** | |
* Prevent local exception preferences from being cleaned up. | |
* @link https://www.mediawiki.org/wiki/Manual:Hooks/DeleteUnknownPreferences | |
* @param string[] &$where Array of where clause conditions to add to. | |
* @param IDatabase $db | |
*/ | |
public static function onDeleteUnknownPreferences( &$where, IDatabase $db ): void { | |
$like = $db->buildLike( $db->anyString(), GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX ); | |
$where[] = "up_property NOT $like"; | |
} | |
/** | |
* @param ApiOptions $apiModule | |
* @param User $user | |
* @param array $changes | |
*/ | |
public static function onApiOptions( ApiOptions $apiModule, User $user, | |
array $changes | |
): void { | |
// Only hook to the core module but not to our code that inherits from it | |
if ( $apiModule->getModuleName() !== 'options' ) { | |
return; | |
} | |
$factory = self::getPreferencesFactory(); | |
$globalPrefs = $factory->getGlobalPreferencesValues( $user ); | |
$userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); | |
$toWarn = []; | |
foreach ( array_keys( $changes ) as $preference ) { | |
if ( GlobalPreferencesFactory::isLocalPrefName( $preference ) ) { | |
continue; | |
} | |
$exceptionName = $preference . GlobalPreferencesFactory::LOCAL_EXCEPTION_SUFFIX; | |
if ( !$userOptionsLookup->getOption( $user, $exceptionName ) ) { | |
if ( $globalPrefs && array_key_exists( $preference, $globalPrefs ) ) { | |
$toWarn[] = $preference; | |
} | |
} | |
} | |
if ( $toWarn ) { | |
$toWarn = array_map( static function ( $str ) { | |
return wfEscapeWikiText( "`$str`" ); | |
}, $toWarn ); | |
$apiModule->addWarning( | |
[ | |
'apiwarn-globally-overridden', | |
Message::listParam( $toWarn ), | |
count( $toWarn ), | |
], | |
'globally-overridden' | |
); | |
} | |
} | |
/** | |
* Convenience method for getting a preferences factory instance and centralized | |
* wrestling with Phan. | |
* | |
* @return GlobalPreferencesFactory | |
*/ | |
private static function getPreferencesFactory(): GlobalPreferencesFactory { | |
/** @var GlobalPreferencesFactory $factory */ | |
$factory = MediaWikiServices::getInstance()->getPreferencesFactory(); | |
'@phan-var GlobalPreferencesFactory $factory'; | |
return $factory; | |
} | |
} |