Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
MakeGlobalVariablesScriptHookHandler
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
3 / 3
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onMakeGlobalVariablesScript
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
1 / 1
15
 addHCaptchaModules
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare( strict_types=1 );
4
5namespace MediaWiki\Extension\ConfirmEdit\Hooks\Handlers;
6
7use MediaWiki\Config\Config;
8use MediaWiki\Extension\ConfirmEdit\hCaptcha\HCaptcha;
9use MediaWiki\Extension\ConfirmEdit\Services\CaptchaFactory;
10use MediaWiki\Extension\VisualEditor\Services\VisualEditorAvailabilityLookup;
11use MediaWiki\Output\Hook\MakeGlobalVariablesScriptHook;
12use MediaWiki\Output\OutputPage;
13use MediaWiki\Registration\ExtensionRegistry;
14use MobileContext;
15
16/**
17 * Adds a JavaScript configuration variable that indicates what kind of captcha is required to be completed for
18 * editing the page that the user is viewing or editing. This is only the generic sense, as the specific content
19 * of the edit may cause a captcha to appear at save time.
20 *
21 * Used by the VisualEditor and MobileFrontend integrations to determine if they need to display hCaptcha to the
22 * user.
23 */
24class MakeGlobalVariablesScriptHookHandler implements MakeGlobalVariablesScriptHook {
25
26    public function __construct(
27        private readonly ExtensionRegistry $extensionRegistry,
28        private readonly Config $config,
29        private readonly CaptchaFactory $captchaFactory,
30        private readonly ?VisualEditorAvailabilityLookup $visualEditorAvailabilityLookup = null,
31        private readonly ?MobileContext $mobileContext = null
32    ) {
33    }
34
35    /** @inheritDoc */
36    public function onMakeGlobalVariablesScript( &$vars, $out ): void {
37        if ( !$out->canUseWikiPage() ) {
38            $vars['wgConfirmEditCaptchaNeededForGenericEdit'] = false;
39            return;
40        }
41
42        $mobileFrontendAvailable = $this->extensionRegistry->isLoaded( 'MobileFrontend' );
43        $visualEditorAvailable = $this->extensionRegistry->isLoaded( 'VisualEditor' );
44
45        if ( $visualEditorAvailable && $this->visualEditorAvailabilityLookup !== null ) {
46            $visualEditorAvailable = $this->visualEditorAvailabilityLookup->isAvailable(
47                $out->getTitle(), $out->getRequest(), $out->getUser()
48            );
49        }
50
51        if ( $mobileFrontendAvailable && $this->mobileContext !== null ) {
52            $mobileFrontendAvailable = $this->mobileContext->shouldDisplayMobileView();
53        }
54
55        $captchaNeededForEdit = false;
56
57        $captchaInstance = $this->captchaFactory->getGlobalInstanceFromContext( $out );
58        if (
59            $captchaInstance->shouldCheck( $out->getWikiPage(), '', '', $out->getContext() )
60        ) {
61            $captchaNeededForEdit = strtolower( $captchaInstance->getName() );
62        }
63
64        $vars['wgConfirmEditCaptchaNeededForGenericEdit'] = $captchaNeededForEdit;
65        $vars['wgConfirmEditForceShowCaptcha'] = $captchaInstance->shouldForceShowCaptcha();
66
67        // instanceof check necessary for Phan to be happy
68        if ( $captchaNeededForEdit === 'hcaptcha' && $captchaInstance instanceof HCaptcha ) {
69            $vars['wgConfirmEditHCaptchaSiteKey'] = $captchaInstance->getSiteKeyForAction();
70        }
71
72        // Load hCaptcha unconditionally if in use as MobileFrontend doesn't reload
73        // and it's unknown on page load if something like AbuseFilter will require
74        // the module on server response.
75        $usingHCaptcha = strtolower( $captchaInstance->getName() ) === 'hcaptcha';
76
77        if (
78            $mobileFrontendAvailable &&
79            $this->config->get( 'HCaptchaEnabledInMobileFrontend' ) &&
80            $usingHCaptcha
81        ) {
82            if ( $this->extensionRegistry->isLoaded( 'Abuse Filter' ) ) {
83                $vars['wgConfirmEditMobileHCaptchaAbuseFilterEnabled'] = true;
84            }
85
86            $this->addHCaptchaModules( $out, $vars, true );
87        } elseif ( $visualEditorAvailable && $usingHCaptcha ) {
88            $this->addHCaptchaModules( $out, $vars, $mobileFrontendAvailable );
89        }
90    }
91
92    /**
93     * Adds the HCaptcha modules to the current page.
94     *
95     * If the $initMobileFrontendModules flag is set, this additionally adds a
96     * variable to the page that makes the MobileFrontend call additional
97     * initialization code.
98     *
99     * @param OutputPage $out Current page
100     * @param array &$vars Page variables
101     * @param bool $initMobileFrontendModules Whether to call MF-specific init code
102     * @return void
103     */
104    private function addHCaptchaModules(
105        OutputPage $out,
106        array &$vars,
107        bool $initMobileFrontendModules
108    ): void {
109        $requiredModule = 'ext.confirmEdit.hCaptcha';
110
111        if ( $initMobileFrontendModules ) {
112            $mfInitModulesKey = 'wgMobileFrontendSourceEditorInitializeModules';
113            $modulesToInit = $vars[$mfInitModulesKey] ?? [];
114
115            if ( !in_array( $requiredModule, $modulesToInit ) ) {
116                $modulesToInit[] = $requiredModule;
117            }
118
119            $vars[$mfInitModulesKey] = $modulesToInit;
120        }
121
122        $out->addModules( $requiredModule );
123    }
124}