Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
4.27% covered (danger)
4.27%
5 / 117
12.50% covered (danger)
12.50%
1 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Hooks
4.27% covered (danger)
4.27%
5 / 117
12.50% covered (danger)
12.50%
1 / 8
1742.25
0.00% covered (danger)
0.00%
0 / 1
 getConfig
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isConfiguredCorrectly
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
6
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
156
 loadForUser
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
7.46
 onResourceLoaderRegisterModules
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
12
 onEditPage__showEditForm_initial
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
132
 makeCentralLink
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
56
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 *
17 * @file
18 * @author Szymon Ćšwierkosz
19 * @author Kunal Mehta
20 */
21
22namespace MediaWiki\GlobalCssJs;
23
24use MediaWiki\Config\Config;
25use MediaWiki\EditPage\EditPage;
26use MediaWiki\Hook\BeforePageDisplayHook;
27use MediaWiki\Hook\EditPage__showEditForm_initialHook;
28use MediaWiki\Linker\Linker;
29use MediaWiki\MediaWikiServices;
30use MediaWiki\Output\OutputPage;
31use MediaWiki\Preferences\Hook\GetPreferencesHook;
32use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook;
33use MediaWiki\ResourceLoader\ResourceLoader;
34use MediaWiki\Title\Title;
35use MediaWiki\User\User;
36use MediaWiki\User\UserIdentity;
37use MediaWiki\WikiMap\WikiMap;
38use RequestContext;
39use Skin;
40
41//phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
42class Hooks implements
43    BeforePageDisplayHook,
44    ResourceLoaderRegisterModulesHook,
45    EditPage__showEditForm_initialHook,
46    GetPreferencesHook
47{
48
49    /**
50     * @return Config
51     */
52    private static function getConfig(): Config {
53        return MediaWikiServices::getInstance()->getConfigFactory()->makeConfig( 'globalcssjs' );
54    }
55
56    /**
57     * Helper function for checking whether the extension has been configured correctly.
58     *
59     * @param array $config
60     * @return bool
61     */
62    private static function isConfiguredCorrectly( $config ) {
63        return !( $config['wiki'] === false || $config['source'] === false );
64    }
65
66    /**
67     * Handler for BeforePageDisplay hook.
68     *
69     * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay
70     * @param OutputPage $out
71     * @param Skin $skin
72     */
73    public function onBeforePageDisplay( $out, $skin ): void {
74        $config = self::getConfig();
75        $useSiteCssJs = $config->get( 'UseGlobalSiteCssJs' );
76        $globalCssJsConfig = $config->get( 'GlobalCssJsConfig' );
77
78        if ( !self::isConfiguredCorrectly( $globalCssJsConfig ) ) {
79            // Not configured yet, don't register any modules.
80            return;
81        }
82
83        $out->addModuleStyles( [ 'ext.globalCssJs.user.styles' ] );
84        $out->addModules( [ 'ext.globalCssJs.user' ] );
85        if ( $useSiteCssJs ) {
86            $out->addModuleStyles( [ 'ext.globalCssJs.site.styles' ] );
87            $out->addModules( [ 'ext.globalCssJs.site' ] );
88        }
89
90        // Add help link
91        $rlConfig = $config->get( 'GlobalCssJsConfig' );
92        if ( $rlConfig['wiki'] === WikiMap::getCurrentWikiId() ) {
93            $title = $out->getTitle();
94            $user = $out->getUser();
95            $name = $user->getName();
96            if ( $useSiteCssJs && $title->inNamespace( NS_MEDIAWIKI )
97                && ( $title->getText() === 'Global.css' || $title->getText() === 'Global.js' )
98            ) {
99                $out->addHelpLink( 'Help:Extension:GlobalCssJs' );
100            } elseif ( $user->isRegistered() && $title->inNamespace( NS_USER )
101                && ( $title->getText() === "$name/global.js" || $title->getText() === "$name/global.css" )
102            ) {
103                $out->addHelpLink( 'Help:Extension:GlobalCssJs' );
104            }
105        }
106    }
107
108    /**
109     * Given a user, should we load scripts for them?
110     *
111     * @param UserIdentity $user
112     * @return bool
113     */
114    public static function loadForUser( UserIdentity $user ): bool {
115        $config = self::getConfig()->get( 'GlobalCssJsConfig' );
116        $wiki = $config['wiki'];
117        if ( $wiki === WikiMap::getCurrentWikiId() ) {
118            return true;
119        }
120        if ( $wiki === false ) {
121            // Not configured, don't load anything
122            return false;
123        }
124
125        $lookup = MediaWikiServices::getInstance()
126            ->getCentralIdLookupFactory()
127            ->getLookup();
128        return $lookup->isAttached( $user ) && $lookup->isAttached( $user, $wiki );
129    }
130
131    /**
132     * Handler for ResourceLoaderRegisterModules hook.
133     *
134     * @see https://www.mediawiki.org/wiki/Manual:Hooks/ResourceLoaderRegisterModules
135     * @param ResourceLoader $resourceLoader
136     */
137    public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void {
138        $config = self::getConfig()->get( 'GlobalCssJsConfig' );
139
140        if ( !self::isConfiguredCorrectly( $config ) ) {
141            // Not configured yet, don't register any modules.
142            return;
143        }
144
145        $userJs = [
146            'class' => ResourceLoaderGlobalUserModule::class,
147            'type' => 'script',
148        ] + $config;
149        $resourceLoader->register( 'ext.globalCssJs.user', $userJs );
150
151        $userCss = [
152            'class' => ResourceLoaderGlobalUserModule::class,
153            'type' => 'style',
154        ] + $config;
155        $resourceLoader->register( 'ext.globalCssJs.user.styles', $userCss );
156
157        if ( self::getConfig()->get( 'UseGlobalSiteCssJs' ) ) {
158            $siteJs = [
159                'class' => ResourceLoaderGlobalSiteModule::class,
160                'type' => 'script',
161            ] + $config;
162            $resourceLoader->register( 'ext.globalCssJs.site', $siteJs );
163
164            $siteCss = [
165                'class' => ResourceLoaderGlobalSiteModule::class,
166                'type' => 'style',
167            ] + $config;
168            $resourceLoader->register( 'ext.globalCssJs.site.styles', $siteCss );
169        }
170    }
171
172    /**
173     * Handler for 'EditPage::showEditForm:initial' hook.
174     *
175     * @see https://www.mediawiki.org/wiki/Manual:Hooks/EditPage::showEditForm:initial
176     * @param EditPage $editPage
177     * @param OutputPage $output
178     */
179    public function onEditPage__showEditForm_initial( $editPage, $output ) {
180        $gcssjsConfig = self::getConfig()->get( 'GlobalCssJsConfig' );
181        $config = $output->getConfig();
182        $user = $output->getUser();
183        $title = $editPage->getTitle();
184        if ( $gcssjsConfig['wiki'] === WikiMap::getCurrentWikiId() && $user->isRegistered()
185            && $editPage->formtype == 'initial' && $title->isUserConfigPage()
186        ) {
187            $title = $editPage->getTitle();
188            $name = $user->getName();
189            if ( $config->get( 'AllowUserJs' ) && $title->isUserJsConfigPage() &&
190                $title->getText() == $name . '/global.js'
191            ) {
192                $msg = 'globalcssjs-warning-js';
193            } elseif ( $config->get( 'AllowUserCss' ) && $title->isUserCssConfigPage() &&
194                $title->getText() == $name . '/global.css'
195            ) {
196                $msg = 'globalcssjs-warning-css';
197            } else {
198                // CSS or JS page, but not a global one
199                return;
200            }
201            $output->wrapWikiMsg( "<div id='mw-$msg'>\n$1\n</div>", [ $msg ] );
202        }
203    }
204
205    /**
206     * Convenince function to make a link to page that might be on another site.
207     *
208     * @param Title $title
209     * @param string $msg message key
210     * @return string HTMl link
211     * @suppress SecurityCheck-DoubleEscaped phan false positive
212     */
213    protected static function makeCentralLink( Title $title, string $msg ): string {
214        $config = self::getConfig()->get( 'GlobalCssJsConfig' );
215        $message = wfMessage( $msg );
216        if ( $config['wiki'] === WikiMap::getCurrentWikiId() ) {
217            return MediaWikiServices::getInstance()->getLinkRenderer()->makeLink( $title, $message->text() );
218        } elseif ( isset( $config['baseurl'] ) && $config['baseurl'] !== false ) {
219            return Linker::makeExternalLink(
220                // bug 66873, don't use localized namespace
221                $config['baseurl'] . '/User:' .
222                    htmlspecialchars( $title->getText(), ENT_QUOTES ),
223                $message->escaped()
224            );
225        } else {
226            return WikiMap::makeForeignLink(
227                $config['wiki'],
228                'User:' . $title->getText(), // bug 66873, don't use localized namespace
229                $message->escaped()
230            );
231        }
232    }
233
234    /**
235     * Handler for GetPreferences hook.
236     *
237     * @see https://www.mediawiki.org/wiki/Manual:Hooks/GetPreferences
238     * @param User $user
239     * @param array &$prefs
240     */
241    public function onGetPreferences( $user, &$prefs ) {
242        $ctx = RequestContext::getMain();
243        $allowUserCss = $ctx->getConfig()->get( 'AllowUserCss' );
244        $allowUserJs = $ctx->getConfig()->get( 'AllowUserJs' );
245
246        if ( !$allowUserCss && !$allowUserJs ) {
247            // No user CSS or JS allowed
248            return;
249        }
250
251        $safeMode = MediaWikiServices::getInstance()->getUserOptionsLookup()->getOption( $user, 'forcesafemode' );
252        if ( $safeMode ) {
253            // Safe mode is enabled
254            return;
255        }
256
257        if ( !self::loadForUser( $user ) ) {
258            // No global scripts for this user :(
259            return;
260        }
261        $userName = $user->getName();
262        $linkTools = [];
263        if ( $allowUserCss ) {
264            $cssPage = Title::makeTitleSafe( NS_USER, $userName . '/global.css' );
265            $linkTools[] = self::makeCentralLink( $cssPage, 'globalcssjs-custom-css' );
266        }
267        if ( $allowUserJs ) {
268            $jsPage = Title::makeTitleSafe( NS_USER, $userName . '/global.js' );
269            $linkTools[] = self::makeCentralLink( $jsPage, 'globalcssjs-custom-js' );
270        }
271
272        $prefs = wfArrayInsertAfter(
273            $prefs,
274            [ 'globalcssjs' => [
275                'type' => 'info',
276                'raw' => 'true',
277                'default' => $ctx->getLanguage()->pipeList( $linkTools ),
278                'label-message' => 'globalcssjs-custom-css-js',
279                'section' => 'rendering/skin',
280            ] ],
281            'commoncssjs'
282        );
283    }
284}