Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 128 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
HelpPanelHooks | |
0.00% |
0 / 128 |
|
0.00% |
0 / 11 |
1560 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
onGetPreferences | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
onUserGetDefaultOptions | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
onResourceLoaderExcludeUserOptions | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onLocalUserCreated | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
90 | |||
onBeforePageDisplay | |
0.00% |
0 / 35 |
|
0.00% |
0 / 1 |
72 | |||
getModuleData | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
12 | |||
onListDefinedTags | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
onChangeTagsListActive | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getMentorData | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
30 | |||
getPreferredEditor | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments; |
4 | |
5 | use GrowthExperiments\Config\GrowthConfigLoaderStaticTrait; |
6 | use GrowthExperiments\HelpPanel\QuestionPoster\HelpdeskQuestionPoster; |
7 | use GrowthExperiments\HomepageModules\Mentorship; |
8 | use GrowthExperiments\HomepageModules\SuggestedEdits; |
9 | use GrowthExperiments\MentorDashboard\MentorTools\MentorStatusManager; |
10 | use GrowthExperiments\Mentorship\MentorManager; |
11 | use MediaWiki\Auth\Hook\LocalUserCreatedHook; |
12 | use MediaWiki\Cache\GenderCache; |
13 | use MediaWiki\ChangeTags\Hook\ChangeTagsListActiveHook; |
14 | use MediaWiki\ChangeTags\Hook\ListDefinedTagsHook; |
15 | use MediaWiki\Config\Config; |
16 | use MediaWiki\Context\RequestContext; |
17 | use MediaWiki\Language\Language; |
18 | use MediaWiki\MediaWikiServices; |
19 | use MediaWiki\Output\Hook\BeforePageDisplayHook; |
20 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
21 | use MediaWiki\ResourceLoader as RL; |
22 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderExcludeUserOptionsHook; |
23 | use MediaWiki\User\Hook\UserGetDefaultOptionsHook; |
24 | use MediaWiki\User\Options\UserOptionsManager; |
25 | use MediaWiki\User\User; |
26 | use MediaWiki\User\UserEditTracker; |
27 | use MessageLocalizer; |
28 | |
29 | class 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 | } |