Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 11
870
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
 onResourceLoaderRegisterModules
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 onPreferencesGetLayout
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onFetchChangesList
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 onSpecialPageBeforeExecute
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
90
 onUserLogoutComplete
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onResourceLoaderGetConfigVars
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 onOutputPageBodyAttributes
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 onSkinPageReadyConfig
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 onDifferenceEngineViewHeader
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Minerva;
22
23use DifferenceEngine;
24use MediaWiki\Config\Config;
25use MediaWiki\Diff\Hook\DifferenceEngineViewHeaderHook;
26use MediaWiki\Hook\FetchChangesListHook;
27use MediaWiki\Hook\PreferencesGetLayoutHook;
28use MediaWiki\Hook\UserLogoutCompleteHook;
29use MediaWiki\Html\Html;
30use MediaWiki\Minerva\Skins\SkinMinerva;
31use MediaWiki\Output\Hook\OutputPageBodyAttributesHook;
32use MediaWiki\Output\OutputPage;
33use MediaWiki\Preferences\Hook\GetPreferencesHook;
34use MediaWiki\RecentChanges\ChangesList;
35use MediaWiki\RecentChanges\ChangesListFilterGroup;
36use MediaWiki\RecentChanges\OldChangesList;
37use MediaWiki\Registration\ExtensionRegistry;
38use MediaWiki\ResourceLoader\Context;
39use MediaWiki\ResourceLoader\Hook\ResourceLoaderGetConfigVarsHook;
40use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook;
41use MediaWiki\ResourceLoader\ResourceLoader;
42use MediaWiki\Skin\Skin;
43use MediaWiki\Skins\Hook\SkinPageReadyConfigHook;
44use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook;
45use MediaWiki\SpecialPage\SpecialPage;
46use MediaWiki\User\Options\UserOptionsLookup;
47use MediaWiki\User\User;
48use MobileContext;
49use Wikimedia\Rdbms\ConfiguredReadOnlyMode;
50
51/**
52 * Hook handlers for Minerva skin.
53 *
54 * Hook handler method names should be in the form of:
55 *    on<HookName>()
56 */
57class Hooks implements
58    DifferenceEngineViewHeaderHook,
59    FetchChangesListHook,
60    GetPreferencesHook,
61    OutputPageBodyAttributesHook,
62    PreferencesGetLayoutHook,
63    ResourceLoaderGetConfigVarsHook,
64    ResourceLoaderRegisterModulesHook,
65    SkinPageReadyConfigHook,
66    SpecialPageBeforeExecuteHook,
67    UserLogoutCompleteHook
68{
69    public const FEATURE_OVERFLOW_PAGE_ACTIONS = 'MinervaOverflowInPageActions';
70
71    private ConfiguredReadOnlyMode $configuredReadOnlyMode;
72    private SkinOptions $skinOptions;
73    private UserOptionsLookup $userOptionsLookup;
74    private ?MobileContext $mobileContext;
75
76    public function __construct(
77        ConfiguredReadOnlyMode $configuredReadOnlyMode,
78        SkinOptions $skinOptions,
79        UserOptionsLookup $userOptionsLookup,
80        ?MobileContext $mobileContext
81    ) {
82        $this->configuredReadOnlyMode = $configuredReadOnlyMode;
83        $this->skinOptions = $skinOptions;
84        $this->userOptionsLookup = $userOptionsLookup;
85        $this->mobileContext = $mobileContext;
86    }
87
88    /**
89     * ResourceLoaderRegisterModules hook handler.
90     *
91     * Registers:
92     *
93     * * EventLogging schema modules, if the EventLogging extension is loaded;
94     * * Modules for the Visual Editor overlay, if the VisualEditor extension is loaded; and
95     * * Modules for the notifications overlay, if the Echo extension is loaded.
96     *
97     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
98     *
99     * @param ResourceLoader $resourceLoader
100     */
101    public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void {
102        if ( !ExtensionRegistry::getInstance()->isLoaded( 'MobileFrontend' ) ) {
103            $resourceLoader->register( [
104                'mobile.startup' => [
105                    'dependencies' => [ 'mediawiki.searchSuggest' ],
106                    'localBasePath' => dirname( __DIR__ ),
107                    'remoteExtPath' => 'Minerva',
108                    'scripts' => 'resources/mobile.startup.stub.js',
109                ]
110            ] );
111        }
112    }
113
114    /**
115     * Adds Minerva-specific user preferences that can only be accessed via API
116     *
117     * @param User $user user whose preferences are being modified
118     * @param array[] &$prefs preferences description array, to be fed to a HTMLForm object
119     */
120    public function onGetPreferences( $user, &$prefs ): void {
121        $minervaPrefs = [
122            'minerva-theme' => [
123                'type' => 'api'
124            ],
125        ];
126
127        $prefs += $minervaPrefs;
128    }
129
130    /**
131     * PreferencesGetLayout hook handler.
132     *
133     * Use mobile layout in Special:Preferences
134     * @see https://www.mediawiki.org/wiki/Manual:Hooks/PreferencesGetLayout
135     *
136     * @param bool &$useMobileLayout
137     * @param string $skinName
138     * @param array $skinProperties
139     */
140    public function onPreferencesGetLayout( &$useMobileLayout, $skinName, $skinProperties = [] ) {
141        if ( $skinName === 'minerva' ) {
142            $useMobileLayout = true;
143        }
144    }
145
146    /**
147     * Disable recent changes enhanced mode (table mode)
148     * @see https://www.mediawiki.org/wiki/Manual:Hooks/FetchChangesList
149     *
150     * @param User $user
151     * @param Skin $skin
152     * @param ChangesList|null &$list
153     * @param ChangesListFilterGroup[] $groups
154     * @return bool|null
155     */
156    public function onFetchChangesList( $user, $skin, &$list, $groups ) {
157        if ( $skin->getSkinName() === 'minerva' ) {
158            // The new changes list (table-based) does not work with Minerva
159            $list = new OldChangesList( $skin->getContext(), $groups );
160            // returning false makes sure $list is used instead.
161            return false;
162        }
163    }
164
165    /**
166     * Invocation of hook SpecialPageBeforeExecute
167     *
168     * We use this hook to ensure that login/account creation pages
169     * are redirected to HTTPS if they are not accessed via HTTPS and
170     * $wgSecureLogin == true - but only when using the
171     * mobile site.
172     *
173     * @param SpecialPage $special
174     * @param string $subpage
175     */
176    public function onSpecialPageBeforeExecute( $special, $subpage ) {
177        $name = $special->getName();
178        if ( !in_array( $name, [ 'Recentchanges', 'Userlogin', 'CreateAccount' ] ) ) {
179            return;
180        }
181        $skin = $special->getSkin();
182        if ( !$skin instanceof SkinMinerva ) {
183            return;
184        }
185        $request = $special->getRequest();
186        if ( $name === 'Recentchanges' ) {
187            $isEnhancedDefaultForUser = $this->userOptionsLookup
188                ->getBoolOption( $special->getUser(), 'usenewrc' );
189            $enhanced = $request->getBool( 'enhanced', $isEnhancedDefaultForUser );
190            if ( $enhanced ) {
191                $out = $special->getOutput();
192                $out->addModuleStyles( 'mediawiki.codex.messagebox.styles' );
193                $out->addHTML( Html::warningBox(
194                    $special->msg( 'skin-minerva-recentchanges-warning-enhanced-not-supported' )->parse()
195                ) );
196            }
197        } else {
198            // Add default notice message to Special:UserLogin and Special:UserCreate
199            // if no warning or notice message is set.
200            if (
201                !$request->getCheck( 'warning' ) &&
202                !$request->getCheck( 'notice' ) &&
203                !$special->getUser()->isRegistered() &&
204                !$request->wasPosted()
205            ) {
206                $request->setVal( 'notice', 'mobile-frontend-generic-login-new' );
207            }
208        }
209    }
210
211    /**
212     * UserLogoutComplete hook handler.
213     * Resets skin options if a user logout occurs - this is necessary as the
214     * RequestContextCreateSkinMobile hook runs before the UserLogout hook.
215     *
216     * @param User $user
217     * @param string &$inject_html
218     * @param string $oldName
219     */
220    public function onUserLogoutComplete( $user, &$inject_html, $oldName ) {
221        if ( $this->mobileContext ) {
222            $this->skinOptions->setMinervaSkinOptions( $this->mobileContext, $this->mobileContext->getSkin() );
223        }
224    }
225
226    /**
227     * ResourceLoaderGetConfigVars hook handler.
228     * Used for setting JS variables which are pulled in dynamically with RL
229     * instead of embedded directly on the page with a script tag.
230     * These vars have a shorter cache-life than those in `getJsConfigVars`.
231     *
232     * @param array &$vars Array of variables to be added into the output of the RL startup module.
233     * @param string $skin
234     * @param Config $config
235     */
236    public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void {
237        if ( $skin === 'minerva' ) {
238            // This is to let the UI adjust itself to a wiki that is always read-only.
239            // Ignore temporary read-only on live wikis, requires heavy DB check (T233458).
240            $roConf = $this->configuredReadOnlyMode;
241            $vars += [
242                'wgMinervaABSamplingRate' => $config->get( 'MinervaABSamplingRate' ),
243                'wgMinervaReadOnly' => $roConf->isReadOnly(),
244            ];
245        }
246    }
247
248    /**
249     * Modifies the `<body>` element's attributes.
250     *
251     * By default, the `class` attribute is set to the output's "bodyClassName"
252     * property.
253     *
254     * @param OutputPage $out
255     * @param Skin $skin
256     * @param string[] &$bodyAttrs
257     */
258    public function onOutputPageBodyAttributes( $out, $skin, &$bodyAttrs ): void {
259        $classes = $out->getProperty( 'bodyClassName' );
260        $isMinerva = $skin instanceof SkinMinerva;
261
262        if ( $isMinerva && $this->skinOptions->get( SkinOptions::HISTORY_IN_PAGE_ACTIONS ) ) {
263            // Class is used when page actions is modified to contain more elements
264            $classes .= ' minerva--history-page-action-enabled';
265        }
266
267        if ( $isMinerva ) {
268            $bodyAttrs['class'] .= ' ' . $classes;
269        }
270    }
271
272    /**
273     * SkinPageReadyConfig hook handler
274     *
275     * @param Context $context
276     * @param mixed[] &$config Associative array of configurable options
277     */
278    public function onSkinPageReadyConfig(
279        Context $context,
280        array &$config
281    ): void {
282        if ( $context->getSkin() === 'minerva' ) {
283            $config['searchModule'] = 'skins.minerva.search';
284            // Enable collapsible styles on Minerva. Projects are already doing this via gadgets
285            // which creates an unpredictable testing environment so it is better to match production.
286            // NOTE: This is enabled despite the well documented problems with the current design on T111565.
287            $config['collapsible'] = true;
288            $config['selectorLogoutLink'] = 'a.menu__item--logout[data-mw="interface"]';
289        }
290    }
291
292    /**
293     * Force inline diffs on mobile site.
294     *
295     * @param DifferenceEngine $differenceEngine
296     */
297    public function onDifferenceEngineViewHeader( $differenceEngine ) {
298        $skin = $differenceEngine->getSkin();
299        if ( $skin->getSkinName() !== 'minerva' ) {
300            return;
301        }
302        $differenceEngine->setSlotDiffOptions( [
303            'diff-type' => 'inline',
304        ] );
305    }
306}