Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
WelcomeSurveyLogger
0.00% covered (danger)
0.00%
0 / 38
0.00% covered (danger)
0.00%
0 / 4
132
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initialize
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 logInteraction
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
72
 getLoggedActions
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\EventLogging;
4
5use GrowthExperiments\Specials\SpecialWelcomeSurvey;
6use MediaWiki\Extension\EventLogging\EventLogging;
7use MediaWiki\Registration\ExtensionRegistry;
8use MediaWiki\Request\WebRequest;
9use MediaWiki\User\User;
10use Psr\Log\LoggerInterface;
11
12class WelcomeSurveyLogger {
13
14    public const SCHEMA = '/analytics/mediawiki/welcomesurvey/interaction/1.0.1';
15    private const STREAM_NAME = 'mediawiki.welcomesurvey.interaction';
16    public const INTERACTION_PHASE_COOKIE = 'growth.welcomesurvey.phase';
17    public const WELCOME_SURVEY_TOKEN = 'growth.welcomesurvey.token';
18    public const WELCOME_SURVEY_LOGGED_OUT = 'logged_out';
19
20    /** @var LoggerInterface */
21    private $logger;
22    /** @var WebRequest */
23    private $webRequest;
24    /** @var bool */
25    private $isMobile;
26    /** @var User */
27    private $user;
28    /** @var array */
29    private $loggedActions = [];
30
31    /**
32     * @param LoggerInterface $logger
33     */
34    public function __construct( LoggerInterface $logger ) {
35        $this->logger = $logger;
36    }
37
38    /**
39     * @param WebRequest $webRequest
40     * @param User $user
41     * @param bool $isMobile
42     */
43    public function initialize( WebRequest $webRequest, User $user, bool $isMobile ): void {
44        $this->webRequest = $webRequest;
45        $this->user = $user;
46        $this->isMobile = $isMobile;
47    }
48
49    /**
50     * Log user interactions with the WelcomeSurvey form.
51     *
52     * @param string $action
53     */
54    public function logInteraction( string $action ): void {
55        $event = [
56            '$schema' => self::SCHEMA,
57            'action' => $action,
58            'is_mobile' => $this->isMobile,
59            'was_posted' => $this->webRequest->wasPosted(),
60            'user_id' => $this->user->getId(),
61            'token' => $this->webRequest->getVal( '_welcomesurveytoken' ),
62            'returnto_param_is_present' => ( $this->webRequest->getVal( '_returnto' ) ??
63                $this->webRequest->getVal( 'returnto' ) ) !== null,
64        ];
65        // Used for integration testing, see SpecialWelcomeSurveyTest.php.
66        $this->loggedActions[] = $event;
67        // Used to keep track of whether the user is logged out when submitting the form. See
68        // WelcomeSurveyHooks::onSpecialPageBeforeExecute
69        $this->webRequest->response()->setCookie( self::INTERACTION_PHASE_COOKIE, $action );
70        // Except that if the user reached handleResponses() and is logged in, we'll assume submission worked,
71        // and we can delete the cookie.
72        if ( $this->user->isNamed() && $action === SpecialWelcomeSurvey::ACTION_SUBMIT_SUCCESS ) {
73            $this->webRequest->response()->clearCookie( self::INTERACTION_PHASE_COOKIE );
74        }
75
76        // Suspicious events:
77        // * no token in the event
78        // * user is logged out
79        // * the stored token cookie doesn't match what we got from the web request
80        if ( !$event['token'] ||
81            $event['user_id'] === 0 ||
82            $this->webRequest->getCookie( self::WELCOME_SURVEY_TOKEN ) !== $event['token'] ||
83            $action === self::WELCOME_SURVEY_LOGGED_OUT
84        ) {
85            $this->logger->debug( 'Suspicious {schema} event for action {action}', [
86                'schema' => self::SCHEMA,
87                'action' => $action,
88                'user_id' => $event['user_id'],
89                'was_posted' => $event['was_posted'],
90                'token' => $event['token'],
91                'token_from_cookie' => $this->webRequest->getCookie( self::WELCOME_SURVEY_TOKEN ),
92                'is_mobile' => $event['is_mobile'],
93                'returnto_param_is_present' => $event['returnto_param_is_present'],
94                'interaction_phase_from_cookie' => $this->webRequest->getCookie( self::INTERACTION_PHASE_COOKIE ),
95            ] );
96        }
97        if ( !ExtensionRegistry::getInstance()->isLoaded( 'EventLogging' ) ) {
98            return;
99        }
100
101        // Make sure that the token is a string value for event logging validation.
102        $event['token'] ??= $this->webRequest->getCookie( self::WELCOME_SURVEY_TOKEN, null, '' );
103
104        EventLogging::submit( self::STREAM_NAME, $event );
105    }
106
107    /**
108     * @internal Used for integration testing.
109     * @return array
110     */
111    public function getLoggedActions(): array {
112        return $this->loggedActions;
113    }
114
115}