Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 128
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
HelpPanelHooks
0.00% covered (danger)
0.00%
0 / 128
0.00% covered (danger)
0.00%
0 / 11
1560
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 onUserGetDefaultOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 onResourceLoaderExcludeUserOptions
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 onLocalUserCreated
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
90
 onBeforePageDisplay
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 1
72
 getModuleData
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
12
 onListDefinedTags
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 onChangeTagsListActive
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getMentorData
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
30
 getPreferredEditor
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3namespace GrowthExperiments;
4
5use GrowthExperiments\Config\GrowthConfigLoaderStaticTrait;
6use GrowthExperiments\HelpPanel\QuestionPoster\HelpdeskQuestionPoster;
7use GrowthExperiments\HomepageModules\Mentorship;
8use GrowthExperiments\HomepageModules\SuggestedEdits;
9use GrowthExperiments\MentorDashboard\MentorTools\MentorStatusManager;
10use GrowthExperiments\Mentorship\MentorManager;
11use MediaWiki\Auth\Hook\LocalUserCreatedHook;
12use MediaWiki\Cache\GenderCache;
13use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook;
14use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook;
15use MediaWiki\Config\Config;
16use MediaWiki\Context\RequestContext;
17use MediaWiki\Language\Language;
18use MediaWiki\MediaWikiServices;
19use MediaWiki\Output\Hook\BeforePageDisplayHook;
20use MediaWiki\Preferences\Hook\GetPreferencesHook;
21use MediaWiki\ResourceLoader as RL;
22use MediaWiki\ResourceLoader\Hook\ResourceLoaderExcludeUserOptionsHook;
23use MediaWiki\User\Hook\UserGetDefaultOptionsHook;
24use MediaWiki\User\Options\UserOptionsManager;
25use MediaWiki\User\User;
26use MediaWiki\User\UserEditTracker;
27use MessageLocalizer;
28
29class HelpPanelHooks implements
30    GetPreferencesHook,
31    UserGetDefaultOptionsHook,
32    ResourceLoaderExcludeUserOptionsHook,
33    LocalUserCreatedHook,
34    BeforePageDisplayHook,
35    ListDefinedTagsHook,
36    ChangeTagsListActiveHook
37{
38    use GrowthConfigLoaderStaticTrait;
39
40    public const HELP_PANEL_PREFERENCES_TOGGLE = 'growthexperiments-help-panel-tog-help-panel';
41
42    private Config $config;
43    private Config $wikiConfig;
44    private GenderCache $genderCache;
45    private UserEditTracker $userEditTracker;
46    private UserOptionsManager $userOptionsManager;
47    private MentorManager $mentorManager;
48    private MentorStatusManager $mentorStatusManager;
49
50    /**
51     * @param Config $config
52     * @param Config $wikiConfig
53     * @param GenderCache $genderCache
54     * @param UserEditTracker $userEditTracker
55     * @param UserOptionsManager $userOptionsManager
56     * @param MentorManager $mentorManager
57     * @param MentorStatusManager $mentorStatusManager
58     */
59    public function __construct(
60        Config $config,
61        Config $wikiConfig,
62        GenderCache $genderCache,
63        UserEditTracker $userEditTracker,
64        UserOptionsManager $userOptionsManager,
65        MentorManager $mentorManager,
66        MentorStatusManager $mentorStatusManager
67    ) {
68        $this->config = $config;
69        $this->wikiConfig = $wikiConfig;
70        $this->genderCache = $genderCache;
71        $this->userEditTracker = $userEditTracker;
72        $this->userOptionsManager = $userOptionsManager;
73        $this->mentorManager = $mentorManager;
74        $this->mentorStatusManager = $mentorStatusManager;
75    }
76
77    /**
78     * Register preference to toggle help panel.
79     *
80     * @param User $user
81     * @param array &$preferences
82     */
83    public function onGetPreferences( $user, &$preferences ) {
84        if ( HelpPanel::isHelpPanelEnabled() ) {
85            $preferences[HelpdeskQuestionPoster::QUESTION_PREF] = [
86                'type' => 'api',
87            ];
88        }
89
90        // FIXME: Guidance doesn't need an opt-in anymore, let's remove this.
91        if ( SuggestedEdits::isGuidanceEnabledForAnyone( RequestContext::getMain() )
92            && $this->config->get( 'GENewcomerTasksGuidanceRequiresOptIn' )
93        ) {
94            $preferences[SuggestedEdits::GUIDANCE_ENABLED_PREF] = [
95                'type' => 'api',
96            ];
97        }
98    }
99
100    /**
101     * Register default preferences for Help Panel.
102     *
103     * @param array &$defaultOptions
104     */
105    public function onUserGetDefaultOptions( &$defaultOptions ) {
106        if ( HelpPanel::isHelpPanelEnabled() ) {
107            $defaultOptions += [
108                self::HELP_PANEL_PREFERENCES_TOGGLE => false
109            ];
110        }
111    }
112
113    /** @inheritDoc */
114    public function onResourceLoaderExcludeUserOptions(
115        array &$keysToExclude,
116        RL\Context $context
117    ): void {
118        $keysToExclude = array_merge( $keysToExclude, [
119            HelpdeskQuestionPoster::QUESTION_PREF,
120            SuggestedEdits::GUIDANCE_ENABLED_PREF,
121        ] );
122    }
123
124    /** @inheritDoc */
125    public function onLocalUserCreated( $user, $autocreated ) {
126        if ( $user->isTemp() ) {
127            return;
128        }
129        if ( !HelpPanel::isHelpPanelEnabled() ) {
130            return;
131        }
132
133        $growthOptInOptOutOverride = HomepageHooks::getGrowthFeaturesOptInOptOutOverride();
134        if ( $growthOptInOptOutOverride === HomepageHooks::GROWTH_FORCE_OPTOUT ) {
135            // User opted-out from Growth features, short-circuit
136            return;
137        }
138
139        // Enable the help panel for a percentage of non-autocreated users.
140        if (
141            $this->config->get( 'GEHelpPanelNewAccountEnableWithHomepage' ) &&
142            HomepageHooks::isHomepageEnabled()
143        ) {
144            // HomepageHooks::onLocalUserCreated() will enable the help panel if needed
145            return;
146        }
147
148        $enablePercentage = $this->config->get( 'GEHelpPanelNewAccountEnablePercentage' );
149        if (
150            $growthOptInOptOutOverride === HomepageHooks::GROWTH_FORCE_OPTIN ||
151            ( !$autocreated && rand( 0, 99 ) < $enablePercentage )
152        ) {
153            $this->userOptionsManager->setOption( $user, self::HELP_PANEL_PREFERENCES_TOGGLE, 1 );
154        }
155    }
156
157    /** @inheritDoc */
158    public function onBeforePageDisplay( $out, $skin ): void {
159        $maybeShow = HelpPanel::shouldShowHelpPanel( $out, false );
160        if ( !$maybeShow ) {
161            return;
162        }
163
164        $definitelyShow = HelpPanel::shouldShowHelpPanel( $out );
165
166        if ( $definitelyShow ) {
167            $out->enableOOUI();
168            $out->addModuleStyles( 'ext.growthExperiments.HelpPanelCta.styles' );
169            $out->addModuleStyles( 'ext.growthExperiments.icons' );
170            $out->addModules( 'ext.growthExperiments.HelpPanel' );
171
172            $out->addHTML( HelpPanel::getHelpPanelCtaButton( $this->wikiConfig ) );
173        }
174
175        if ( SuggestedEdits::isGuidanceEnabled( $out->getContext() ) ) {
176            // Note: wgGELinkRecommendationsFrontendEnabled reflects the configuration flag.
177            // Checking whether Add Link has been disabled in community configuration is the
178            // frontend code's responsibility.
179            $out->addJsConfigVars( [
180                'wgGENewcomerTasksGuidanceEnabled' => true,
181                'wgGEAskQuestionEnabled' => HelpPanel::getHelpDeskTitle( $this->wikiConfig ) !== null,
182                'wgGELinkRecommendationsFrontendEnabled' =>
183                    $out->getConfig()->get( 'GELinkRecommendationsFrontendEnabled' )
184            ] );
185        }
186
187        // If the help panel would be shown but for the value of the 'action' parameter,
188        // add the email config var anyway. We'll need it if the user loads an editor via JS.
189        // Also set wgGEHelpPanelEnabled to let our JS modules know it's safe to display the help panel.
190        $out->addJsConfigVars( [
191                // We know the help panel is enabled, otherwise we wouldn't get here
192                'wgGEHelpPanelEnabled' => true,
193                'wgGEHelpPanelMentorData' => $this->getMentorData(
194                    $this->wikiConfig,
195                    $out->getUser(),
196                    $out->getContext(),
197                    $out->getContext()->getLanguage()
198                ),
199                // wgGEHelpPanelAskMentor needs to be here and not in getModuleData,
200                // because getting current user is not possible within ResourceLoader context
201                'wgGEHelpPanelAskMentor' =>
202                    $this->wikiConfig->get( 'GEMentorshipEnabled' ) &&
203                    $this->wikiConfig->get( 'GEHelpPanelAskMentor' ) &&
204                    $this->mentorManager->getMentorshipStateForUser(
205                        $out->getUser()
206                    ) === MentorManager::MENTORSHIP_ENABLED &&
207                    $this->mentorManager->getEffectiveMentorForUserSafe( $out->getUser() ) !== null,
208            ] + HelpPanel::getUserEmailConfigVars( $out->getUser() ) );
209
210        if ( !$definitelyShow ) {
211            // Add the init module to make sure that the main HelpPanel module is loaded
212            // if and when VisualEditor is loaded
213            $out->addModules( 'ext.growthExperiments.HelpPanel.init' );
214        }
215    }
216
217    /**
218     * Build the contents of the data.json file in the ext.growthExperiments.Help module.
219     * @param RL\Context $context
220     * @param Config $config
221     * @return array
222     */
223    public static function getModuleData( RL\Context $context, Config $config ) {
224        $helpdeskTitle = HelpPanel::getHelpDeskTitle( self::getGrowthWikiConfig() );
225        // The copyright warning can contain markup and has to be parsed via PHP messages API.
226        $copyrightWarningMessage = $context->msg( 'wikimedia-copyrightwarning' );
227        return [
228            'GEHelpPanelSearchNamespaces' => self::getGrowthWikiConfig()
229                ->get( 'GEHelpPanelSearchNamespaces' ),
230            'GEHelpPanelReadingModeNamespaces' => self::getGrowthWikiConfig()
231                ->get( 'GEHelpPanelReadingModeNamespaces' ),
232            'GEHelpPanelSearchForeignAPI' => $config->get( 'GEHelpPanelSearchForeignAPI' ),
233            'GEHelpPanelLinks' => HelpPanel::getHelpPanelLinks(
234                $context, self::getGrowthWikiConfig()
235            ),
236            'GEHelpPanelSuggestedEditsPreferredEditor' => self::getPreferredEditor( $context, $config ),
237            'GEHelpPanelHelpDeskTitle' => $helpdeskTitle ? $helpdeskTitle->getPrefixedText() : null,
238            'GEAskHelpCopyrightWarning' => $copyrightWarningMessage->exists() ?
239                $copyrightWarningMessage->parse() : ''
240        ];
241    }
242
243    /** @inheritDoc */
244    public function onListDefinedTags( &$tags ) {
245        if ( HelpPanel::isHelpPanelEnabled() ) {
246            $tags[] = HelpPanel::HELPDESK_QUESTION_TAG;
247        }
248    }
249
250    /** @inheritDoc */
251    public function onChangeTagsListActive( &$tags ) {
252        if ( HelpPanel::isHelpPanelEnabled() ) {
253            $tags[] = HelpPanel::HELPDESK_QUESTION_TAG;
254        }
255    }
256
257    /**
258     * @param Config $wikiConfig
259     * @param User $user
260     * @param MessageLocalizer $localizer
261     * @param Language $language
262     * @return array
263     */
264    private function getMentorData(
265        Config $wikiConfig,
266        User $user,
267        MessageLocalizer $localizer,
268        Language $language
269    ): array {
270        if ( !$wikiConfig->get( 'GEHelpPanelAskMentor' ) || !$wikiConfig->get( 'GEMentorshipEnabled' ) ) {
271            return [];
272        }
273        $mentor = $this->mentorManager->getMentorForUserSafe( $user );
274        $effectiveMentor = $this->mentorManager->getEffectiveMentorForUserSafe( $user );
275
276        if ( !$mentor || !$effectiveMentor ) {
277            return [];
278        }
279        return [
280            'name' => $mentor->getUserIdentity()->getName(),
281            'gender' => $this->genderCache->getGenderOf(
282                $mentor->getUserIdentity(),
283                __METHOD__
284            ),
285            'effectiveName' => $effectiveMentor->getUserIdentity()->getName(),
286            'effectiveGender' => $this->genderCache->getGenderOf(
287                $effectiveMentor->getUserIdentity(),
288                __METHOD__
289            ),
290            'editCount' => $this->userEditTracker->getUserEditCount( $mentor->getUserIdentity() ),
291            'lastActive' => Mentorship::getMentorLastActive(
292                $mentor->getUserIdentity(), $user,
293                $localizer, $this->userEditTracker
294            ),
295            'backAt' => $language->date(
296                $this->mentorStatusManager->getMentorBackTimestamp( $mentor->getUserIdentity() ) ?? ''
297            )
298        ];
299    }
300
301    /**
302     * Return preferred editor for each task type based on task type handler
303     *
304     * @param RL\Context $context
305     * @param Config $config
306     * @return array
307     */
308    private static function getPreferredEditor( RL\Context $context, Config $config ): array {
309        $geServices = GrowthExperimentsServices::wrap( MediaWikiServices::getInstance() );
310
311        // Hack - RL\Context is not exposed to services initialization
312        $validator = $geServices->getNewcomerTasksConfigurationValidator();
313        $validator->setMessageLocalizer( $context );
314
315        $taskTypes = $geServices->getNewcomerTasksConfigurationLoader()->getTaskTypes();
316        $preferredEditorPerHandlerId = $config->get( 'GEHelpPanelSuggestedEditsPreferredEditor' );
317        $preferredEditorPerTaskType = [];
318        foreach ( $taskTypes as $taskTypeId => $taskType ) {
319            $preferredEditorPerTaskType[ $taskTypeId ] = $preferredEditorPerHandlerId[ $taskType->getHandlerId() ];
320        }
321        return $preferredEditorPerTaskType;
322    }
323
324}