%PDF- %PDF-
| Direktori : /www/varak.net/wiki.varak.net/extensions/MobileFrontend/includes/ |
| Current File : //www/varak.net/wiki.varak.net/extensions/MobileFrontend/includes/MobileFrontend.hooks.php |
<?php
use MediaWiki\Auth\AuthManager;
use MediaWiki\Auth\AuthenticationRequest;
/**
* Hook handlers for MobileFrontend extension
*
* Hook handler method names should be in the form of:
* on<HookName>()
* For intance, the hook handler for the 'RequestContextCreateSkin' would be called:
* onRequestContextCreateSkin()
*
* If your hook changes the behaviour of the Minerva skin, you are in the wrong place.
* Any changes relating to Minerva should go into Minerva.hooks.php
*/
class MobileFrontendHooks {
const MOBILE_PREFERENCES_SECTION = 'rendering/mobile';
const MOBILE_PREFERENCES_SPECIAL_PAGES = 'mobile-specialpages';
const ENABLE_SPECIAL_PAGE_OPTIMISATIONS = '1';
// This should always be kept in sync with @width-breakpoint-tablet
// in resources/src/mediawiki.less/mediawiki.ui/variables.less
const DEVICE_WIDTH_TABLET = '720px';
/**
* Enables the global booleans $wgHTMLFormAllowTableFormat and $wgUseMediaWikiUIEverywhere
* for mobile users.
*/
private static function enableMediaWikiUI() {
// FIXME: Temporary variables, will be deprecated in core in the future
global $wgHTMLFormAllowTableFormat, $wgUseMediaWikiUIEverywhere;
$mobileContext = MobileContext::singleton();
if ( $mobileContext->shouldDisplayMobileView() && !$mobileContext->isBlacklistedPage() ) {
// Force non-table based layouts (see bug 63428)
$wgHTMLFormAllowTableFormat = false;
// Turn on MediaWiki UI styles so special pages with form are styled.
// FIXME: Remove when this becomes the default.
$wgUseMediaWikiUIEverywhere = true;
}
}
/**
* Obtain the default mobile skin
*
* @param IContextSource $context ContextSource interface
* @param MobileContext $mobileContext
* @throws RuntimeException if MFDefaultSkinClass is incorrectly configured
* @return Skin
*/
protected static function getDefaultMobileSkin( IContextSource $context,
MobileContext $mobileContext
) {
$skinName = $mobileContext->getMFConfig()->get( 'MFDefaultSkinClass' );
if ( class_exists( $skinName ) ) {
$skin = new $skinName( $context );
} else {
throw new \RuntimeException(
'wgMFDefaultSkinClass is not setup correctly. ' .
'It should point to the class name of a valid skin e.g. SkinMinerva, SkinVector'
);
}
return $skin;
}
/**
* RequestContextCreateSkin hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/RequestContextCreateSkin
*
* @param IContextSource $context The RequestContext object the skin is being created for.
* @param Skin|null|string &$skin A variable reference you may set a Skin instance or string
* key on to override the skin that will be used for the context.
* @return bool
*/
public static function onRequestContextCreateSkin( $context, &$skin ) {
// FIXME: This shouldn't be a global, it should be possible for other extensions
// to set this via a static variable or set function in ULS
global $wgULSPosition;
$mobileContext = MobileContext::singleton();
$mobileContext->doToggling();
if ( !$mobileContext->shouldDisplayMobileView()
|| $mobileContext->isBlacklistedPage()
) {
return true;
}
// TODO, do we want to have a specific hook just for Mobile Features initialization
// or do we want to reuse the RequestContextCreateSkinMobile and use MediawikiService
// to retrieve the FeaturesManager
// Important: This must be run before RequestContextCreateSkinMobile which may make modifications
// to the skin based on enabled features.
\MediaWiki\MediaWikiServices::getInstance()
->getService( 'MobileFrontend.FeaturesManager' )
->setup();
// enable wgUseMediaWikiUIEverywhere
self::enableMediaWikiUI();
// FIXME: Remove hack around Universal Language selector bug 57091
$wgULSPosition = 'none';
// Handle any X-Analytics header values in the request by adding them
// as log items. X-Analytics header values are serialized key=value
// pairs, separated by ';', used for analytics purposes.
$xanalytics = $mobileContext->getRequest()->getHeader( 'X-Analytics' );
if ( $xanalytics ) {
$xanalytics_arr = explode( ';', $xanalytics );
if ( count( $xanalytics_arr ) > 1 ) {
foreach ( $xanalytics_arr as $xanalytics_item ) {
$mobileContext->addAnalyticsLogItemFromXAnalytics( $xanalytics_item );
}
} else {
$mobileContext->addAnalyticsLogItemFromXAnalytics( $xanalytics );
}
}
// log whether user is using beta/stable
$mobileContext->logMobileMode();
// Allow overriding of skin by useskin e.g. useskin=vector&useformat=mobile or by
// setting the mobileskin preferences (api only currently)
$userSkin = $context->getRequest()->getVal(
'useskin',
$context->getUser()->getOption( 'mobileskin' )
);
if ( $userSkin ) {
// Normalize the key in case the user is passing gibberish or has old preferences
$normalizedSkin = Skin::normalizeKey( $userSkin );
// If the skin has been normalized and is different from user input, use it
if ( $normalizedSkin === $userSkin ) {
$skin = $normalizedSkin;
return false;
}
}
$skin = self::getDefaultMobileSkin( $context, $mobileContext );
Hooks::run( 'RequestContextCreateSkinMobile', [ $mobileContext, $skin ] );
return false;
}
/**
* MediaWikiPerformAction hook handler (enable mwui for all pages)
* @see https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiPerformAction
*
* @param OutputPage $output
* @param Article $article
* @param Title $title Page title
* @param User $user User performing action
* @param RequestContext $request
* @param MediaWiki $wiki
* @return bool
*/
public static function onMediaWikiPerformAction( $output, $article, $title,
$user, $request, $wiki
) {
self::enableMediaWikiUI();
// don't prevent performAction to do anything
return true;
}
/**
* SkinTemplateOutputPageBeforeExec hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinTemplateOutputPageBeforeExec
*
* Adds a link to view the current page in 'mobile view' to the desktop footer.
*
* @param Skin &$skin
* @param QuickTemplate &$tpl
* @return bool
*/
public static function onSkinTemplateOutputPageBeforeExec( Skin &$skin, QuickTemplate &$tpl ) {
MobileFrontendSkinHooks::prepareFooter( $skin, $tpl );
return true;
}
/**
* SkinAfterBottomScripts hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinAfterBottomScripts
*
* Adds an inline script for lazy loading the images in Grade C browsers.
*
* @param Skin $skin
* @param string &$html bottomScripts text. Append to $text to add additional
* text/scripts after the stock bottom scripts.
* @return bool
*/
public static function onSkinAfterBottomScripts( Skin $skin, &$html ) {
$context = MobileContext::singleton();
$featureManager = \MediaWiki\MediaWikiServices::getInstance()
->getService( 'MobileFrontend.FeaturesManager' );
// TODO: We may want to enable the following script on Desktop Minerva...
// ... when Minerva is widely used.
if ( $context->shouldDisplayMobileView() &&
$featureManager->isFeatureAvailableInContext( 'MFLazyLoadImages', $context )
) {
$html .= Html::inlineScript( ResourceLoader::filter( 'minify-js',
MobileFrontendSkinHooks::gradeCImageSupport()
) );
}
return true;
}
/**
* OutputPageBeforeHTML hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageBeforeHTML
*
* Applies MobileFormatter to mobile viewed content
* Also enables Related Articles in the footer in the beta mode.
* Adds inline script to allow opening of sections while JS is still loading
*
* @param OutputPage &$out the OutputPage object to which wikitext is added
* @param string &$text the HTML to be wrapped inside the #mw-content-text element
* @return bool
*/
public static function onOutputPageBeforeHTML( &$out, &$text ) {
$context = MobileContext::singleton();
$title = $context->getTitle();
$config = $context->getMFConfig();
if ( !$title ) {
return true;
}
// Perform a few extra changes if we are in mobile mode
$namespaceAllowed = !$title->inNamespaces(
$config->get( 'MFMobileFormatterNamespaceBlacklist' )
);
$displayMobileView = $context->shouldDisplayMobileView();
$alwaysUseProvider = $config->get( 'MFAlwaysUseContentProvider' );
if ( $namespaceAllowed && ( $displayMobileView || $alwaysUseProvider ) ) {
$text = ExtMobileFrontend::domParse( $out, $text, $displayMobileView );
if ( !$title->isMainPage() ) {
$text = MobileFrontendSkinHooks::interimTogglingSupport() . $text;
}
}
return true;
}
/**
* BeforePageRedirect hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageRedirect
*
* Ensures URLs are handled properly for select special pages.
* @param OutputPage $out
* @param string &$redirect URL string, modifiable
* @param string &$code HTTP code (eg '301' or '302'), modifiable
* @return bool
*/
public static function onBeforePageRedirect( $out, &$redirect, &$code ) {
$context = MobileContext::singleton();
$shouldDisplayMobileView = $context->shouldDisplayMobileView();
if ( !$shouldDisplayMobileView ) {
return true;
}
// Bug 43123: force mobile URLs only for local redirects
if ( $context->isLocalUrl( $redirect ) ) {
$out->addVaryHeader( 'X-Subdomain' );
$out->addVaryHeader( 'X-CS' );
$redirect = $context->getMobileUrl( $redirect );
}
return true;
}
/**
* DiffViewHeader hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/DiffViewHeader
*
* Redirect Diff page to mobile version if appropriate
*
* @param DifferenceEngine $diff DifferenceEngine object that's calling
* @param Revision $oldRev Revision object of the "old" revision (may be null/invalid)
* @param Revision $newRev Revision object of the "new" revision
* @return bool
*/
public static function onDiffViewHeader( $diff, $oldRev, $newRev ) {
$context = MobileContext::singleton();
// Only do redirects to MobileDiff if user is in mobile view and it's not a special page
if ( $context->shouldDisplayMobileView() &&
!$diff->getContext()->getTitle()->isSpecialPage() &&
self::shouldMobileFormatSpecialPages( $context->getUser() )
) {
$output = $context->getOutput();
$newRevId = $newRev->getId();
// The MobileDiff page currently only supports showing a single revision, so
// only redirect to MobileDiff if we are sure this isn't a multi-revision diff.
if ( $oldRev ) {
// Get the revision immediately before the new revision
$prevRev = $newRev->getPrevious();
if ( $prevRev ) {
$prevRevId = $prevRev->getId();
$oldRevId = $oldRev->getId();
if ( $prevRevId === $oldRevId ) {
$output->redirect( SpecialPage::getTitleFor( 'MobileDiff', $newRevId )->getFullURL() );
}
}
} else {
$output->redirect( SpecialPage::getTitleFor( 'MobileDiff', $newRevId )->getFullURL() );
}
}
return true;
}
/**
* ResourceLoaderTestModules hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderTestModules
*
* @param array &$testModules array of javascript testing modules,
* keyed by framework (e.g. 'qunit').
* @param ResourceLoader &$resourceLoader
* @return bool
*/
public static function onResourceLoaderTestModules( array &$testModules,
ResourceLoader &$resourceLoader
) {
// FIXME: Global core variable don't use it.
global $wgResourceModules;
$testFiles = [];
$dependencies = [];
$localBasePath = dirname( __DIR__ );
// find test files for every RL module
foreach ( $wgResourceModules as $key => $module ) {
$hasTests = false;
if ( substr( $key, 0, 7 ) === 'mobile.' && isset( $module['scripts'] ) ) {
foreach ( $module['scripts'] as $script ) {
$testFile = 'tests/' . dirname( $script ) . '/' .
preg_replace( '/.js$/', '.test.js', basename( $script ) );
// For resources folder
$testFile = str_replace( 'tests/resources/', 'tests/qunit/', $testFile );
// if a test file exists for a given JS file, add it
if ( file_exists( $localBasePath . '/' . $testFile ) ) {
$testFiles[] = $testFile;
$hasTests = true;
}
}
// if test files exist for given module, create a corresponding test module
if ( $hasTests ) {
$dependencies[] = $key;
}
}
}
$testFiles[] = 'resources/dist/tests.mobilefrontend.js';
// While several of our files have been moved out of the
// resources folder, to the src folder,
// their test file remains in tests/qunit (cannot be run
// in headless mode). Therefore, the test file is not autodiscovered by the
// code above and needs to be explicitly added to the array of supported
// test files.
$testFiles[] = 'tests/qunit/mobile.startup/PageGateway.test.js';
$testFiles[] = 'tests/qunit/mobile.startup/Overlay.test.js';
$testModule = [
'dependencies' => $dependencies,
'templates' => [
'section.hogan' => 'tests/qunit/tests.mobilefrontend/section.hogan',
'issues.hogan' => 'tests/qunit/tests.mobilefrontend/issues.hogan',
'skinPage.html' => 'tests/qunit/tests.mobilefrontend/skinPage.html',
'page.html' => 'tests/qunit/tests.mobilefrontend/page.html',
'page2.html' => 'tests/qunit/tests.mobilefrontend/page2.html',
'pageWithStrippedRefs.html' => 'tests/qunit/tests.mobilefrontend/pageWithStrippedRefs.html',
'references.html' => 'tests/qunit/tests.mobilefrontend/references.html'
],
'localBasePath' => $localBasePath,
'remoteExtPath' => 'MobileFrontend',
'targets' => [ 'mobile', 'desktop' ],
'scripts' => $testFiles,
];
// Expose templates module
$testModules['qunit']["tests.mobilefrontend"] = $testModule;
return true;
}
/**
* GetCacheVaryCookies hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetCacheVaryCookies
*
* @param OutputPage $out
* @param array &$cookies array of cookies name, add a value to it
* if you want to add a cookie that have to vary cache options
* @return bool
*/
public static function onGetCacheVaryCookies( $out, &$cookies ) {
$context = MobileContext::singleton();
$mobileUrlTemplate = $context->getMobileUrlTemplate();
// Enables mobile cookies on wikis w/o mobile domain
$cookies[] = MobileContext::USEFORMAT_COOKIE_NAME;
// Don't redirect to mobile if user had explicitly opted out of it
$cookies[] = MobileContext::STOP_MOBILE_REDIRECT_COOKIE_NAME;
if ( $context->shouldDisplayMobileView() || !$mobileUrlTemplate ) {
// beta cookie
$cookies[] = MobileContext::OPTIN_COOKIE_NAME;
}
// Redirect people who want so from HTTP to HTTPS. Ideally, should be
// only for HTTP but we don't vary on protocol.
$cookies[] = 'forceHTTPS';
return true;
}
/**
* Varies the parser cache if responsive images should have their variants
* stripped from the parser output, since the transformation happens during
* the parse.
*
* See `$wgMFStripResponsiveImages` and `$wgMFResponsiveImageWhitelist` for
* more detail about the stripping of responsive images.
*
* See https://www.mediawiki.org/wiki/Manual:Hooks/PageRenderingHash for more
* detail about the `PageRenderingHash` hook.
*
* @param string &$confstr Reference to the parser cache key
* @param User $user The user that is requesting the page
* @param array &$forOptions The options used to generate the parser cache key
*/
public static function onPageRenderingHash( &$confstr, User $user, &$forOptions ) {
if ( MobileContext::singleton()->shouldStripResponsiveImages() ) {
$confstr .= '!responsiveimages=0';
}
}
/**
* ResourceLoaderGetConfigVars hook handler
* This should be used for variables which:
* - vary with the html
* - variables that should work cross skin including anonymous users
* - used for both, stable and beta mode (don't use
* MobileContext::isBetaGroupMember in this function - T127860)
*
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderGetConfigVars
*
* @param array &$vars Array of variables to be added into the output of the startup module.
* @return bool
*/
public static function onResourceLoaderGetConfigVars( &$vars ) {
$context = MobileContext::singleton();
$config = $context->getMFConfig();
$pageProps = $config->get( 'MFQueryPropModules' );
$searchParams = $config->get( 'MFSearchAPIParams' );
// Avoid API warnings and allow integration with optional extensions.
if ( ExtensionRegistry::getInstance()->isLoaded( 'PageImages' ) ) {
$pageProps[] = 'pageimages';
$searchParams = array_merge_recursive( $searchParams, [
'piprop' => 'thumbnail',
'pithumbsize' => MobilePage::SMALL_IMAGE_WIDTH,
'pilimit' => 50,
] );
}
// Get the licensing agreement that is displayed in the uploading interface.
$vars += [
'wgMFMobileFormatterHeadings' => $config->get( 'MFMobileFormatterHeadings' ),
'wgMFSearchAPIParams' => $searchParams,
'wgMFQueryPropModules' => $pageProps,
'wgMFSearchGenerator' => $config->get( 'MFSearchGenerator' ),
'wgMFNearbyEndpoint' => $config->get( 'MFNearbyEndpoint' ),
'wgMFThumbnailSizes' => [
'tiny' => MobilePage::TINY_IMAGE_WIDTH,
'small' => MobilePage::SMALL_IMAGE_WIDTH,
],
'wgMFEditorOptions' => $config->get( 'MFEditorOptions' ),
'wgMFLicense' => MobileFrontendSkinHooks::getLicense( 'editor' ),
'wgMFSchemaSearchSampleRate' => $config->get( 'MFSchemaSearchSampleRate' ),
'wgMFExperiments' => $config->get( 'MFExperiments' ),
'wgMFEnableJSConsoleRecruitment' => $config->get( 'MFEnableJSConsoleRecruitment' ),
'wgMFPhotoUploadEndpoint' =>
$config->get( 'MFPhotoUploadEndpoint' ) ? $config->get( 'MFPhotoUploadEndpoint' ) : '',
'wgMFDeviceWidthTablet' => self::DEVICE_WIDTH_TABLET,
'wgMFCollapseSectionsByDefault' => $config->get( 'MFCollapseSectionsByDefault' ),
];
return true;
}
/**
* @param MobileContext $context
* @return array
*/
private static function getWikibaseStaticConfigVars( MobileContext $context ) {
$config = $context->getMFConfig();
$features = array_keys( $config->get( 'MFDisplayWikibaseDescriptions' ) );
$result = [ 'wgMFDisplayWikibaseDescriptions' => [] ];
$featureManager = \MediaWiki\MediaWikiServices::getInstance()
->getService( 'MobileFrontend.FeaturesManager' );
$descriptionsEnabled = $featureManager->isFeatureAvailableInContext(
'MFEnableWikidataDescriptions',
$context
);
foreach ( $features as $feature ) {
$result['wgMFDisplayWikibaseDescriptions'][$feature] = $descriptionsEnabled &&
$context->shouldShowWikibaseDescriptions( $feature );
}
return $result;
}
/**
* Should special pages be replaced with mobile formatted equivalents?
*
* @param User $user for which we need to make the decision based on user prefs
* @return bool whether special pages should be substituted with
* mobile friendly equivalents
*/
public static function shouldMobileFormatSpecialPages( $user ) {
$ctx = MobileContext::singleton();
$enabled = $ctx->getMFConfig()->get( 'MFEnableMobilePreferences' );
if ( !$enabled ) {
return true;
} elseif ( !$user->isSafeToLoad() ) {
// if not isSafeToLoad
// assume an anonymous session
// (see I2a6ef640d328106c88331da7c53785486e16a353)
return true;
} else {
return $user->getOption( self::MOBILE_PREFERENCES_SPECIAL_PAGES,
self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS ) === self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS;
}
}
/**
* Hook for SpecialPage_initList in SpecialPageFactory.
*
* @param array &$list list of special page classes
* @return bool hook return value
*/
public static function onSpecialPageInitList( &$list ) {
$ctx = MobileContext::singleton();
// Perform substitutions of pages that are unsuitable for mobile
// FIXME: Upstream these changes to core.
if ( $ctx->shouldDisplayMobileView() &&
self::shouldMobileFormatSpecialPages( $ctx->getUser() )
) {
// Replace the standard watchlist view with our custom one
$list['Watchlist'] = 'SpecialMobileWatchlist';
$list['EditWatchlist'] = 'SpecialMobileEditWatchlist';
/* Special:MobileContributions redefines Special:History in
* such a way that for Special:Contributions/Foo, Foo is a
* username (in Special:History/Foo, Foo is a page name).
* Redirect people here as this is essential
* Special:Contributions without the bells and whistles.
*/
$list['Contributions'] = 'SpecialMobileContributions';
}
// add Special:Nearby only, if Nearby is activated
if ( $ctx->getMFConfig()->get( 'MFNearby' ) ) {
$list['Nearby'] = 'SpecialNearby';
}
return true;
}
/**
* ListDefinedTags and ChangeTagsListActive hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ListDefinedTags
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ChangeTagsListActive
*
* @param array &$tags The list of tags. Add your extension's tags to this array.
* @return bool
*/
public static function onListDefinedTags( &$tags ) {
$tags[] = 'mobile edit';
$tags[] = 'mobile web edit';
return true;
}
/**
* RecentChange_save hook handler that tags mobile changes
* @see https://www.mediawiki.org/wiki/Manual:Hooks/RecentChange_save
*
* @param RecentChange $rc
* @return bool
*/
public static function onRecentChangeSave( RecentChange $rc ) {
$context = MobileContext::singleton();
$userAgent = $context->getRequest()->getHeader( "User-agent" );
$logType = $rc->getAttribute( 'rc_log_type' );
// Only log edits and uploads
if ( $context->shouldDisplayMobileView() && ( $logType === 'upload' || is_null( $logType ) ) ) {
$rc->addTags( 'mobile edit' );
// Tag as mobile web edit specifically, if it isn't coming from the apps
if ( strpos( $userAgent, 'WikipediaApp/' ) !== 0 ) {
$rc->addTags( 'mobile web edit' );
}
}
return true;
}
/**
* AbuseFilter-GenerateUserVars hook handler that adds a user_mobile variable.
* Altering the variables generated for a specific user
*
* @see hooks.txt in AbuseFilter extension
* @param AbuseFilterVariableHolder $vars object to add vars to
* @param User $user
* @return bool
*/
public static function onAbuseFilterGenerateUserVars( $vars, $user ) {
$context = MobileContext::singleton();
if ( $context->shouldDisplayMobileView() ) {
$vars->setVar( 'user_mobile', true );
} else {
$vars->setVar( 'user_mobile', false );
}
return true;
}
/**
* AbuseFilter-builder hook handler that adds user_mobile variable to list
* of valid vars
*
* @param array &$builder Array in AbuseFilter::getBuilderValues to add to.
* @return bool
*/
public static function onAbuseFilterBuilder( &$builder ) {
$builder['vars']['user_mobile'] = 'user-mobile';
return true;
}
/**
* Invocation of hook SpecialPageBeforeExecute
*
* We use this hook to ensure that login/account creation pages
* are redirected to HTTPS if they are not accessed via HTTPS and
* $wgSecureLogin == true - but only when using the
* mobile site.
*
* @param SpecialPage $special
* @param string $subpage subpage name
* @return bool
*/
public static function onSpecialPageBeforeExecute( SpecialPage $special, $subpage ) {
$context = MobileContext::singleton();
$isMobileView = $context->shouldDisplayMobileView();
$taglines = $context->getConfig()->get( 'MFSpecialPageTaglines' );
$name = $special->getName();
if ( $isMobileView ) {
$special->getOutput()->addModuleStyles(
[ 'mobile.special.styles', 'mobile.messageBox.styles' ]
);
if ( $name === 'Userlogin' || $name === 'CreateAccount' ) {
$special->getOutput()->addModules( 'mobile.special.userlogin.scripts' );
}
if ( array_key_exists( $name, $taglines ) ) {
self::setTagline( $special->getOutput(),
wfMessage( $taglines[$name] ) );
}
}
return true;
}
/**
* UserLoginComplete hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/UserLoginComplete
*
* Used here to handle watchlist actions made by anons to be handled after
* login or account creation.
*
* @param User &$currentUser the user object that was created on login
* @param string &$injected_html From 1.13, any HTML to inject after the login success message.
* @return bool
*/
public static function onUserLoginComplete( &$currentUser, &$injected_html ) {
$context = MobileContext::singleton();
if ( !$context->shouldDisplayMobileView() ) {
return true;
}
// If 'watch' is set from the login form, watch the requested article
$watch = $context->getRequest()->getVal( 'watch' );
if ( !is_null( $watch ) ) {
$title = Title::newFromText( $watch );
// protect against watching special pages (these cannot be watched!)
if ( !is_null( $title ) && !$title->isSpecialPage() ) {
WatchAction::doWatch( $title, $currentUser );
}
}
return true;
}
/**
* BeforePageDisplay hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
*
* @param OutputPage &$out
* @param Skin &$skin Skin object that will be used to generate the page, added in 1.13.
* @return bool
*/
public static function onBeforePageDisplay( OutputPage &$out, Skin &$skin ) {
$context = MobileContext::singleton();
$config = $context->getMFConfig();
$mfEnableXAnalyticsLogging = $config->get( 'MFEnableXAnalyticsLogging' );
$mfAppPackageId = $config->get( 'MFAppPackageId' );
$mfAppScheme = $config->get( 'MFAppScheme' );
$mfNoIndexPages = $config->get( 'MFNoindexPages' );
$mfMobileUrlTemplate = $context->getMobileUrlTemplate();
$title = $skin->getTitle();
$request = $context->getRequest();
// Add deep link to a mobile app specified by $wgMFAppScheme
if ( ( $mfAppPackageId !== false ) && ( $title->isContentPage() )
&& ( $request->getRawQueryString() === '' )
) {
$fullUrl = $title->getFullURL();
$mobileUrl = $context->getMobileUrl( $fullUrl );
$path = preg_replace( "/^([a-z]+:)?(\/)*/", '', $mobileUrl, 1 );
$scheme = 'http';
if ( $mfAppScheme !== false ) {
$scheme = $mfAppScheme;
} else {
$protocol = $request->getProtocol();
if ( $protocol != '' ) {
$scheme = $protocol;
}
}
$hreflink = 'android-app://' . $mfAppPackageId . '/' . $scheme . '/' . $path;
$out->addLink( [ 'rel' => 'alternate', 'href' => $hreflink ] );
}
// an canonical/alternate link is only useful, if the mobile and desktop URL are different
// and $wgMFNoindexPages needs to be true
if ( $mfMobileUrlTemplate && $mfNoIndexPages ) {
$link = false;
if ( !$context->shouldDisplayMobileView() ) {
// add alternate link to desktop sites - bug T91183
$desktopUrl = $title->getFullUrl();
$link = [
'rel' => 'alternate',
'media' => 'only screen and (max-width: ' . self::DEVICE_WIDTH_TABLET . ')',
'href' => $context->getMobileUrl( $desktopUrl ),
];
} elseif ( !$title->isSpecial( 'MobileCite' ) ) {
// Add canonical link to mobile pages (except for Special:MobileCite),
// instead of noindex - bug T91183.
$link = [
'rel' => 'canonical',
'href' => $title->getFullUrl(),
];
}
if ( $link !== false ) {
$out->addLink( $link );
}
}
// set the vary header to User-Agent, if mobile frontend auto detects, if the mobile
// view should be delivered and the same url is used for desktop and mobile devices
// Bug: T123189
if (
$config->get( 'MFVaryOnUA' ) &&
$config->get( 'MFAutodetectMobileView' ) &&
!$config->get( 'MobileUrlTemplate' )
) {
$out->addVaryHeader( 'User-Agent' );
}
// Set X-Analytics HTTP response header if necessary
if ( $context->shouldDisplayMobileView() ) {
$analyticsHeader = ( $mfEnableXAnalyticsLogging ? $context->getXAnalyticsHeader() : false );
if ( $analyticsHeader ) {
$resp = $out->getRequest()->response();
$resp->header( $analyticsHeader );
}
// in mobile view: always add vary header
$out->addVaryHeader( 'Cookie' );
// set the mobile target
if ( !$context->isBlacklistedPage() ) {
$out->setTarget( 'mobile' );
}
if ( $config->get( 'MFEnableManifest' ) ) {
$out->addLink(
[
'rel' => 'manifest',
'href' => wfAppendQuery(
wfScript( 'api' ),
[ 'action' => 'webapp-manifest' ]
)
]
);
}
// In mobile mode, MediaWiki:Common.css/MediaWiki:Common.js is not loaded.
// We load MediaWiki:Mobile.css/js instead
// We load mobile.init so that lazy loading images works on all skins
$out->addModules( [ 'mobile.site', 'mobile.init' ] );
if ( $title->isMainPage() && $config->get( 'MFMobileMainPageCss' ) ) {
$out->addModuleStyles( [ 'mobile.mainpage.css' ] );
}
if ( $config->get( 'MFSiteStylesRenderBlocking' ) ) {
$out->addModuleStyles( [ 'mobile.site.styles' ] );
}
// Allow modifications in mobile only mode
Hooks::run( 'BeforePageDisplayMobile', [ &$out, &$skin ] );
// Warning box styles are needed when reviewing old revisions
// and inside the fallback editor styles to action=edit page
$requestAction = $out->getRequest()->getVal( 'action' );
if (
$out->getRequest()->getText( 'oldid' ) ||
$requestAction === 'edit' || $requestAction === 'submit'
) {
$out->addModuleStyles( [
'mobile.messageBox.styles'
] );
}
}
return true;
}
/**
* AfterBuildFeedLinks hook handler. Remove all feed links in mobile view.
*
* @param array &$tags Added feed links
*/
public static function onAfterBuildFeedLinks( array &$tags ) {
$context = MobileContext::singleton();
if ( $context->shouldDisplayMobileView() && !$context->getMFConfig()->get( 'MFRSSFeedLink' ) ) {
$tags = [];
}
}
/**
* Register default preferences for MobileFrontend
*
* @param array &$wgDefaultUserOptions Reference to default options array
*/
public static function onUserGetDefaultOptions( &$wgDefaultUserOptions ) {
$wgDefaultUserOptions += [
self::MOBILE_PREFERENCES_SPECIAL_PAGES => self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS,
];
}
/**
* GetPreferences hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences
*
* @param User $user User whose preferences are being modified
* @param array &$preferences Preferences description array, to be fed to an HTMLForm object
*
* @return bool
*/
public static function onGetPreferences( $user, &$preferences ) {
$ctx = MobileContext::singleton();
$config = $ctx->getMFConfig();
$defaultSkin = $config->get( 'DefaultSkin' );
$definition = [
'type' => 'api',
'default' => '',
];
$preferences[SpecialMobileWatchlist::FILTER_OPTION_NAME] = $definition;
$preferences[SpecialMobileWatchlist::VIEW_OPTION_NAME] = $definition;
if ( $config->get( 'MFEnableMobilePreferences' ) ) {
$preferences[ self::MOBILE_PREFERENCES_SPECIAL_PAGES ] = [
'type' => 'check',
'label-message' => 'mobile-frontend-special-pages-pref',
'help-message' => 'mobile-frontend-special-pages-pref',
'section' => self::MOBILE_PREFERENCES_SECTION
];
}
return true;
}
/**
* Gadgets::allowLegacy hook handler
*
* @return bool
*/
public static function onAllowLegacyGadgets() {
return !MobileContext::singleton()->shouldDisplayMobileView();
}
/**
* CentralAuthLoginRedirectData hook handler
* Saves mobile host so that the CentralAuth wiki could redirect back properly
*
* @see CentralAuthHooks::doCentralLoginRedirect in CentralAuth extension
* @param CentralAuthUser $centralUser
* @param array &$data Redirect data
*
* @return bool
*/
public static function onCentralAuthLoginRedirectData( $centralUser, &$data ) {
$context = MobileContext::singleton();
$server = $context->getConfig()->get( 'Server' );
if ( $context->shouldDisplayMobileView() ) {
$data['mobileServer'] = $context->getMobileUrl( $server );
}
return true;
}
/**
* CentralAuthSilentLoginRedirect hook handler
* Points redirects from CentralAuth wiki to mobile domain if user has logged in from it
* @see SpecialCentralLogin in CentralAuth extension
* @param CentralAuthUser $centralUser
* @param string &$url to redirect to
* @param array $info token information
*
* @return bool
*/
public static function onCentralAuthSilentLoginRedirect( $centralUser, &$url, $info ) {
if ( isset( $info['mobileServer'] ) ) {
$mobileUrlParsed = wfParseUrl( $info['mobileServer'] );
$urlParsed = wfParseUrl( $url );
$urlParsed['host'] = $mobileUrlParsed['host'];
$url = wfAssembleUrl( $urlParsed );
}
return true;
}
/**
* ResourceLoaderRegisterModules hook handler.
*
* Registers:
*
* * EventLogging schema modules, if the EventLogging extension is loaded;
* * Modules for the Visual Editor overlay, if the VisualEditor extension is loaded; and
* * Modules for the notifications overlay, if the Echo extension is loaded.
*
* @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
*
* @param ResourceLoader &$resourceLoader
* @return bool Always true
*/
public static function onResourceLoaderRegisterModules( ResourceLoader &$resourceLoader ) {
$resourceBoilerplate = [
'localBasePath' => dirname( __DIR__ ),
'remoteExtPath' => 'MobileFrontend',
];
// add VisualEditor related modules only, if VisualEditor seems to be installed - T85007
if ( ExtensionRegistry::getInstance()->isLoaded( 'VisualEditor' ) ) {
$resourceLoader->register( [
'mobile.editor.ve' => $resourceBoilerplate + [
'dependencies' => [
'ext.visualEditor.mobileArticleTarget',
'mobile.editor.common',
'mobile.startup',
],
'scripts' => [
'resources/mobile.editor.ve/ve.init.mw.MobileFrontendArticleTarget.js',
'resources/mobile.editor.ve/VisualEditorOverlay.js',
],
'styles' => [
'resources/mobile.editor.ve/editor.ve.less',
],
'templates' => [
'contentVE.hogan' => 'resources/mobile.editor.ve/contentVE.hogan',
'toolbarVE.hogan' => 'resources/mobile.editor.ve/toolbarVE.hogan',
],
'messages' => [
'mobile-frontend-page-edit-summary',
'mobile-frontend-editor-editing',
],
'targets' => [
'mobile',
],
],
] );
}
// add Echo, if it's installed
if ( ExtensionRegistry::getInstance()->isLoaded( 'Echo' ) ) {
$resourceLoader->register( [
'mobile.notifications.overlay' => $resourceBoilerplate + [
'dependencies' => [
'mediawiki.util',
'mobile.startup',
'ext.echo.ui',
'oojs-ui.styles.icons-interactions',
],
'scripts' => [
'resources/mobile.notifications.overlay/NotificationsOverlay.js',
'resources/mobile.notifications.overlay/NotificationsFilterOverlay.js',
],
'styles' => [
'resources/mobile.notifications.overlay/NotificationsOverlay.less',
'resources/mobile.notifications.overlay/NotificationsFilterOverlay.less',
],
'messages' => [
'mobile-frontend-notifications-filter-title',
// defined in Echo
'echo-none',
'notifications',
'echo-overlay-link',
'echo-mark-all-as-read-confirmation',
],
'targets' => [ 'mobile', 'desktop' ],
],
'mobile.notifications.filter.overlay' => [
'dependencies' => [
'mobile.notifications.overlay',
],
'deprecated' => 'Please use "mobile.notifications.overlay" instead.',
'targets' => [ 'mobile', 'desktop' ],
],
] );
};
return true;
}
/**
* Sets a tagline for a given page that can be displayed by the skin.
*
* @param OutputPage $outputPage
* @param string $desc
*/
private static function setTagline( OutputPage $outputPage, $desc ) {
$outputPage->setProperty( 'wgMFDescription', $desc );
}
/**
* Finds the wikidata tagline associated with the page
*
* @param ParserOutput $po
* @param Callable $fallbackWikibaseDescriptionFunc A fallback to provide Wikibase description.
* Function takes wikibase_item as a first and only argument
* @return string
*/
public static function findTagline( ParserOutput $po, $fallbackWikibaseDescriptionFunc ) {
$desc = $po->getProperty( 'wikibase-shortdesc' );
$item = $po->getProperty( 'wikibase_item' );
if ( $desc === false && $item && $fallbackWikibaseDescriptionFunc ) {
return $fallbackWikibaseDescriptionFunc( $item );
}
return $desc;
}
/**
* OutputPageParserOutput hook handler
* @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput
*
* @param OutputPage $outputPage the OutputPage object to which wikitext is added
* @param ParserOutput $po
* @return bool
*/
public static function onOutputPageParserOutput( $outputPage, ParserOutput $po ) {
$context = MobileContext::singleton();
if ( $context->shouldDisplayMobileView() ) {
$title = $outputPage->getTitle();
// Only set the tagline if the feature has been enabled and the article is in the main namespace
if ( $context->shouldShowWikibaseDescriptions( 'tagline' ) &&
!$title->isMainPage() &&
$title->getNamespace() === NS_MAIN
) {
$desc = self::findTagline( $po, function ( $item ) {
return ExtMobileFrontend::getWikibaseDescription( $item );
} );
if ( $desc ) {
self::setTagline( $outputPage, $desc );
}
}
}
return true;
}
/**
* HTMLFileCache::useFileCache hook handler
* Disables file caching for mobile pageviews
* @see https://www.mediawiki.org/wiki/Manual:Hooks/HTMLFileCache::useFileCache
*
* @return bool
*/
public static function onHTMLFileCacheUseFileCache() {
return !MobileContext::singleton()->shouldDisplayMobileView();
}
/**
* Removes the responsive image's variants from the parser output if
* configured to do so and the thumbnail's MIME type isn't whitelisted.
*
* See https://www.mediawiki.org/wiki/Manual:Hooks/ThumbnailBeforeProduceHTML
* for more detail about the `ThumbnailBeforeProduceHTML` hook.
*
* @param ThumbnailImage $thumbnail
* @param array &$attribs The attributes of the DOMElement being contructed
* to represent the thumbnail
* @param array &$linkAttribs The attributes of the DOMElement being
* constructed to represent the link to original file
*/
public static function onThumbnailBeforeProduceHTML( $thumbnail, &$attribs, &$linkAttribs ) {
$context = MobileContext::singleton();
$config = $context->getMFConfig();
if ( $context->shouldStripResponsiveImages() ) {
$file = $thumbnail->getFile();
if ( !$file || !in_array( $file->getMimeType(),
$config->get( 'MFResponsiveImageWhitelist' ) ) ) {
// Remove all responsive image 'srcset' attributes, except
// from SVG->PNG renderings which usually aren't too huge,
// or other whitelisted types.
// Note that in future, srcset may be used for specifying
// small-screen-friendly image variants as well as density
// variants, so this should be used with caution.
unset( $attribs['srcset'] );
}
}
}
/**
* LoginFormValidErrorMessages hook handler to promote MF specific error message be valid.
*
* @param array &$messages Array of already added messages
*/
public static function onLoginFormValidErrorMessages( &$messages ) {
$messages = array_merge( $messages,
[
// watchstart sign up CTA
'mobile-frontend-watchlist-signup-action',
// Watchlist and watchstar sign in CTA
'mobile-frontend-watchlist-purpose',
// Uploads link
'mobile-frontend-donate-image-anon',
// Edit button sign in CTA
'mobile-frontend-edit-login-action',
// Edit button sign-up CTA
'mobile-frontend-edit-signup-action',
'mobile-frontend-donate-image-login-action',
// default message
'mobile-frontend-generic-login-new',
]
);
}
/**
* Handler for MakeGlobalVariablesScript hook.
* For values that depend on the current page, user or request state.
*
* @see http://www.mediawiki.org/wiki/Manual:Hooks/MakeGlobalVariablesScript
* @param array &$vars Variables to be added into the output
* @param OutputPage $out OutputPage instance calling the hook
* @return bool true in all cases
*/
public static function onMakeGlobalVariablesScript( array &$vars, OutputPage $out ) {
$featureManager = \MediaWiki\MediaWikiServices::getInstance()
->getService( 'MobileFrontend.FeaturesManager' );
// If the device is a mobile, Remove the category entry.
$context = MobileContext::singleton();
if ( $context->shouldDisplayMobileView() ) {
unset( $vars['wgCategories'] );
$vars['wgMFMode'] = $context->isBetaGroupMember() ? 'beta' : 'stable';
$vars['wgMFLazyLoadImages'] =
$featureManager->isFeatureAvailableInContext( 'MFLazyLoadImages', $context );
$vars['wgMFLazyLoadReferences'] =
$featureManager->isFeatureAvailableInContext( 'MFLazyLoadReferences', $context );
}
$title = $out->getTitle();
// Accesses getBetaGroupMember so does not belong in onResourceLoaderGetConfigVars
$vars['wgMFExpandAllSectionsUserOption'] =
$featureManager->isFeatureAvailableInContext( 'MFExpandAllSectionsUserOption', $context );
$vars['wgMFEnableFontChanger'] =
$featureManager->isFeatureAvailableInContext( 'MFEnableFontChanger', $context );
$vars += self::getWikibaseStaticConfigVars( $context );
return true;
}
/**
* Handler for TitleSquidURLs hook to add copies of the cache purge
* URLs which are transformed according to the wgMobileUrlTemplate, so
* that both mobile and non-mobile URL variants get purged.
*
* @see * http://www.mediawiki.org/wiki/Manual:Hooks/TitleSquidURLs
* @param Title $title the article title
* @param array &$urls the set of URLs to purge
*/
public static function onTitleSquidURLs( Title $title, array &$urls ) {
$context = MobileContext::singleton();
foreach ( $urls as $url ) {
$newUrl = $context->getMobileUrl( $url );
if ( $newUrl !== false && $newUrl !== $url ) {
$urls[] = $newUrl;
}
}
}
/**
* Handler for the AuthChangeFormFields hook to add a logo on top of
* the login screen. This is the AuthManager equivalent of changeUserLoginCreateForm.
* @param AuthenticationRequest[] $requests AuthenticationRequest objects array
* @param array $fieldInfo Field description as given by AuthenticationRequest::mergeFieldInfo
* @param array &$formDescriptor A form descriptor suitable for the HTMLForm constructor
* @param string $action One of the AuthManager::ACTION_* constants
*/
public static function onAuthChangeFormFields(
array $requests, array $fieldInfo, array &$formDescriptor, $action
) {
$context = MobileContext::singleton();
$mfLogo = $context->getMFConfig()->get( 'MobileFrontendLogo' );
// do nothing in desktop mode
if (
$context->shouldDisplayMobileView() && $mfLogo
&& in_array( $action, [ AuthManager::ACTION_LOGIN, AuthManager::ACTION_CREATE ], true )
) {
$logoHtml = Html::rawElement( 'div', [ 'class' => 'watermark' ],
Html::element( 'img', [ 'src' => $mfLogo, 'alt' => '' ] ) );
$formDescriptor = [
'mfLogo' => [
'type' => 'info',
'default' => $logoHtml,
'raw' => true,
],
] + $formDescriptor;
}
}
/**
* Add the base mobile site URL to the siteinfo API output.
* @param ApiQuerySiteinfo $module
* @param array &$result Api result array
*/
public static function onAPIQuerySiteInfoGeneralInfo( ApiQuerySiteinfo $module, array &$result ) {
global $wgCanonicalServer;
$ctx = MobileContext::singleton();
$result['mobileserver'] = $ctx->getMobileUrl( $wgCanonicalServer );
}
/**
* Use inline diff engine when on Special:MobileDiff.
* @param IContextSource $context
* @param int $old Old revision ID or 0 for current
* @param int $new New revision ID or 0 for current
* @param bool $refreshCache Refresh diff cache
* @param bool $unhide Show deleted revisions
* @param DifferenceEngine &$differenceEngine Difference engine to alter/replace
*/
public static function onGetDifferenceEngine(
IContextSource $context, $old, $new, $refreshCache, $unhide, &$differenceEngine
) {
if ( $differenceEngine === null ) {
// old hook behavior before 1.32
throw new Exception( 'Incompatible MediaWiki version!' );
}
// FIXME hack for T201842. DifferenceEngine does both calculation and formatting of
// diffs; the two should be separated.
if (
get_class( $differenceEngine ) === DifferenceEngine::class
&& $context->getTitle()->isSpecial( 'MobileDiff' )
) {
if ( defined( 'MW_PHPUNIT_TEST' ) ) {
$differenceEngine = new MockInlineDifferenceEngine();
} else {
$differenceEngine = new InlineDifferenceEngine( $context, $old, $new, 0,
$refreshCache, $unhide );
}
}
}
}