Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
37.36% covered (danger)
37.36%
34 / 91
50.00% covered (danger)
50.00%
5 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
PopupsHooks
37.36% covered (danger)
37.36%
34 / 91
50.00% covered (danger)
50.00%
5 / 10
165.55
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getCustomPopupTypes
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 onGetPreferences
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
4
 getPagePreviewPrefToggle
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 getReferencePreviewPrefToggle
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 onBeforePageDisplay
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 onResourceLoaderGetConfigVars
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 onMakeGlobalVariablesScript
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onUserGetDefaultOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 onLocalUserCreated
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/*
3 * This file is part of the MediaWiki extension Popups.
4 *
5 * Popups is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * Popups is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with Popups.  If not, see <http://www.gnu.org/licenses/>.
17 *
18 * @file
19 * @ingroup extensions
20 */
21namespace Popups;
22
23use ExtensionRegistry;
24use MediaWiki\Auth\Hook\LocalUserCreatedHook;
25use MediaWiki\Config\Config;
26use MediaWiki\Hook\BeforePageDisplayHook;
27use MediaWiki\Hook\MakeGlobalVariablesScriptHook;
28use MediaWiki\Output\OutputPage;
29use MediaWiki\Preferences\Hook\GetPreferencesHook;
30use MediaWiki\ResourceLoader\Context;
31use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
32use MediaWiki\User\Hook\UserGetDefaultOptionsHook;
33use MediaWiki\User\Options\UserOptionsManager;
34use MediaWiki\User\User;
35use Psr\Log\LoggerInterface;
36use Skin;
37
38/**
39 * Hooks definitions for Popups extension
40 *
41 * @package Popups
42 */
43class PopupsHooks implements
44    GetPreferencesHook,
45    BeforePageDisplayHook,
46    ResourceLoaderGetConfigVarsHook,
47    MakeGlobalVariablesScriptHook,
48    UserGetDefaultOptionsHook,
49    LocalUserCreatedHook
50{
51
52    private const PREVIEWS_PREFERENCES_SECTION = 'rendering/reading';
53
54    /** @var Config */
55    private $config;
56
57    /** @var PopupsContext */
58    private $popupsContext;
59
60    /** @var LoggerInterface */
61    private $logger;
62
63    /** @var UserOptionsManager */
64    private $userOptionsManager;
65
66    /**
67     * @param Config $config
68     * @param PopupsContext $popupsContext
69     * @param LoggerInterface $logger
70     * @param UserOptionsManager $userOptionsManager
71     */
72    public function __construct(
73        Config $config,
74        PopupsContext $popupsContext,
75        LoggerInterface $logger,
76        UserOptionsManager $userOptionsManager
77    ) {
78        $this->config = $config;
79        $this->popupsContext = $popupsContext;
80        $this->logger = $logger;
81        $this->userOptionsManager = $userOptionsManager;
82    }
83
84    /**
85     * Get custom Popups types registered by extensions
86     * @param Context $context
87     * @return array
88     */
89    public static function getCustomPopupTypes( Context $context ): array {
90        // FIXME: If the module ext.cite.referencePreviews does not exist register reference previews.
91        // This code can be removed once T355194 is complete.
92        $others = $context->getResourceLoader()->getModule( 'ext.cite.referencePreviews' ) ?
93            [] : [ 'ext.popups.referencePreviews' ];
94
95        return array_merge( ExtensionRegistry::getInstance()->getAttribute(
96            'PopupsPluginModules'
97        ), $others );
98    }
99
100    /**
101     * Add options to user Preferences page
102     *
103     * @param User $user User whose preferences are being modified
104     * @param array[] &$prefs Preferences description array, to be fed to a HTMLForm object
105     */
106    public function onGetPreferences( $user, &$prefs ) {
107        if ( !$this->popupsContext->showPreviewsOptInOnPreferencesPage() ) {
108            return;
109        }
110
111        $skinPosition = array_search( 'skin', array_keys( $prefs ) );
112        $readingOptions = self::getPagePreviewPrefToggle( $user, $this->popupsContext );
113
114        if ( $this->config->get( 'PopupsReferencePreviews' ) ) {
115            $readingOptions = array_merge(
116                $readingOptions,
117                self::getReferencePreviewPrefToggle( $user, $this->popupsContext )
118            );
119        }
120
121        if ( $skinPosition !== false ) {
122            $injectIntoIndex = $skinPosition + 1;
123            $prefs = array_slice( $prefs, 0, $injectIntoIndex, true )
124                + $readingOptions
125                + array_slice( $prefs, $injectIntoIndex, null, true );
126        } else {
127            $prefs += $readingOptions;
128        }
129    }
130
131    /**
132     * Get Page Preview option
133     *
134     * @param User $user User whose preferences are being modified
135     * @param PopupsContext $context
136     * @return array[]
137     */
138    private static function getPagePreviewPrefToggle( User $user, PopupsContext $context ) {
139        $option = [
140            'type' => 'toggle',
141            'label-message' => 'popups-prefs-optin',
142            'help-message' => 'popups-prefs-conflicting-gadgets-info',
143            'section' => self::PREVIEWS_PREFERENCES_SECTION
144        ];
145
146        if ( $context->conflictsWithNavPopupsGadget( $user ) ) {
147            $option[ 'disabled' ] = true;
148            $option[ 'help-message' ] = [ 'popups-prefs-disable-nav-gadgets-info',
149                'Special:Preferences#mw-prefsection-gadgets' ];
150        }
151
152        return [
153            PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME => $option
154        ];
155    }
156
157    /**
158     * Get Reference Preview option
159     *
160     * @param User $user User whose preferences are being modified
161     * @param PopupsContext $context
162     * @return array[]
163     */
164    private static function getReferencePreviewPrefToggle( User $user, PopupsContext $context ) {
165        $option = [
166            'type' => 'toggle',
167            'label-message' => 'popups-refpreview-user-preference-label',
168            'help-message' => 'popups-prefs-conflicting-gadgets-info',
169            'section' => self::PREVIEWS_PREFERENCES_SECTION
170        ];
171
172        $isNavPopupsGadgetEnabled = $context->conflictsWithNavPopupsGadget( $user );
173        $isRefTooltipsGadgetEnabled = $context->conflictsWithRefTooltipsGadget( $user );
174
175        if ( $isNavPopupsGadgetEnabled && $isRefTooltipsGadgetEnabled ) {
176            $option[ 'disabled' ] = true;
177            $option[ 'help-message' ] = [ 'popups-prefs-reftooltips-and-navpopups-gadget-conflict-info',
178                'Special:Preferences#mw-prefsection-gadgets' ];
179        } elseif ( $isNavPopupsGadgetEnabled ) {
180            $option[ 'disabled' ] = true;
181            $option[ 'help-message' ] = [ 'popups-prefs-navpopups-gadget-conflict-info',
182                'Special:Preferences#mw-prefsection-gadgets' ];
183        } elseif ( $isRefTooltipsGadgetEnabled ) {
184            $option[ 'disabled' ] = true;
185            $option[ 'help-message' ] = [ 'popups-prefs-reftooltips-gadget-conflict-info',
186                'Special:Preferences#mw-prefsection-gadgets' ];
187        }
188
189        return [
190            PopupsContext::REFERENCE_PREVIEWS_PREFERENCE_NAME => $option
191        ];
192    }
193
194    /**
195     * Allows last minute changes to the output page, e.g. adding of CSS or JavaScript by extensions.
196     *
197     * @param OutputPage $out The Output page object
198     * @param Skin $skin Skin object that will be used to generate the page
199     */
200    public function onBeforePageDisplay( $out, $skin ): void {
201        if ( $this->popupsContext->isTitleExcluded( $out->getTitle() ) ) {
202            return;
203        }
204
205        if ( !$this->popupsContext->areDependenciesMet() ) {
206            $this->logger->error( 'Popups requires the PageImages extensions.
207                TextExtracts extension is required when using mwApiPlain gateway.' );
208            return;
209        }
210
211        $user = $out->getUser();
212        if ( $this->popupsContext->shouldSendModuleToUser( $user ) ) {
213            $out->addModules( [ 'ext.popups' ] );
214        }
215    }
216
217    /**
218     * Hook handler for the ResourceLoaderStartUpModule that makes static configuration visible to
219     * the frontend. These variables end in the only "startup" ResourceLoader module that is loaded
220     * before all others.
221     *
222     * Dynamic configuration that depends on the context needs to be published via the
223     * MakeGlobalVariablesScript hook.
224     *
225     * @param array &$vars Array of variables to be added into the output of the startup module
226     * @param string $skin
227     * @param Config $config
228     */
229    public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void {
230        $vars['wgPopupsVirtualPageViews'] = $this->config->get( 'PopupsVirtualPageViews' );
231        $vars['wgPopupsGateway'] = $this->config->get( 'PopupsGateway' );
232        $vars['wgPopupsRestGatewayEndpoint'] = $this->config->get( 'PopupsRestGatewayEndpoint' );
233        $vars['wgPopupsStatsvSamplingRate'] = $this->config->get( 'PopupsStatsvSamplingRate' );
234        $vars['wgPopupsTextExtractsIntroOnly'] = $this->config->get( 'PopupsTextExtractsIntroOnly' );
235    }
236
237    /**
238     * Hook handler publishing dynamic configuration that depends on the context, e.g. the page or
239     * the users settings. These variables end in an inline <script> in the documents head.
240     *
241     * Variables added:
242     * * `wgPopupsReferencePreviews' - The server's notion of whether or not the reference
243     *   previews should be enabled. Depending on the general setting done on the wiki.
244     * * `wgPopupsConflictsWithNavPopupGadget' - The server's notion of whether or not the
245     *   user has enabled conflicting Navigational Popups Gadget.
246     * * `wgPopupsConflictsWithRefTooltipsGadget' - The server's notion of whether or not the
247     *   user has enabled conflicting Reference Tooltips Gadget.
248     *
249     * @param array &$vars variables to be added into the output of OutputPage::headElement
250     * @param \IContextSource $out OutputPage instance calling the hook
251     */
252    public function onMakeGlobalVariablesScript( &$vars, $out ): void {
253        $vars['wgPopupsFlags'] = $this->popupsContext->getConfigBitmaskFromUser( $out->getUser() );
254    }
255
256    /**
257     * Called whenever a user wants to reset their preferences.
258     *
259     * @param array &$defaultOptions
260     */
261    public function onUserGetDefaultOptions( &$defaultOptions ) {
262        $default = $this->config->get( 'PopupsOptInDefaultState' );
263        $defaultOptions[PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME] = $default;
264
265        if ( $this->config->get( 'PopupsReferencePreviews' ) ) {
266            $defaultOptions[PopupsContext::REFERENCE_PREVIEWS_PREFERENCE_NAME] = '1';
267        }
268    }
269
270    /**
271     * Called one time when initializing a users preferences for a newly created account.
272     *
273     * @param User $user Newly created user object
274     * @param bool $isAutoCreated
275     */
276    public function onLocalUserCreated( $user, $isAutoCreated ) {
277        $default = $this->config->get( 'PopupsOptInStateForNewAccounts' );
278        $this->userOptionsManager->setOption(
279            $user,
280            PopupsContext::PREVIEWS_OPTIN_PREFERENCE_NAME,
281            $default
282        );
283
284        if ( $this->config->get( 'PopupsReferencePreviews' ) ) {
285            $this->userOptionsManager->setOption(
286                $user,
287                PopupsContext::REFERENCE_PREVIEWS_PREFERENCE_NAME,
288                $default
289            );
290        }
291    }
292}