Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 63 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
LevelingUpHooks | |
0.00% |
0 / 63 |
|
0.00% |
0 / 6 |
420 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onVisualEditorApiVisualEditorEditPostSave | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
onBeforePageDisplay | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
72 | |||
onBeforeCreateEchoEvent | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
2 | |||
onUserGetDefaultOptions | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isLevelingUpEnabledForUser | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\LevelingUp; |
4 | |
5 | use EchoAttributeManager; |
6 | use EchoUserLocator; |
7 | use GrowthExperiments\ExperimentUserManager; |
8 | use GrowthExperiments\HomepageHooks; |
9 | use GrowthExperiments\HomepageModules\SuggestedEdits; |
10 | use GrowthExperiments\NewcomerTasks\ConfigurationLoader\ConfigurationLoader; |
11 | use GrowthExperiments\VariantHooks; |
12 | use GrowthExperiments\VisualEditorHooks; |
13 | use MediaWiki\Config\Config; |
14 | use MediaWiki\Extension\VisualEditor\VisualEditorApiVisualEditorEditPostSaveHook; |
15 | use MediaWiki\Hook\BeforePageDisplayHook; |
16 | use MediaWiki\Page\ProperPageIdentity; |
17 | use MediaWiki\User\Hook\UserGetDefaultOptionsHook; |
18 | use MediaWiki\User\UserIdentity; |
19 | |
20 | /** |
21 | * Hooks for the "leveling up" feature. |
22 | * @see https://www.mediawiki.org/wiki/Growth/Positive_reinforcement#Leveling_up |
23 | */ |
24 | class LevelingUpHooks implements |
25 | BeforePageDisplayHook, |
26 | VisualEditorApiVisualEditorEditPostSaveHook, |
27 | UserGetDefaultOptionsHook |
28 | { |
29 | |
30 | private Config $config; |
31 | private ConfigurationLoader $configurationLoader; |
32 | private ExperimentUserManager $experimentUserManager; |
33 | private LevelingUpManager $levelingUpManager; |
34 | |
35 | /** |
36 | * @param Config $config |
37 | * @param ConfigurationLoader $configurationLoader |
38 | * @param ExperimentUserManager $experimentUserManager |
39 | * @param LevelingUpManager $levelingUpManager |
40 | */ |
41 | public function __construct( |
42 | Config $config, |
43 | ConfigurationLoader $configurationLoader, |
44 | ExperimentUserManager $experimentUserManager, |
45 | LevelingUpManager $levelingUpManager |
46 | ) { |
47 | $this->config = $config; |
48 | $this->configurationLoader = $configurationLoader; |
49 | $this->experimentUserManager = $experimentUserManager; |
50 | $this->levelingUpManager = $levelingUpManager; |
51 | } |
52 | |
53 | /** |
54 | * Load the InviteToSuggestedEdits module after a VisualEditor save. |
55 | * @inheritDoc |
56 | */ |
57 | public function onVisualEditorApiVisualEditorEditPostSave( |
58 | ProperPageIdentity $page, |
59 | UserIdentity $user, |
60 | string $wikitext, |
61 | array $params, |
62 | array $pluginData, |
63 | array $saveResult, |
64 | array &$apiResponse |
65 | ): void { |
66 | $taskTypes = $this->configurationLoader->getTaskTypes(); |
67 | $pluginFields = array_map( |
68 | fn ( $taskTypeId ) => VisualEditorHooks::PLUGIN_PREFIX . $taskTypeId, |
69 | array_keys( $taskTypes ) |
70 | ); |
71 | $isSuggestedEdit = count( array_intersect( $pluginFields, array_keys( $pluginData ) ) ) > 0; |
72 | |
73 | // Check that the feature is enabled, we are editing an article and the user |
74 | // just passed the threshold for the invite. |
75 | // Also check if the current edit is a suggested edit, as a micro-optimisation |
76 | // (shouldInviteUserAfterNormalEdit() would discard that case anyway). |
77 | if ( $page->getNamespace() !== NS_MAIN |
78 | || $isSuggestedEdit |
79 | || !self::isLevelingUpEnabledForUser( $user, $this->config, $this->experimentUserManager ) |
80 | || !$this->levelingUpManager->shouldInviteUserAfterNormalEdit( $user ) |
81 | ) { |
82 | return; |
83 | } |
84 | |
85 | $apiResponse['modules'][] = 'ext.growthExperiments.LevelingUp.InviteToSuggestedEdits'; |
86 | $apiResponse['jsconfigvars']['wgPostEditConfirmationDisabled'] = true; |
87 | } |
88 | |
89 | /** |
90 | * Load the InviteToSuggestedEdits module when VE reloads the page after save (which it |
91 | * indicates by the use of the 'venotify' parameter). |
92 | * |
93 | * @inheritDoc |
94 | */ |
95 | public function onBeforePageDisplay( $out, $skin ): void { |
96 | // VE sets a query parameter, but there is no elegant way to detect post-edit reloads |
97 | // in the wikitext editor. Check the JS variable that it uses to configure the notice. |
98 | $isPostEditReload = $out->getRequest()->getCheck( 'venotify' ) |
99 | || $out->getRequest()->getCheck( 'mfnotify' ) |
100 | || ( $out->getJsConfigVars()['wgPostEdit'] ?? false ); |
101 | |
102 | if ( |
103 | // Check that the feature is enabled, we are indeed in the post-edit reload of |
104 | // an article, and the user just passed the threshold for the invite. |
105 | !$isPostEditReload |
106 | || ( !$out->getTitle() || !$out->getTitle()->inNamespace( NS_MAIN ) ) |
107 | || !self::isLevelingUpEnabledForUser( $out->getUser(), $this->config, $this->experimentUserManager ) |
108 | || !$this->levelingUpManager->shouldInviteUserAfterNormalEdit( $out->getUser() ) |
109 | ) { |
110 | return; |
111 | } |
112 | |
113 | $out->addModules( 'ext.growthExperiments.LevelingUp.InviteToSuggestedEdits' ); |
114 | // Disable the default core post-edit notice. |
115 | $out->addJsConfigVars( 'wgPostEditConfirmationDisabled', true ); |
116 | $out->addJsConfigVars( 'wgGELevelingUpInviteToSuggestedEditsImmediate', true ); |
117 | $out->addJsConfigVars( 'wgCXSectionTranslationRecentEditInvitationSuppressed', true ); |
118 | } |
119 | |
120 | /** |
121 | * Add GrowthExperiments events to Echo |
122 | * |
123 | * @param array &$notifications array of Echo notifications |
124 | * @param array &$notificationCategories array of Echo notification categories |
125 | * @param array &$icons array of icon details |
126 | */ |
127 | public static function onBeforeCreateEchoEvent( |
128 | &$notifications, &$notificationCategories, &$icons |
129 | ) { |
130 | $notificationCategories['ge-newcomer'] = [ |
131 | 'tooltip' => 'echo-pref-tooltip-ge-newcomer', |
132 | ]; |
133 | $notifications['keep-going'] = [ |
134 | 'category' => 'ge-newcomer', |
135 | 'group' => 'positive', |
136 | 'section' => 'message', |
137 | 'canNotifyAgent' => true, |
138 | 'presentation-model' => EchoKeepGoingPresentationModel::class, |
139 | EchoAttributeManager::ATTR_LOCATORS => [ |
140 | [ EchoUserLocator::class . '::locateEventAgent' ] |
141 | ] |
142 | ]; |
143 | |
144 | $icons['growthexperiments-keep-going'] = [ |
145 | 'path' => 'GrowthExperiments/images/notifications-keep-going.svg' |
146 | ]; |
147 | |
148 | $notifications['get-started'] = [ |
149 | 'category' => 'ge-newcomer', |
150 | 'group' => 'positive', |
151 | 'section' => 'message', |
152 | 'canNotifyAgent' => true, |
153 | 'presentation-model' => EchoGetStartedPresentationModel::class, |
154 | EchoAttributeManager::ATTR_LOCATORS => [ |
155 | [ EchoUserLocator::class . '::locateEventAgent' ] |
156 | ] |
157 | ]; |
158 | |
159 | $icons['growthexperiments-get-started'] = [ |
160 | 'path' => 'GrowthExperiments/images/notifications-get-started.svg' |
161 | ]; |
162 | } |
163 | |
164 | /** @inheritDoc */ |
165 | public function onUserGetDefaultOptions( &$defaultOptions ) { |
166 | $defaultOptions['echo-subscriptions-email-ge-newcomer'] = true; |
167 | $defaultOptions['echo-subscriptions-web-ge-newcomer'] = true; |
168 | } |
169 | |
170 | /** |
171 | * Whether leveling up features are available. This does not include any checks based on |
172 | * the user's contribution history, just site configuration and A/B test cohorts. |
173 | * @param UserIdentity $user |
174 | * @param Config $config Site configuration. |
175 | * @param ExperimentUserManager $experimentUserManager |
176 | * @return bool |
177 | */ |
178 | public static function isLevelingUpEnabledForUser( |
179 | UserIdentity $user, |
180 | Config $config, |
181 | ExperimentUserManager $experimentUserManager |
182 | ): bool { |
183 | // Leveling up should only be shown if |
184 | // 1) suggested edits are available on this wiki, as we'll direct the user there |
185 | // 2) the user's homepage is enabled, which maybe SuggestedEdits::isEnabled should |
186 | // check, but it doesn't (this also excludes autocreated potentially-experienced |
187 | // users who probably shouldn't get invites) |
188 | // 3) (for now) the wiki is a pilot wiki and the user is in the experiment group |
189 | return $config->get( 'GELevelingUpFeaturesEnabled' ) |
190 | && SuggestedEdits::isEnabled( $config ) |
191 | && HomepageHooks::isHomepageEnabled( $user ) |
192 | && $experimentUserManager->isUserInVariant( $user, VariantHooks::VARIANT_CONTROL ); |
193 | } |
194 | |
195 | } |