Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
22.62% |
88 / 389 |
|
4.55% |
2 / 44 |
CRAP | |
0.00% |
0 / 1 |
MobileFrontendHooks | |
22.62% |
88 / 389 |
|
4.55% |
2 / 44 |
10714.42 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultMobileSkin | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
onRequestContextCreateSkin | |
68.18% |
15 / 22 |
|
0.00% |
0 / 1 |
8.58 | |||
onSkinAddFooterLinks | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
onSkinAfterBottomScripts | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
onBeforeDisplayNoArticleText | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
onOutputPageBeforeHTML | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
56 | |||
onOutputPageBodyAttributes | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
onBeforePageRedirect | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
onMediaWikiPerformAction | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
onResourceLoaderSiteStylesModulePages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
onResourceLoaderSiteModulePages | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
onGetCacheVaryCookies | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
getResourceLoaderMFConfigVars | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
12 | |||
getWikibaseStaticConfigVars | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
shouldMobileFormatSpecialPages | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
3.01 | |||
onSpecialPage_initList | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
onListDefinedTags | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onChangeTagsListActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addDefinedTags | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
onRecentChange_save | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onManualLogEntryBeforePublish | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onTaggableObjectCreation | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
onAbuseFilterGenerateUserVars | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
onAbuseFilterBuilder | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onSpecialPageBeforeExecute | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
onPostLoginRedirect | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
42 | |||
onBeforePageDisplay | |
85.71% |
54 / 63 |
|
0.00% |
0 / 1 |
18.94 | |||
onAfterBuildFeedLinks | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
onUserGetDefaultOptions | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onGetPreferences | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
onCentralAuthLoginRedirectData | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onCentralAuthSilentLoginRedirect | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
setTagline | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
findTagline | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
4 | |||
onOutputPageParserOutput | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
56 | |||
onArticleParserOptions | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
onHTMLFileCache__useFileCache | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onLoginFormValidErrorMessages | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
onMakeGlobalVariablesScript | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
hasEditNoticesFeatureConflict | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
onTitleSquidURLs | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
4 | |||
onAuthChangeFormFields | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
20 | |||
onAPIQuerySiteInfoGeneralInfo | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | // phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName |
4 | |
5 | use MediaWiki\Actions\ActionEntryPoint; |
6 | use MediaWiki\Api\ApiQuerySiteinfo; |
7 | use MediaWiki\Api\Hook\APIQuerySiteInfoGeneralInfoHook; |
8 | use MediaWiki\Auth\AuthenticationRequest; |
9 | use MediaWiki\Auth\AuthManager; |
10 | use MediaWiki\Cache\Hook\HTMLFileCache__useFileCacheHook; |
11 | use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook; |
12 | use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook; |
13 | use MediaWiki\ChangeTags\Taggable; |
14 | use MediaWiki\Config\Config; |
15 | use MediaWiki\Context\IContextSource; |
16 | use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder; |
17 | use MediaWiki\Extension\Gadgets\GadgetRepo; |
18 | use MediaWiki\Hook\LoginFormValidErrorMessagesHook; |
19 | use MediaWiki\Hook\ManualLogEntryBeforePublishHook; |
20 | use MediaWiki\Hook\MediaWikiPerformActionHook; |
21 | use MediaWiki\Hook\PostLoginRedirectHook; |
22 | use MediaWiki\Hook\RecentChange_saveHook; |
23 | use MediaWiki\Hook\RequestContextCreateSkinHook; |
24 | use MediaWiki\Hook\SkinAddFooterLinksHook; |
25 | use MediaWiki\Hook\SkinAfterBottomScriptsHook; |
26 | use MediaWiki\Hook\TitleSquidURLsHook; |
27 | use MediaWiki\HookContainer\HookContainer; |
28 | use MediaWiki\Html\Html; |
29 | use MediaWiki\MainConfigNames; |
30 | use MediaWiki\MediaWikiServices; |
31 | use MediaWiki\Output\Hook\AfterBuildFeedLinksHook; |
32 | use MediaWiki\Output\Hook\BeforePageDisplayHook; |
33 | use MediaWiki\Output\Hook\BeforePageRedirectHook; |
34 | use MediaWiki\Output\Hook\GetCacheVaryCookiesHook; |
35 | use MediaWiki\Output\Hook\MakeGlobalVariablesScriptHook; |
36 | use MediaWiki\Output\Hook\OutputPageBeforeHTMLHook; |
37 | use MediaWiki\Output\Hook\OutputPageBodyAttributesHook; |
38 | use MediaWiki\Output\Hook\OutputPageParserOutputHook; |
39 | use MediaWiki\Output\OutputPage; |
40 | use MediaWiki\Page\Hook\ArticleParserOptionsHook; |
41 | use MediaWiki\Page\Hook\BeforeDisplayNoArticleTextHook; |
42 | use MediaWiki\Parser\ParserOptions; |
43 | use MediaWiki\Parser\ParserOutput; |
44 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
45 | use MediaWiki\Registration\ExtensionRegistry; |
46 | use MediaWiki\Request\WebRequest; |
47 | use MediaWiki\ResourceLoader as RL; |
48 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderSiteModulePagesHook; |
49 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderSiteStylesModulePagesHook; |
50 | use MediaWiki\ResourceLoader\ResourceLoader; |
51 | use MediaWiki\SpecialPage\Hook\AuthChangeFormFieldsHook; |
52 | use MediaWiki\SpecialPage\Hook\SpecialPage_initListHook; |
53 | use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook; |
54 | use MediaWiki\SpecialPage\SpecialPage; |
55 | use MediaWiki\Title\Title; |
56 | use MediaWiki\User\Hook\UserGetDefaultOptionsHook; |
57 | use MediaWiki\User\Options\UserOptionsLookup; |
58 | use MediaWiki\User\User; |
59 | use MediaWiki\Utils\UrlUtils; |
60 | use MediaWiki\Watchlist\WatchlistManager; |
61 | use MobileFrontend\Api\ApiParseExtender; |
62 | use MobileFrontend\ContentProviders\DefaultContentProvider; |
63 | use MobileFrontend\Features\FeaturesManager; |
64 | use MobileFrontend\Hooks\HookRunner; |
65 | use MobileFrontend\Models\MobilePage; |
66 | use MobileFrontend\Transforms\LazyImageTransform; |
67 | use MobileFrontend\Transforms\MakeSectionsTransform; |
68 | |
69 | /** |
70 | * Hook handlers for MobileFrontend extension |
71 | * |
72 | * If your hook changes the behaviour of the Minerva skin, you are in the wrong place. |
73 | * Any changes relating to Minerva should go into Minerva.hooks.php |
74 | */ |
75 | class MobileFrontendHooks implements |
76 | APIQuerySiteInfoGeneralInfoHook, |
77 | AuthChangeFormFieldsHook, |
78 | RequestContextCreateSkinHook, |
79 | BeforeDisplayNoArticleTextHook, |
80 | OutputPageBeforeHTMLHook, |
81 | OutputPageBodyAttributesHook, |
82 | ResourceLoaderSiteStylesModulePagesHook, |
83 | ResourceLoaderSiteModulePagesHook, |
84 | SkinAfterBottomScriptsHook, |
85 | SkinAddFooterLinksHook, |
86 | BeforePageRedirectHook, |
87 | MediaWikiPerformActionHook, |
88 | GetCacheVaryCookiesHook, |
89 | SpecialPage_initListHook, |
90 | ListDefinedTagsHook, |
91 | ChangeTagsListActiveHook, |
92 | RecentChange_saveHook, |
93 | SpecialPageBeforeExecuteHook, |
94 | PostLoginRedirectHook, |
95 | BeforePageDisplayHook, |
96 | GetPreferencesHook, |
97 | OutputPageParserOutputHook, |
98 | ArticleParserOptionsHook, |
99 | HTMLFileCache__useFileCacheHook, |
100 | LoginFormValidErrorMessagesHook, |
101 | AfterBuildFeedLinksHook, |
102 | MakeGlobalVariablesScriptHook, |
103 | TitleSquidURLsHook, |
104 | UserGetDefaultOptionsHook, |
105 | ManualLogEntryBeforePublishHook |
106 | { |
107 | private const MOBILE_PREFERENCES_SECTION = 'rendering/mobile'; |
108 | public const MOBILE_PREFERENCES_SPECIAL_PAGES = 'mobile-specialpages'; |
109 | public const MOBILE_PREFERENCES_EDITOR = 'mobile-editor'; |
110 | public const MOBILE_PREFERENCES_FONTSIZE = 'mf-font-size'; |
111 | public const MOBILE_PREFERENCES_EXPAND_SECTIONS = 'mf-expand-sections'; |
112 | private const ENABLE_SPECIAL_PAGE_OPTIMISATIONS = '1'; |
113 | // This should always be kept in sync with Codex `@min-width-breakpoint-tablet` |
114 | // in mediawiki.skin.variables.less |
115 | private const DEVICE_WIDTH_TABLET = '640px'; |
116 | |
117 | private HookContainer $hookContainer; |
118 | private Config $config; |
119 | private SkinFactory $skinFactory; |
120 | private UserOptionsLookup $userOptionsLookup; |
121 | private WatchlistManager $watchlistManager; |
122 | private MobileContext $mobileContext; |
123 | private FeaturesManager $featuresManager; |
124 | private ?GadgetRepo $gadgetRepo; |
125 | |
126 | public function __construct( |
127 | HookContainer $hookContainer, |
128 | Config $config, |
129 | SkinFactory $skinFactory, |
130 | UserOptionsLookup $userOptionsLookup, |
131 | WatchlistManager $watchlistManager, |
132 | MobileContext $mobileContext, |
133 | FeaturesManager $featuresManager, |
134 | ?GadgetRepo $gadgetRepo |
135 | ) { |
136 | $this->hookContainer = $hookContainer; |
137 | $this->config = $config; |
138 | $this->skinFactory = $skinFactory; |
139 | $this->userOptionsLookup = $userOptionsLookup; |
140 | $this->watchlistManager = $watchlistManager; |
141 | $this->mobileContext = $mobileContext; |
142 | $this->featuresManager = $featuresManager; |
143 | $this->gadgetRepo = $gadgetRepo; |
144 | } |
145 | |
146 | /** |
147 | * Obtain the default mobile skin |
148 | * |
149 | * @throws SkinException If a factory function isn't registered for the skin name |
150 | * @return Skin |
151 | */ |
152 | protected function getDefaultMobileSkin(): Skin { |
153 | $defaultSkin = $this->config->get( 'DefaultMobileSkin' ) ?: |
154 | $this->config->get( MainConfigNames::DefaultSkin ); |
155 | return $this->skinFactory->makeSkin( Skin::normalizeKey( $defaultSkin ) ); |
156 | } |
157 | |
158 | /** |
159 | * RequestContextCreateSkin hook handler |
160 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/RequestContextCreateSkin |
161 | * |
162 | * @param IContextSource $context The RequestContext object the skin is being created for. |
163 | * @param Skin|null|string &$skin A variable reference you may set a Skin instance or string |
164 | * key on to override the skin that will be used for the context. |
165 | * @return bool |
166 | */ |
167 | public function onRequestContextCreateSkin( $context, &$skin ) { |
168 | $mobileContext = $this->mobileContext; |
169 | |
170 | $mobileContext->doToggling(); |
171 | if ( !$mobileContext->shouldDisplayMobileView() ) { |
172 | return true; |
173 | } |
174 | |
175 | // Handle any X-Analytics header values in the request by adding them |
176 | // as log items. X-Analytics header values are serialized key=value |
177 | // pairs, separated by ';', used for analytics purposes. |
178 | $xanalytics = $mobileContext->getRequest()->getHeader( 'X-Analytics' ); |
179 | if ( $xanalytics ) { |
180 | $xanalytics_arr = explode( ';', $xanalytics ); |
181 | if ( count( $xanalytics_arr ) > 1 ) { |
182 | foreach ( $xanalytics_arr as $xanalytics_item ) { |
183 | $mobileContext->addAnalyticsLogItemFromXAnalytics( $xanalytics_item ); |
184 | } |
185 | } else { |
186 | $mobileContext->addAnalyticsLogItemFromXAnalytics( $xanalytics ); |
187 | } |
188 | } |
189 | |
190 | // log whether user is using beta/stable |
191 | $mobileContext->logMobileMode(); |
192 | |
193 | // Allow overriding of skin by useskin e.g. useskin=vector&useformat=mobile or by |
194 | // setting the mobileskin preferences (api only currently) |
195 | $userSkin = $context->getRequest()->getRawVal( 'useskin' ) ?? |
196 | $this->userOptionsLookup->getOption( |
197 | $context->getUser(), 'mobileskin' |
198 | ); |
199 | if ( $userSkin && Skin::normalizeKey( $userSkin ) === $userSkin ) { |
200 | $skin = $this->skinFactory->makeSkin( $userSkin ); |
201 | } else { |
202 | $skin = $this->getDefaultMobileSkin(); |
203 | } |
204 | |
205 | $hookRunner = new HookRunner( $this->hookContainer ); |
206 | $hookRunner->onRequestContextCreateSkinMobile( $mobileContext, $skin ); |
207 | |
208 | return false; |
209 | } |
210 | |
211 | /** |
212 | * Update the footer |
213 | * @param Skin $skin |
214 | * @param string $key the current key for the current group (row) of footer links. |
215 | * e.g. `info` or `places`. |
216 | * @param array &$footerLinks an empty array that can be populated with new links. |
217 | * keys should be strings and will be used for generating the ID of the footer item |
218 | * and value should be an HTML string. |
219 | */ |
220 | public function onSkinAddFooterLinks( Skin $skin, string $key, array &$footerLinks ) { |
221 | $context = $this->mobileContext; |
222 | if ( $key === 'places' ) { |
223 | if ( $context->shouldDisplayMobileView() ) { |
224 | $terms = MobileFrontendSkinHooks::getTermsLink( $skin ); |
225 | if ( $terms ) { |
226 | $footerLinks['terms-use'] = $terms; |
227 | } |
228 | $footerLinks['desktop-toggle'] = MobileFrontendSkinHooks::getDesktopViewLink( $skin, $context ); |
229 | } else { |
230 | // If desktop site append a mobile view link |
231 | $footerLinks['mobileview'] = |
232 | MobileFrontendSkinHooks::getMobileViewLink( $skin, $context ); |
233 | } |
234 | } |
235 | } |
236 | |
237 | /** |
238 | * SkinAfterBottomScripts hook handler |
239 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/SkinAfterBottomScripts |
240 | * |
241 | * Adds an inline script for lazy loading the images in Grade C browsers. |
242 | * |
243 | * @param Skin $skin |
244 | * @param string &$html bottomScripts text. Append to $text to add additional |
245 | * text/scripts after the stock bottom scripts. |
246 | */ |
247 | public function onSkinAfterBottomScripts( $skin, &$html ) { |
248 | // TODO: We may want to enable the following script on Desktop Minerva... |
249 | // ... when Minerva is widely used. |
250 | if ( |
251 | $this->mobileContext->shouldDisplayMobileView() && |
252 | $this->featuresManager->isFeatureAvailableForCurrentUser( 'MFLazyLoadImages' ) |
253 | ) { |
254 | $html .= Html::inlineScript( ResourceLoader::filter( 'minify-js', |
255 | LazyImageTransform::gradeCImageSupport() |
256 | ) ); |
257 | } |
258 | } |
259 | |
260 | /** |
261 | * BeforeDisplayNoArticleText hook handler |
262 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforeDisplayNoArticleText |
263 | * |
264 | * @param Article $article The (empty) article |
265 | * @return bool This hook can abort |
266 | */ |
267 | public function onBeforeDisplayNoArticleText( $article ) { |
268 | $displayMobileView = $this->mobileContext->shouldDisplayMobileView(); |
269 | |
270 | $title = $article->getTitle(); |
271 | |
272 | // if the page is a userpage |
273 | // @todo: Upstream to core (T248347). |
274 | if ( $displayMobileView && |
275 | $title->inNamespaces( NS_USER ) && |
276 | !$title->isSubpage() |
277 | ) { |
278 | $out = $article->getContext()->getOutput(); |
279 | $userpagetext = ExtMobileFrontend::blankUserPageHTML( $out, $title ); |
280 | if ( $userpagetext ) { |
281 | // Replace the default message with ours |
282 | $out->addHTML( $userpagetext ); |
283 | return false; |
284 | } |
285 | } |
286 | |
287 | return true; |
288 | } |
289 | |
290 | /** |
291 | * OutputPageBeforeHTML hook handler |
292 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageBeforeHTML |
293 | * |
294 | * Applies MobileFormatter to mobile viewed content |
295 | * |
296 | * @param OutputPage $out the OutputPage object to which wikitext is added |
297 | * @param string &$text the HTML to be wrapped inside the #mw-content-text element |
298 | */ |
299 | public function onOutputPageBeforeHTML( $out, &$text ) { |
300 | // This hook can be executed more than once per page view if the page content is composed from |
301 | // multiple sources! Anything that doesn't depend on $text should use onBeforePageDisplay. |
302 | |
303 | $context = $this->mobileContext; |
304 | $title = $out->getTitle(); |
305 | $displayMobileView = $context->shouldDisplayMobileView(); |
306 | |
307 | if ( !$title ) { |
308 | return; |
309 | } |
310 | |
311 | $options = $this->config->get( 'MFMobileFormatterOptions' ); |
312 | $excludeNamespaces = $options['excludeNamespaces'] ?? []; |
313 | // Perform a few extra changes if we are in mobile mode |
314 | $namespaceAllowed = !$title->inNamespaces( $excludeNamespaces ); |
315 | |
316 | $provider = new DefaultContentProvider( $text ); |
317 | $originalProviderClass = DefaultContentProvider::class; |
318 | ( new HookRunner( $this->hookContainer ) )->onMobileFrontendContentProvider( |
319 | $provider, $out |
320 | ); |
321 | |
322 | $isParse = ApiParseExtender::isParseAction( |
323 | $context->getRequest()->getText( 'action' ) |
324 | ); |
325 | |
326 | if ( get_class( $provider ) === $originalProviderClass ) { |
327 | // This line is important to avoid the default content provider running unnecessarily |
328 | // on desktop views. |
329 | $useContentProvider = $displayMobileView; |
330 | $runMobileFormatter = $displayMobileView && ( |
331 | // T245160 - don't run the mobile formatter on old revisions. |
332 | // Note if not the default content provider we ignore this requirement. |
333 | $title->getLatestRevID() > 0 || |
334 | // Always allow the formatter in ApiParse |
335 | $isParse |
336 | ); |
337 | } else { |
338 | // When a custom content provider is enabled, always use it. |
339 | $useContentProvider = true; |
340 | $runMobileFormatter = $displayMobileView; |
341 | } |
342 | |
343 | if ( $namespaceAllowed && $useContentProvider ) { |
344 | $text = ExtMobileFrontend::domParseWithContentProvider( |
345 | $provider, $out, $runMobileFormatter |
346 | ); |
347 | } |
348 | } |
349 | |
350 | /** |
351 | * Modifies the `<body>` element's attributes. |
352 | * |
353 | * By default, the `class` attribute is set to the output's "bodyClassName" |
354 | * property. |
355 | * |
356 | * @param OutputPage $out |
357 | * @param Skin $skin |
358 | * @param string[] &$bodyAttrs |
359 | */ |
360 | public function onOutputPageBodyAttributes( $out, $skin, &$bodyAttrs ): void { |
361 | /** @var \MobileFrontend\Amc\UserMode $userMode */ |
362 | $userMode = MediaWikiServices::getInstance()->getService( 'MobileFrontend.AMC.UserMode' ); |
363 | $isMobile = $this->mobileContext->shouldDisplayMobileView(); |
364 | |
365 | // FIXME: This can be removed when existing references have been updated. |
366 | if ( $isMobile && !$userMode->isEnabled() ) { |
367 | $bodyAttrs['class'] .= ' mw-mf-amc-disabled'; |
368 | } |
369 | |
370 | if ( $isMobile ) { |
371 | // Add a class to the body so that TemplateStyles (which can only |
372 | // access html and body) and gadgets have something to check for. |
373 | // @stable added in 1.38 |
374 | $bodyAttrs['class'] .= ' mw-mf'; |
375 | } |
376 | } |
377 | |
378 | /** |
379 | * BeforePageRedirect hook handler |
380 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageRedirect |
381 | * |
382 | * Ensures URLs are handled properly for select special pages. |
383 | * @param OutputPage $out |
384 | * @param string &$redirect URL string, modifiable |
385 | * @param string &$code HTTP code (eg '301' or '302'), modifiable |
386 | */ |
387 | public function onBeforePageRedirect( $out, &$redirect, &$code ) { |
388 | $shouldDisplayMobileView = $this->mobileContext->shouldDisplayMobileView(); |
389 | if ( !$shouldDisplayMobileView ) { |
390 | return; |
391 | } |
392 | |
393 | // T45123: force mobile URLs only for local redirects |
394 | if ( $this->mobileContext->isLocalUrl( $redirect ) ) { |
395 | $out->addVaryHeader( 'X-Subdomain' ); |
396 | $redirect = $this->mobileContext->getMobileUrl( $redirect ); |
397 | } |
398 | } |
399 | |
400 | /** |
401 | * MediaWikiPerformActionHook hook handler |
402 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/MediaWikiPerformActionHook |
403 | * |
404 | * Set Diff page to diff-only mode for mobile view |
405 | * |
406 | * @param OutputPage $output Context output |
407 | * @param Article $article Article on which the action will be performed |
408 | * @param Title $title Title on which the action will be performed |
409 | * @param User $user Context user |
410 | * @param WebRequest $request Context request |
411 | * @param ActionEntryPoint $entryPoint |
412 | * @return bool|void True or no return value to continue or false to abort |
413 | */ |
414 | public function onMediaWikiPerformAction( $output, $article, $title, $user, |
415 | $request, $entryPoint |
416 | ) { |
417 | if ( !$this->mobileContext->shouldDisplayMobileView() ) { |
418 | // this code should only apply to mobile view. |
419 | return; |
420 | } |
421 | |
422 | // Default to diff-only mode on mobile diff pages if not specified. |
423 | if ( $request->getCheck( 'diff' ) && !$request->getCheck( 'diffonly' ) ) { |
424 | $request->setVal( 'diffonly', 'true' ); |
425 | } |
426 | } |
427 | |
428 | /** |
429 | * ResourceLoaderSiteStylesModulePages hook handler |
430 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderSiteStylesModulePages |
431 | * |
432 | * @param string $skin |
433 | * @param array &$pages to sort modules from. |
434 | */ |
435 | public function onResourceLoaderSiteStylesModulePages( $skin, array &$pages ): void { |
436 | $ctx = $this->mobileContext; |
437 | // Use Mobile.css instead of MediaWiki:Common.css on mobile views. |
438 | if ( $ctx->shouldDisplayMobileView() && $this->config->get( 'MFCustomSiteModules' ) ) { |
439 | unset( $pages['MediaWiki:Common.css'] ); |
440 | unset( $pages['MediaWiki:Print.css'] ); |
441 | if ( $this->config->get( 'MFSiteStylesRenderBlocking' ) ) { |
442 | $pages['MediaWiki:Mobile.css'] = [ 'type' => 'style' ]; |
443 | } |
444 | } |
445 | } |
446 | |
447 | /** |
448 | * ResourceLoaderSiteModulePages hook handler |
449 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderSiteModulePages |
450 | * |
451 | * @param string $skin |
452 | * @param array &$pages to sort modules from. |
453 | */ |
454 | public function onResourceLoaderSiteModulePages( $skin, array &$pages ): void { |
455 | $ctx = $this->mobileContext; |
456 | // Use Mobile.js instead of MediaWiki:Common.js and MediaWiki:<skinname.js> on mobile views. |
457 | if ( $ctx->shouldDisplayMobileView() && $this->config->get( 'MFCustomSiteModules' ) ) { |
458 | unset( $pages['MediaWiki:Common.js'] ); |
459 | $pages['MediaWiki:Mobile.js'] = [ 'type' => 'script' ]; |
460 | if ( !$this->config->get( 'MFSiteStylesRenderBlocking' ) ) { |
461 | $pages['MediaWiki:Mobile.css'] = [ 'type' => 'style' ]; |
462 | } |
463 | } |
464 | } |
465 | |
466 | /** |
467 | * GetCacheVaryCookies hook handler |
468 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetCacheVaryCookies |
469 | * |
470 | * @param OutputPage $out |
471 | * @param array &$cookies array of cookies name, add a value to it |
472 | * if you want to add a cookie that have to vary cache options |
473 | */ |
474 | public function onGetCacheVaryCookies( $out, &$cookies ) { |
475 | // Enables mobile cookies on wikis w/o mobile domain |
476 | $cookies[] = MobileContext::USEFORMAT_COOKIE_NAME; |
477 | // Don't redirect to mobile if user had explicitly opted out of it |
478 | $cookies[] = MobileContext::STOP_MOBILE_REDIRECT_COOKIE_NAME; |
479 | |
480 | if ( |
481 | $this->mobileContext->shouldDisplayMobileView() || |
482 | !$this->mobileContext->hasMobileDomain() |
483 | ) { |
484 | // beta cookie |
485 | $cookies[] = MobileContext::OPTIN_COOKIE_NAME; |
486 | } |
487 | } |
488 | |
489 | /** |
490 | * Generate config for usage inside MobileFrontend |
491 | * This should be used for variables which: |
492 | * - vary with the html |
493 | * - variables that should work cross skin including anonymous users |
494 | * - used for both, stable and beta mode (don't use |
495 | * MobileContext::isBetaGroupMember in this function - T127860) |
496 | * |
497 | * @return array |
498 | */ |
499 | public static function getResourceLoaderMFConfigVars() { |
500 | $vars = []; |
501 | $config = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Config' ); |
502 | $mfScriptPath = $config->get( 'MFScriptPath' ); |
503 | $pageProps = $config->get( 'MFQueryPropModules' ); |
504 | $searchParams = $config->get( 'MFSearchAPIParams' ); |
505 | // Avoid API warnings and allow integration with optional extensions. |
506 | if ( $mfScriptPath || ExtensionRegistry::getInstance()->isLoaded( 'PageImages' ) ) { |
507 | $pageProps[] = 'pageimages'; |
508 | $searchParams = array_merge_recursive( $searchParams, [ |
509 | 'piprop' => 'thumbnail', |
510 | 'pithumbsize' => MobilePage::SMALL_IMAGE_WIDTH, |
511 | 'pilimit' => 50, |
512 | ] ); |
513 | } |
514 | |
515 | // Get the licensing agreement that is displayed in the uploading interface. |
516 | $vars += [ |
517 | // Page.js |
518 | 'wgMFMobileFormatterHeadings' => $config->get( 'MFMobileFormatterOptions' )['headings'], |
519 | // extendSearchParams |
520 | 'wgMFSearchAPIParams' => $searchParams, |
521 | 'wgMFQueryPropModules' => $pageProps, |
522 | // SearchGateway.js |
523 | 'wgMFSearchGenerator' => $config->get( 'MFSearchGenerator' ), |
524 | // PhotoListGateway.js, SearchGateway.js |
525 | 'wgMFThumbnailSizes' => [ |
526 | 'tiny' => MobilePage::TINY_IMAGE_WIDTH, |
527 | 'small' => MobilePage::SMALL_IMAGE_WIDTH, |
528 | ], |
529 | 'wgMFEnableJSConsoleRecruitment' => $config->get( 'MFEnableJSConsoleRecruitment' ), |
530 | // Browser.js |
531 | 'wgMFDeviceWidthTablet' => self::DEVICE_WIDTH_TABLET, |
532 | // extendSearchParams.js |
533 | 'wgMFTrackBlockNotices' => $config->get( 'MFTrackBlockNotices' ), |
534 | ]; |
535 | return $vars; |
536 | } |
537 | |
538 | /** |
539 | * @param MobileContext $context |
540 | * @return array |
541 | */ |
542 | private function getWikibaseStaticConfigVars( |
543 | MobileContext $context |
544 | ) { |
545 | $features = array_keys( $this->config->get( 'MFDisplayWikibaseDescriptions' ) ); |
546 | $result = [ 'wgMFDisplayWikibaseDescriptions' => [] ]; |
547 | $descriptionsEnabled = $this->featuresManager->isFeatureAvailableForCurrentUser( |
548 | 'MFEnableWikidataDescriptions' |
549 | ); |
550 | |
551 | foreach ( $features as $feature ) { |
552 | $result['wgMFDisplayWikibaseDescriptions'][$feature] = $descriptionsEnabled && |
553 | $context->shouldShowWikibaseDescriptions( $feature, $this->config ); |
554 | } |
555 | |
556 | return $result; |
557 | } |
558 | |
559 | /** |
560 | * Should special pages be replaced with mobile formatted equivalents? |
561 | * |
562 | * @internal |
563 | * @param User $user for which we need to make the decision based on user prefs |
564 | * @return bool whether special pages should be substituted with |
565 | * mobile friendly equivalents |
566 | */ |
567 | public function shouldMobileFormatSpecialPages( $user ) { |
568 | $enabled = $this->config->get( 'MFEnableMobilePreferences' ); |
569 | |
570 | if ( !$enabled ) { |
571 | return true; |
572 | } |
573 | if ( !$user->isSafeToLoad() ) { |
574 | // if not isSafeToLoad |
575 | // assume an anonymous session |
576 | // (see I2a6ef640d328106c88331da7c53785486e16a353) |
577 | return true; |
578 | } |
579 | |
580 | $userOption = $this->userOptionsLookup->getOption( |
581 | $user, |
582 | self::MOBILE_PREFERENCES_SPECIAL_PAGES, |
583 | self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS |
584 | ); |
585 | |
586 | return $userOption === self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS; |
587 | } |
588 | |
589 | /** |
590 | * Hook for SpecialPage_initList in SpecialPageFactory. |
591 | * |
592 | * @param array &$list list of special page classes |
593 | */ |
594 | public function onSpecialPage_initList( &$list ) { |
595 | $user = $this->mobileContext->getUser(); |
596 | |
597 | // Perform substitutions of pages that are unsuitable for mobile |
598 | // FIXME: Upstream these changes to core. |
599 | if ( |
600 | $this->mobileContext->shouldDisplayMobileView() && |
601 | $this->shouldMobileFormatSpecialPages( $user ) && |
602 | $user->isSafeToLoad() |
603 | ) { |
604 | if ( |
605 | !$this->featuresManager->isFeatureAvailableForCurrentUser( 'MFUseDesktopSpecialEditWatchlistPage' ) |
606 | ) { |
607 | $list['EditWatchlist'] = SpecialMobileEditWatchlist::class; |
608 | } |
609 | } |
610 | } |
611 | |
612 | /** |
613 | * ListDefinedTags hook handler |
614 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ListDefinedTags |
615 | * |
616 | * @param array &$tags The list of tags. Add your extension's tags to this array. |
617 | */ |
618 | public function onListDefinedTags( &$tags ) { |
619 | $this->addDefinedTags( $tags ); |
620 | } |
621 | |
622 | /** |
623 | * ChangeTagsListActive hook handler |
624 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ChangeTagsListActive |
625 | * |
626 | * @param array &$tags The list of tags. Add your extension's tags to this array. |
627 | */ |
628 | public function onChangeTagsListActive( &$tags ) { |
629 | $this->addDefinedTags( $tags ); |
630 | } |
631 | |
632 | /** |
633 | * @param array &$tags |
634 | */ |
635 | public function addDefinedTags( &$tags ) { |
636 | $tags[] = 'mobile edit'; |
637 | $tags[] = 'mobile web edit'; |
638 | } |
639 | |
640 | /** |
641 | * RecentChange_save hook handler that tags mobile changes |
642 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/RecentChange_save |
643 | * |
644 | * @param RecentChange $recentChange |
645 | */ |
646 | public function onRecentChange_save( $recentChange ) { |
647 | self::onTaggableObjectCreation( $recentChange ); |
648 | } |
649 | |
650 | /** |
651 | * ManualLogEntryBeforePublish hook handler that tags actions logged when user uses mobile mode |
652 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ManualLogEntryBeforePublish |
653 | * |
654 | * @param ManualLogEntry $logEntry |
655 | */ |
656 | public function onManualLogEntryBeforePublish( $logEntry ): void { |
657 | self::onTaggableObjectCreation( $logEntry ); |
658 | } |
659 | |
660 | /** |
661 | * @param Taggable $taggable Object to tag |
662 | */ |
663 | public static function onTaggableObjectCreation( Taggable $taggable ) { |
664 | /** @var MobileContext $context */ |
665 | $context = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' ); |
666 | $userAgent = $context->getRequest()->getHeader( "User-agent" ); |
667 | if ( $context->shouldDisplayMobileView() ) { |
668 | $taggable->addTags( [ 'mobile edit' ] ); |
669 | // Tag as mobile web edit specifically, if it isn't coming from the apps |
670 | if ( strpos( $userAgent, 'WikipediaApp/' ) !== 0 ) { |
671 | $taggable->addTags( [ 'mobile web edit' ] ); |
672 | } |
673 | } |
674 | } |
675 | |
676 | /** |
677 | * AbuseFilter-generateUserVars hook handler that adds a user_mobile variable. |
678 | * Altering the variables generated for a specific user |
679 | * |
680 | * @see hooks.txt in AbuseFilter extension |
681 | * @param VariableHolder $vars object to add vars to |
682 | * @param User $user |
683 | * @param RecentChange|null $rc If the variables should be generated for an RC entry, this |
684 | * is the entry. Null if it's for the current action being filtered. |
685 | */ |
686 | public static function onAbuseFilterGenerateUserVars( $vars, $user, ?RecentChange $rc = null ) { |
687 | $services = MediaWikiServices::getInstance(); |
688 | |
689 | if ( !$rc ) { |
690 | /** @var MobileContext $context */ |
691 | $context = $services->getService( 'MobileFrontend.Context' ); |
692 | $vars->setVar( 'user_mobile', $context->shouldDisplayMobileView() ); |
693 | } else { |
694 | |
695 | $dbr = $services->getConnectionProvider()->getReplicaDatabase(); |
696 | |
697 | $tags = $services->getChangeTagsStore()->getTags( $dbr, $rc->getAttribute( 'rc_id' ) ); |
698 | $val = (bool)array_intersect( $tags, [ 'mobile edit', 'mobile web edit' ] ); |
699 | $vars->setVar( 'user_mobile', $val ); |
700 | } |
701 | } |
702 | |
703 | /** |
704 | * AbuseFilter-builder hook handler that adds user_mobile variable to list |
705 | * of valid vars |
706 | * |
707 | * @param array &$builder Array in AbuseFilter::getBuilderValues to add to. |
708 | */ |
709 | public static function onAbuseFilterBuilder( &$builder ) { |
710 | $builder['vars']['user_mobile'] = 'user-mobile'; |
711 | } |
712 | |
713 | /** |
714 | * Invocation of hook SpecialPageBeforeExecute |
715 | * |
716 | * We use this hook to ensure that login/account creation pages |
717 | * are redirected to HTTPS if they are not accessed via HTTPS and |
718 | * $wgSecureLogin == true - but only when using the |
719 | * mobile site. |
720 | * |
721 | * @param SpecialPage $special |
722 | * @param string $subpage subpage name |
723 | */ |
724 | public function onSpecialPageBeforeExecute( $special, $subpage ) { |
725 | $isMobileView = $this->mobileContext->shouldDisplayMobileView(); |
726 | $taglines = $this->mobileContext->getConfig()->get( 'MFSpecialPageTaglines' ); |
727 | $name = $special->getName(); |
728 | |
729 | if ( $isMobileView ) { |
730 | $out = $special->getOutput(); |
731 | // FIXME: mobile.special.styles should be replaced with mediawiki.special module |
732 | $out->addModuleStyles( |
733 | [ 'mobile.special.styles' ] |
734 | ); |
735 | // FIXME: Should be moved to MediaWiki core module. |
736 | if ( $name === 'Userlogin' || $name === 'CreateAccount' ) { |
737 | $out->addModules( 'mobile.special.userlogin.scripts' ); |
738 | } |
739 | if ( array_key_exists( $name, $taglines ) ) { |
740 | self::setTagline( $out, $out->msg( $taglines[$name] )->parse() ); |
741 | } |
742 | } |
743 | } |
744 | |
745 | /** |
746 | * PostLoginRedirect hook handler |
747 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PostLoginRedirect |
748 | * |
749 | * Used here to handle watchlist actions made by anons to be handled after |
750 | * login or account creation redirect. |
751 | * |
752 | * @inheritDoc |
753 | */ |
754 | public function onPostLoginRedirect( &$returnTo, &$returnToQuery, &$type ) { |
755 | $context = $this->mobileContext; |
756 | |
757 | if ( !$context->shouldDisplayMobileView() ) { |
758 | return; |
759 | } |
760 | |
761 | // If 'watch' is set from the login form, watch the requested article |
762 | $campaign = $context->getRequest()->getRawVal( 'campaign' ); |
763 | |
764 | // The user came from one of the drawers that prompted them to login. |
765 | // We must watch the article per their original intent. |
766 | if ( $campaign === 'mobile_watchPageActionCta' || |
767 | wfArrayToCgi( $returnToQuery ) === 'article_action=watch' |
768 | ) { |
769 | $title = Title::newFromText( $returnTo ); |
770 | // protect against watching special pages (these cannot be watched!) |
771 | if ( $title !== null && !$title->isSpecialPage() ) { |
772 | $this->watchlistManager->addWatch( $context->getAuthority(), $title ); |
773 | } |
774 | } |
775 | } |
776 | |
777 | /** |
778 | * BeforePageDisplay hook handler |
779 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay |
780 | * |
781 | * @param OutputPage $out |
782 | * @param Skin $skin Skin object that will be used to generate the page, added in 1.13. |
783 | */ |
784 | public function onBeforePageDisplay( $out, $skin ): void { |
785 | $context = $this->mobileContext; |
786 | $mfEnableXAnalyticsLogging = $this->config->get( 'MFEnableXAnalyticsLogging' ); |
787 | $mfNoIndexPages = $this->config->get( 'MFNoindexPages' ); |
788 | $isCanonicalLinkHandledByCore = $this->config->get( 'EnableCanonicalServerLink' ); |
789 | $hasMobileUrl = $context->hasMobileDomain(); |
790 | $displayMobileView = $context->shouldDisplayMobileView(); |
791 | |
792 | $title = $skin->getTitle(); |
793 | |
794 | // an canonical/alternate link is only useful, if the mobile and desktop URL are different |
795 | // and $wgMFNoindexPages needs to be true |
796 | if ( $hasMobileUrl && $mfNoIndexPages ) { |
797 | $link = false; |
798 | |
799 | if ( !$displayMobileView ) { |
800 | // add alternate link to desktop sites - bug T91183 |
801 | $desktopUrl = $title->getFullURL(); |
802 | $link = [ |
803 | 'rel' => 'alternate', |
804 | 'media' => 'only screen and (max-width: ' . self::DEVICE_WIDTH_TABLET . ')', |
805 | 'href' => $context->getMobileUrl( $desktopUrl ), |
806 | ]; |
807 | } elseif ( !$isCanonicalLinkHandledByCore ) { |
808 | $link = [ |
809 | 'rel' => 'canonical', |
810 | 'href' => $title->getFullURL(), |
811 | ]; |
812 | } |
813 | |
814 | if ( $link ) { |
815 | $out->addLink( $link ); |
816 | } |
817 | } |
818 | |
819 | // set the vary header to User-Agent, if mobile frontend auto detects, if the mobile |
820 | // view should be delivered and the same url is used for desktop and mobile devices |
821 | // Bug: T123189 |
822 | if ( |
823 | $this->config->get( 'MFVaryOnUA' ) && |
824 | $this->config->get( 'MFAutodetectMobileView' ) && |
825 | !$hasMobileUrl |
826 | ) { |
827 | $out->addVaryHeader( 'User-Agent' ); |
828 | } |
829 | |
830 | // Set X-Analytics HTTP response header if necessary |
831 | if ( $displayMobileView ) { |
832 | $analyticsHeader = ( $mfEnableXAnalyticsLogging ? $context->getXAnalyticsHeader() : false ); |
833 | if ( $analyticsHeader ) { |
834 | $resp = $out->getRequest()->response(); |
835 | $resp->header( $analyticsHeader ); |
836 | } |
837 | |
838 | // in mobile view: always add vary header |
839 | $out->addVaryHeader( 'Cookie' ); |
840 | |
841 | if ( $this->config->get( 'MFEnableManifest' ) ) { |
842 | $out->addLink( |
843 | [ |
844 | 'rel' => 'manifest', |
845 | 'href' => wfAppendQuery( |
846 | wfScript( 'api' ), |
847 | [ 'action' => 'webapp-manifest' ] |
848 | ) |
849 | ] |
850 | ); |
851 | } |
852 | |
853 | // In mobile mode, MediaWiki:Common.css/MediaWiki:Common.js is not loaded. |
854 | // We load MediaWiki:Mobile.css/js instead |
855 | // We load mobile.init so that lazy loading images works on all skins |
856 | $out->addModules( [ 'mobile.init' ] ); |
857 | $out->addModuleStyles( [ 'mobile.init.styles' ] ); |
858 | |
859 | $fontSize = $this->userOptionsLookup->getOption( |
860 | $context->getUser(), self::MOBILE_PREFERENCES_FONTSIZE |
861 | ) ?? 'small'; |
862 | $expandSections = $this->userOptionsLookup->getOption( |
863 | $context->getUser(), self::MOBILE_PREFERENCES_EXPAND_SECTIONS |
864 | ) ? '1' : '0'; |
865 | |
866 | /** @var \MobileFrontend\Amc\UserMode $userMode */ |
867 | $userMode = MediaWikiServices::getInstance()->getService( 'MobileFrontend.AMC.UserMode' ); |
868 | $amc = !$userMode->isEnabled() ? '0' : '1'; |
869 | $context->getOutput()->addHtmlClasses( [ |
870 | 'mf-expand-sections-clientpref-' . $expandSections, |
871 | 'mf-font-size-clientpref-' . $fontSize, |
872 | 'mw-mf-amc-clientpref-' . $amc |
873 | ] ); |
874 | } |
875 | |
876 | // T204691 |
877 | $theme = $this->config->get( 'MFManifestThemeColor' ); |
878 | if ( $theme && $displayMobileView ) { |
879 | $out->addMeta( 'theme-color', $theme ); |
880 | } |
881 | |
882 | if ( $displayMobileView ) { |
883 | // Adds inline script to allow opening of sections while JS is still loading |
884 | $out->prependHTML( MakeSectionsTransform::interimTogglingSupport() ); |
885 | } |
886 | } |
887 | |
888 | /** |
889 | * AfterBuildFeedLinks hook handler. Remove all feed links in mobile view. |
890 | * |
891 | * @param array &$tags Added feed links |
892 | */ |
893 | public function onAfterBuildFeedLinks( &$tags ) { |
894 | if ( |
895 | $this->mobileContext->shouldDisplayMobileView() && |
896 | !$this->config->get( 'MFRSSFeedLink' ) |
897 | ) { |
898 | $tags = []; |
899 | } |
900 | } |
901 | |
902 | /** |
903 | * Register default preferences for MobileFrontend |
904 | * |
905 | * @param array &$defaultUserOptions Reference to default options array |
906 | */ |
907 | public function onUserGetDefaultOptions( &$defaultUserOptions ) { |
908 | if ( $this->config->get( 'MFEnableMobilePreferences' ) ) { |
909 | $defaultUserOptions += [ |
910 | self::MOBILE_PREFERENCES_SPECIAL_PAGES => self::ENABLE_SPECIAL_PAGE_OPTIMISATIONS, |
911 | ]; |
912 | } |
913 | } |
914 | |
915 | /** |
916 | * GetPreferences hook handler |
917 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences |
918 | * |
919 | * @param User $user User whose preferences are being modified |
920 | * @param array &$preferences Preferences description array, to be fed to an HTMLForm object |
921 | */ |
922 | public function onGetPreferences( $user, &$preferences ) { |
923 | $definition = [ |
924 | 'type' => 'api', |
925 | 'default' => '', |
926 | ]; |
927 | $preferences[MobileContext::USER_MODE_PREFERENCE_NAME] = $definition; |
928 | $preferences[self::MOBILE_PREFERENCES_EDITOR] = $definition; |
929 | $preferences[self::MOBILE_PREFERENCES_FONTSIZE] = $definition; |
930 | $preferences[self::MOBILE_PREFERENCES_EXPAND_SECTIONS] = $definition; |
931 | |
932 | if ( $this->config->get( 'MFEnableMobilePreferences' ) ) { |
933 | $preferences[ self::MOBILE_PREFERENCES_SPECIAL_PAGES ] = [ |
934 | 'type' => 'check', |
935 | 'label-message' => 'mobile-frontend-special-pages-pref', |
936 | 'help-message' => 'mobile-frontend-special-pages-pref', |
937 | // The following messages are generated here: |
938 | // * prefs-mobile |
939 | 'section' => self::MOBILE_PREFERENCES_SECTION |
940 | ]; |
941 | } |
942 | } |
943 | |
944 | /** |
945 | * CentralAuthLoginRedirectData hook handler |
946 | * Saves mobile host so that the CentralAuth wiki could redirect back properly |
947 | * |
948 | * @see CentralAuthHooks::doCentralLoginRedirect in CentralAuth extension |
949 | * @param \MediaWiki\Extension\CentralAuth\User\CentralAuthUser $centralUser |
950 | * @param array &$data Redirect data |
951 | */ |
952 | public static function onCentralAuthLoginRedirectData( $centralUser, &$data ) { |
953 | /** @var MobileContext $context */ |
954 | $context = MediaWikiServices::getInstance()->getService( 'MobileFrontend.Context' ); |
955 | $server = $context->getConfig()->get( 'Server' ); |
956 | if ( $context->shouldDisplayMobileView() ) { |
957 | $data['mobileServer'] = $context->getMobileUrl( $server ); |
958 | } |
959 | } |
960 | |
961 | /** |
962 | * CentralAuthSilentLoginRedirect hook handler |
963 | * Points redirects from CentralAuth wiki to mobile domain if user has logged in from it |
964 | * @see SpecialCentralLogin in CentralAuth extension |
965 | * @param \MediaWiki\Extension\CentralAuth\User\CentralAuthUser $centralUser |
966 | * @param string &$url to redirect to |
967 | * @param array $info token information |
968 | */ |
969 | public static function onCentralAuthSilentLoginRedirect( $centralUser, &$url, $info ) { |
970 | if ( isset( $info['mobileServer'] ) ) { |
971 | $urlUtils = MediaWikiServices::getInstance()->getUrlUtils(); |
972 | $mobileUrlParsed = $urlUtils->parse( $info['mobileServer'] ); |
973 | $urlParsed = $urlUtils->parse( $url ); |
974 | $urlParsed['host'] = $mobileUrlParsed['host'] ?? ''; |
975 | $url = UrlUtils::assemble( $urlParsed ); |
976 | } |
977 | } |
978 | |
979 | /** |
980 | * Sets a tagline for a given page that can be displayed by the skin. |
981 | * |
982 | * @param OutputPage $outputPage |
983 | * @param string $desc |
984 | */ |
985 | private static function setTagline( OutputPage $outputPage, $desc ) { |
986 | $outputPage->setProperty( 'wgMFDescription', $desc ); |
987 | } |
988 | |
989 | /** |
990 | * Finds the wikidata tagline associated with the page |
991 | * |
992 | * @param ParserOutput $po |
993 | * @param callable $fallbackWikibaseDescriptionFunc A fallback to provide Wikibase description. |
994 | * Function takes wikibase_item as a first and only argument |
995 | * @return ?string the tagline as a string, or else null if none is found |
996 | */ |
997 | public static function findTagline( ParserOutput $po, $fallbackWikibaseDescriptionFunc ) { |
998 | $desc = $po->getPageProperty( 'wikibase-shortdesc' ); |
999 | $item = $po->getPageProperty( 'wikibase_item' ); |
1000 | if ( $desc === null && $item && $fallbackWikibaseDescriptionFunc ) { |
1001 | return $fallbackWikibaseDescriptionFunc( $item ); |
1002 | } |
1003 | return $desc; |
1004 | } |
1005 | |
1006 | /** |
1007 | * OutputPageParserOutput hook handler |
1008 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/OutputPageParserOutput |
1009 | * |
1010 | * @param OutputPage $outputPage the OutputPage object to which wikitext is added |
1011 | * @param ParserOutput $po |
1012 | */ |
1013 | public function onOutputPageParserOutput( $outputPage, $po ): void { |
1014 | $title = $outputPage->getTitle(); |
1015 | $descriptionsEnabled = !$title->isMainPage() && |
1016 | $title->getNamespace() === NS_MAIN && |
1017 | $this->featuresManager->isFeatureAvailableForCurrentUser( |
1018 | 'MFEnableWikidataDescriptions' |
1019 | ) && $this->mobileContext->shouldShowWikibaseDescriptions( 'tagline', $this->config ); |
1020 | |
1021 | // Only set the tagline if the feature has been enabled and the article is in the main namespace |
1022 | if ( $this->mobileContext->shouldDisplayMobileView() && $descriptionsEnabled ) { |
1023 | $desc = self::findTagline( $po, static function ( $item ) { |
1024 | return ExtMobileFrontend::getWikibaseDescription( $item ); |
1025 | } ); |
1026 | if ( $desc ) { |
1027 | self::setTagline( $outputPage, $desc ); |
1028 | } |
1029 | } |
1030 | } |
1031 | |
1032 | /** |
1033 | * ArticleParserOptions hook handler |
1034 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticleParserOptions |
1035 | * |
1036 | * @param Article $article |
1037 | * @param ParserOptions $parserOptions |
1038 | */ |
1039 | public function onArticleParserOptions( Article $article, ParserOptions $parserOptions ) { |
1040 | // while the parser is actively being migrated, we rely on the ParserMigration extension for using Parsoid |
1041 | if ( ExtensionRegistry::getInstance()->isLoaded( 'ParserMigration' ) ) { |
1042 | $context = $this->mobileContext; |
1043 | $oracle = MediaWikiServices::getInstance()->getService( 'ParserMigration.Oracle' ); |
1044 | |
1045 | $shouldUseParsoid = |
1046 | $oracle->shouldUseParsoid( $context->getUser(), $context->getRequest(), $article->getTitle() ); |
1047 | |
1048 | // set the collapsible sections parser flag so that section content is wrapped in a div for easier targeting |
1049 | // only if we're in mobile view and parsoid is enabled |
1050 | if ( $context->shouldDisplayMobileView() && $shouldUseParsoid ) { |
1051 | $parserOptions->setCollapsibleSections(); |
1052 | } |
1053 | } |
1054 | } |
1055 | |
1056 | /** |
1057 | * HTMLFileCache::useFileCache hook handler |
1058 | * Disables file caching for mobile pageviews |
1059 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/HTMLFileCache::useFileCache |
1060 | * |
1061 | * @param IContextSource $context |
1062 | * @return bool |
1063 | */ |
1064 | public function onHTMLFileCache__useFileCache( $context ) { |
1065 | return !$this->mobileContext->shouldDisplayMobileView(); |
1066 | } |
1067 | |
1068 | /** |
1069 | * LoginFormValidErrorMessages hook handler to promote MF specific error message be valid. |
1070 | * |
1071 | * @param array &$messages Array of already added messages |
1072 | */ |
1073 | public function onLoginFormValidErrorMessages( array &$messages ) { |
1074 | $messages = array_merge( $messages, |
1075 | [ |
1076 | // watchstart sign up CTA |
1077 | 'mobile-frontend-watchlist-signup-action', |
1078 | // Watchlist and watchstar sign in CTA |
1079 | 'mobile-frontend-watchlist-purpose', |
1080 | // Edit button sign in CTA |
1081 | 'mobile-frontend-edit-login-action', |
1082 | // Edit button sign-up CTA |
1083 | 'mobile-frontend-edit-signup-action', |
1084 | 'mobile-frontend-donate-image-login-action', |
1085 | // default message |
1086 | 'mobile-frontend-generic-login-new', |
1087 | ] |
1088 | ); |
1089 | } |
1090 | |
1091 | /** |
1092 | * Handler for MakeGlobalVariablesScript hook. |
1093 | * For values that depend on the current page, user or request state. |
1094 | * |
1095 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/MakeGlobalVariablesScript |
1096 | * @param array &$vars Variables to be added into the output |
1097 | * @param OutputPage $out OutputPage instance calling the hook |
1098 | */ |
1099 | public function onMakeGlobalVariablesScript( &$vars, $out ): void { |
1100 | $services = MediaWikiServices::getInstance(); |
1101 | /** @var \MobileFrontend\Amc\UserMode $userMode */ |
1102 | $userMode = $services->getService( 'MobileFrontend.AMC.UserMode' ); |
1103 | |
1104 | // If the device is a mobile, Remove the category entry. |
1105 | $context = $this->mobileContext; |
1106 | if ( $context->shouldDisplayMobileView() ) { |
1107 | /** @var \MobileFrontend\Amc\Outreach $outreach */ |
1108 | $outreach = $services->getService( 'MobileFrontend.AMC.Outreach' ); |
1109 | unset( $vars['wgCategories'] ); |
1110 | $vars['wgMFMode'] = $context->isBetaGroupMember() ? 'beta' : 'stable'; |
1111 | $vars['wgMFAmc'] = $userMode->isEnabled(); |
1112 | $vars['wgMFAmcOutreachActive'] = $outreach->isCampaignActive(); |
1113 | $vars['wgMFAmcOutreachUserEligible'] = $outreach->isUserEligible(); |
1114 | $vars['wgMFLazyLoadImages'] = |
1115 | $this->featuresManager->isFeatureAvailableForCurrentUser( 'MFLazyLoadImages' ); |
1116 | $vars['wgMFEditNoticesFeatureConflict'] = $this->hasEditNoticesFeatureConflict( |
1117 | $this->config, $context->getUser() |
1118 | ); |
1119 | } |
1120 | // Needed by mobile.startup and mobile.special.watchlist.scripts. |
1121 | // Needs to know if in beta mode or not and needs to load for Minerva desktop as well. |
1122 | // Ideally this would be inside ResourceLoaderFileModuleWithMFConfig but |
1123 | // sessions are not allowed there. |
1124 | $vars += $this->getWikibaseStaticConfigVars( $context ); |
1125 | } |
1126 | |
1127 | /** |
1128 | * Check if a conflicting edit notices gadget is enabled for the current user |
1129 | * |
1130 | * @param Config $config |
1131 | * @param User $user |
1132 | * @return bool |
1133 | */ |
1134 | private function hasEditNoticesFeatureConflict( Config $config, User $user ): bool { |
1135 | $gadgetName = $config->get( 'MFEditNoticesConflictingGadgetName' ); |
1136 | if ( !$gadgetName ) { |
1137 | return false; |
1138 | } |
1139 | |
1140 | if ( $this->gadgetRepo ) { |
1141 | $match = array_search( $gadgetName, $this->gadgetRepo->getGadgetIds(), true ); |
1142 | if ( $match !== false ) { |
1143 | try { |
1144 | return $this->gadgetRepo->getGadget( $gadgetName ) |
1145 | ->isEnabled( $user ); |
1146 | } catch ( \InvalidArgumentException $e ) { |
1147 | return false; |
1148 | } |
1149 | } |
1150 | } |
1151 | return false; |
1152 | } |
1153 | |
1154 | /** |
1155 | * Handler for TitleSquidURLs hook to add copies of the cache purge |
1156 | * URLs which are transformed according to the wgMobileUrlCallback, so |
1157 | * that both mobile and non-mobile URL variants get purged. |
1158 | * |
1159 | * @see * https://www.mediawiki.org/wiki/Manual:Hooks/TitleSquidURLs |
1160 | * @param Title $title the article title |
1161 | * @param array &$urls the set of URLs to purge |
1162 | */ |
1163 | public function onTitleSquidURLs( $title, &$urls ) { |
1164 | foreach ( $urls as $url ) { |
1165 | $newUrl = $this->mobileContext->getMobileUrl( $url ); |
1166 | if ( $newUrl !== false && $newUrl !== $url ) { |
1167 | $urls[] = $newUrl; |
1168 | } |
1169 | } |
1170 | } |
1171 | |
1172 | /** |
1173 | * Handler for the AuthChangeFormFields hook to add a logo on top of |
1174 | * the login screen. This is the AuthManager equivalent of changeUserLoginCreateForm. |
1175 | * @param AuthenticationRequest[] $requests AuthenticationRequest objects array |
1176 | * @param array $fieldInfo Field description as given by AuthenticationRequest::mergeFieldInfo |
1177 | * @param array &$formDescriptor A form descriptor suitable for the HTMLForm constructor |
1178 | * @param string $action One of the AuthManager::ACTION_* constants |
1179 | */ |
1180 | public function onAuthChangeFormFields( |
1181 | $requests, $fieldInfo, &$formDescriptor, $action |
1182 | ) { |
1183 | $logos = RL\SkinModule::getAvailableLogos( $this->config ); |
1184 | $mfLogo = $logos['icon'] ?? false; |
1185 | |
1186 | // do nothing in desktop mode |
1187 | if ( |
1188 | $this->mobileContext->shouldDisplayMobileView() && $mfLogo |
1189 | && in_array( $action, [ AuthManager::ACTION_LOGIN, AuthManager::ACTION_CREATE ], true ) |
1190 | ) { |
1191 | $logoHtml = Html::rawElement( 'div', [ 'class' => 'mw-mf-watermark' ], |
1192 | Html::element( 'img', [ 'src' => $mfLogo, 'alt' => '' ] ) ); |
1193 | $formDescriptor = [ |
1194 | 'mfLogo' => [ |
1195 | 'type' => 'info', |
1196 | 'default' => $logoHtml, |
1197 | 'raw' => true, |
1198 | ], |
1199 | ] + $formDescriptor; |
1200 | } |
1201 | } |
1202 | |
1203 | /** |
1204 | * Add the base mobile site URL to the siteinfo API output. |
1205 | * @param ApiQuerySiteinfo $module |
1206 | * @param array &$result Api result array |
1207 | */ |
1208 | public function onAPIQuerySiteInfoGeneralInfo( $module, &$result ) { |
1209 | global $wgCanonicalServer; |
1210 | $result['mobileserver'] = $this->mobileContext->getMobileUrl( $wgCanonicalServer ); |
1211 | } |
1212 | } |