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