Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
WelcomeSurveyHooks
0.00% covered (danger)
0.00%
0 / 107
0.00% covered (danger)
0.00%
0 / 12
2550
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 onSpecialPage_initList
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isWelcomeSurveyEnabled
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isEditing
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 userWasEditing
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 onSpecialPageBeforeExecute
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
110
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
42
 onLocalUserCreated
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 onCentralAuthPostLoginRedirect
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
42
 onPostLoginRedirect
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
30
 shouldShowWelcomeSurvey
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2// phpcs:disable MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName
3
4namespace GrowthExperiments;
5
6use GrowthExperiments\EventLogging\WelcomeSurveyLogger;
7use GrowthExperiments\NewcomerTasks\CampaignConfig;
8use GrowthExperiments\Specials\SpecialWelcomeSurvey;
9use MediaWiki\Auth\Hook\LocalUserCreatedHook;
10use MediaWiki\Config\Config;
11use MediaWiki\Context\DerivativeContext;
12use MediaWiki\Context\IContextSource;
13use MediaWiki\Context\RequestContext;
14use MediaWiki\Hook\PostLoginRedirectHook;
15use MediaWiki\Logger\LoggerFactory;
16use MediaWiki\Output\Hook\BeforePageDisplayHook;
17use MediaWiki\Preferences\Hook\GetPreferencesHook;
18use MediaWiki\Registration\ExtensionRegistry;
19use MediaWiki\SpecialPage\Hook\SpecialPage_initListHook;
20use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook;
21use MediaWiki\SpecialPage\SpecialPageFactory;
22use MediaWiki\Specials\SpecialCreateAccount;
23use MediaWiki\Specials\SpecialUserLogin;
24use MediaWiki\Title\Title;
25use MediaWiki\Title\TitleFactory;
26use MediaWiki\User\User;
27
28class WelcomeSurveyHooks implements
29    GetPreferencesHook,
30    LocalUserCreatedHook,
31    PostLoginRedirectHook,
32    SpecialPage_initListHook,
33    SpecialPageBeforeExecuteHook,
34    BeforePageDisplayHook
35{
36
37    private Config $config;
38    private TitleFactory $titleFactory;
39    private SpecialPageFactory $specialPageFactory;
40    private WelcomeSurveyFactory $welcomeSurveyFactory;
41    private CampaignConfig $campaignConfig;
42
43    /**
44     * @param Config $config
45     * @param TitleFactory $titleFactory
46     * @param SpecialPageFactory $specialPageFactory
47     * @param WelcomeSurveyFactory $welcomeSurveyFactory
48     * @param CampaignConfig $campaignConfig
49     */
50    public function __construct(
51        Config $config,
52        TitleFactory $titleFactory,
53        SpecialPageFactory $specialPageFactory,
54        WelcomeSurveyFactory $welcomeSurveyFactory,
55        CampaignConfig $campaignConfig
56    ) {
57        $this->config = $config;
58        $this->titleFactory = $titleFactory;
59        $this->specialPageFactory = $specialPageFactory;
60        $this->welcomeSurveyFactory = $welcomeSurveyFactory;
61        $this->campaignConfig = $campaignConfig;
62    }
63
64    /**
65     * Register WelcomeSurvey special page.
66     *
67     * @param array &$list
68     */
69    public function onSpecialPage_initList( &$list ) {
70        if ( $this->isWelcomeSurveyEnabled() ) {
71            $list[ 'WelcomeSurvey' ] = function () {
72                return new SpecialWelcomeSurvey(
73                    $this->specialPageFactory,
74                    $this->welcomeSurveyFactory,
75                    new WelcomeSurveyLogger(
76                        LoggerFactory::getInstance( 'GrowthExperiments' )
77                    )
78                );
79            };
80        }
81    }
82
83    /**
84     * Register preference to save the Welcome survey responses.
85     *
86     * @param User $user
87     * @param array &$preferences
88     */
89    public function onGetPreferences( $user, &$preferences ) {
90        if ( $this->isWelcomeSurveyEnabled() ) {
91            $preferences[WelcomeSurvey::SURVEY_PROP] = [
92                'type' => 'api',
93            ];
94        }
95    }
96
97    private function isWelcomeSurveyEnabled() {
98        return $this->config->get( 'WelcomeSurveyEnabled' );
99    }
100
101    /**
102     * Check if a given title + query string means some kind of editor is open.
103     * @param Title|null $title
104     * @param array|null $query
105     * @return bool
106     */
107    private function isEditing( ?Title $title, ?array $query = null ): bool {
108        return $title && $title->canExist() && (
109            // normal editor, VE with some settings
110            ( $query['action'] ?? null ) === 'edit'
111            // VE
112            || ( $query['veaction'] ?? null ) === 'edit'
113            // mobile editor
114            || str_starts_with( $title->getFragment(), '/editor/' )
115        );
116    }
117
118    /**
119     * True if the user started the registration process while in the middle of editing.
120     * @param string|null $returnTo returnto parameter. Read from URL if omitted.
121     * @param string|string[]|null $returnToQuery returntoquery parameter. Read from URL if omitted.
122     * @return bool
123     */
124    private function userWasEditing( ?string $returnTo = null, $returnToQuery = null ): bool {
125        $context = RequestContext::getMain();
126        $returnTo ??= $context->getRequest()->getText( 'returnto' );
127        $returntoTitle = ( $returnTo !== '' ) ? $this->titleFactory->newFromText( $returnTo ) : null;
128        if ( $returnToQuery === null ) {
129            $returnToQuery = wfCgiToArray( $context->getRequest()->getText( 'returntoquery' ) );
130        } elseif ( is_string( $returnToQuery ) ) {
131            $returnToQuery = wfCgiToArray( $returnToQuery );
132        }
133        return $this->isEditing( $returntoTitle, $returnToQuery );
134    }
135
136    /** @inheritDoc */
137    public function onSpecialPageBeforeExecute( $special, $subPage ) {
138        $context = $special->getContext();
139        $user = $context->getUser();
140        if ( $special instanceof SpecialUserLogin && $user->isAnon() ) {
141            $request = $context->getRequest();
142            if ( $user->isAnon() && $request->getCookie( WelcomeSurveyLogger::INTERACTION_PHASE_COOKIE ) ) {
143                $welcomeSurveyLogger = new WelcomeSurveyLogger( LoggerFactory::getInstance( 'GrowthExperiments' ) );
144                $welcomeSurveyLogger->initialize( $request, $user, Util::isMobile( $context->getSkin() ) );
145                $welcomeSurveyLogger->logInteraction( WelcomeSurveyLogger::WELCOME_SURVEY_LOGGED_OUT );
146            }
147        } elseif (
148            $special instanceof SpecialCreateAccount
149            && $user->isAnon() && $this->userWasEditing()
150            && !Util::isMobile( $context->getSkin() )
151            && $this->shouldShowWelcomeSurvey( $context )
152        ) {
153            $context->getOutput()->addModules( 'ext.growthExperiments.MidEditSignup' );
154            $context->getOutput()->addJsConfigVars( 'wgGEMidEditSignup', true );
155        }
156    }
157
158    /** @inheritDoc */
159    public function onBeforePageDisplay( $out, $skin ): void {
160        if ( $out->getRequest()->getCookie( 'ge.midEditSignup' )
161            && !Util::isMobile( $skin )
162            // maybe the user filled out or dismissed the survey in another tab, don't show then
163            && $this->welcomeSurveyFactory->newWelcomeSurvey( $out->getContext() )->isUnfinished()
164            && (
165                // Check if we are post-edit, somewhat relying on \MediaWiki\EditPage\EditPage internals.
166                // There isn't a good way to do that; between trying to check the dynamically named
167                // postedit cookie and looking for the JS variable Article::show() sets based on
168                // that cookie, this is the less painful one.
169                ( $out->getJsConfigVars()['wgPostEdit'] ?? false )
170                // Also load the module if the editor is open, as some editors save without
171                // reloading the page.
172                || $this->isEditing( $out->getTitle(), $out->getRequest()->getQueryValues() )
173            )
174        ) {
175            $out->addModules( 'ext.growthExperiments.MidEditSignup' );
176        }
177    }
178
179    /** @inheritDoc */
180    public function onLocalUserCreated( $user, $autocreated ) {
181        if ( $user->isTemp() ) {
182            return;
183        }
184        $context = new DerivativeContext( RequestContext::getMain() );
185        $context->setUser( $user );
186        if ( $autocreated || !$this->shouldShowWelcomeSurvey( $context ) ) {
187            return;
188        }
189        $welcomeSurvey = $this->welcomeSurveyFactory->newWelcomeSurvey( $context );
190        $group = $welcomeSurvey->getGroup();
191        $welcomeSurvey->saveGroup( $group );
192    }
193
194    /** @inheritDoc */
195    public function onCentralAuthPostLoginRedirect(
196        string &$returnTo, string &$returnToQuery, bool $stickHTTPS, string $type, string &$injectedHtml
197    ) {
198        $context = RequestContext::getMain();
199        if ( $type !== 'signup'
200            || !$this->shouldShowWelcomeSurvey( $context )
201        ) {
202            return;
203        }
204
205        $welcomeSurvey = $this->welcomeSurveyFactory->newWelcomeSurvey( $context );
206        $group = $welcomeSurvey->getGroup();
207        if ( $group === false ) {
208            return;
209        }
210
211        if ( $this->userWasEditing( $returnTo, $returnToQuery ) ) {
212            return;
213        }
214
215        $oldReturnTo = $returnTo;
216        $oldReturnToQuery = $returnToQuery;
217        $returnToQueryArray = $welcomeSurvey->getRedirectUrlQuery( $group, $oldReturnTo, $oldReturnToQuery );
218        if ( $returnToQueryArray === false ) {
219            return;
220        }
221
222        $returnTo = $this->specialPageFactory->getTitleForAlias( 'WelcomeSurvey' )->getPrefixedText();
223        $returnToQuery = wfArrayToCgi( $returnToQueryArray );
224        $injectedHtml = '';
225        return false;
226    }
227
228    /** @inheritDoc */
229    public function onPostLoginRedirect( &$returnTo, &$returnToQuery, &$type ) {
230        $context = RequestContext::getMain();
231        if ( $type !== 'signup'
232             // handled by onCentralAuthPostLoginRedirect
233            || ExtensionRegistry::getInstance()->isLoaded( 'CentralAuth' )
234            || !$this->shouldShowWelcomeSurvey( $context )
235        ) {
236            return;
237        }
238
239        $welcomeSurvey = $this->welcomeSurveyFactory->newWelcomeSurvey( $context );
240        $group = $welcomeSurvey->getGroup();
241        $welcomeSurvey->saveGroup( $group );
242
243        if ( $this->userWasEditing( $returnTo, $returnToQuery ) ) {
244            return;
245        }
246
247        $oldReturnTo = $returnTo;
248        $oldReturnToQuery = $returnToQuery;
249
250        $returnTo = $this->specialPageFactory->getTitleForAlias( 'WelcomeSurvey' )->getPrefixedText();
251        $returnToQuery = $welcomeSurvey->getRedirectUrlQuery( $group, $oldReturnTo, wfArrayToCgi( $oldReturnToQuery ) );
252        $type = 'successredirect';
253        return false;
254    }
255
256    /**
257     * @param IContextSource $context
258     * @return bool
259     */
260    private function shouldShowWelcomeSurvey( IContextSource $context ): bool {
261        return $this->isWelcomeSurveyEnabled()
262            && !$context->getUser()->isTemp()
263            && HomepageHooks::getGrowthFeaturesOptInOptOutOverride() !== HomepageHooks::GROWTH_FORCE_OPTOUT
264            && !VariantHooks::shouldCampaignSkipWelcomeSurvey(
265                VariantHooks::getCampaign( $context ), $this->campaignConfig
266            );
267    }
268
269}