Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 77 |
|
0.00% |
0 / 5 |
CRAP | |
0.00% |
0 / 1 |
NewcomerTasksChangeTagsManager | |
0.00% |
0 / 77 |
|
0.00% |
0 / 5 |
306 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
apply | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
30 | |||
checkExistingTags | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
6 | |||
checkUserAccess | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
getTags | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\NewcomerTasks; |
4 | |
5 | use GrowthExperiments\HomepageModules\SuggestedEdits; |
6 | use GrowthExperiments\NewcomerTasks\ConfigurationLoader\ConfigurationLoader; |
7 | use GrowthExperiments\NewcomerTasks\TaskType\TaskType; |
8 | use GrowthExperiments\NewcomerTasks\TaskType\TaskTypeHandler; |
9 | use GrowthExperiments\NewcomerTasks\TaskType\TaskTypeHandlerRegistry; |
10 | use MediaWiki\ChangeTags\ChangeTagsStore; |
11 | use MediaWiki\Config\Config; |
12 | use MediaWiki\Context\RequestContext; |
13 | use MediaWiki\Logger\LoggerFactory; |
14 | use MediaWiki\Revision\RevisionLookup; |
15 | use MediaWiki\User\Options\UserOptionsLookup; |
16 | use MediaWiki\User\UserIdentity; |
17 | use MediaWiki\User\UserIdentityUtils; |
18 | use StatusValue; |
19 | use Wikimedia\Rdbms\IConnectionProvider; |
20 | use Wikimedia\Stats\PrefixingStatsdDataFactoryProxy; |
21 | |
22 | class NewcomerTasksChangeTagsManager { |
23 | |
24 | /** @var ConfigurationLoader */ |
25 | private $configurationLoader; |
26 | /** @var RevisionLookup */ |
27 | private $revisionLookup; |
28 | /** @var TaskTypeHandlerRegistry */ |
29 | private $taskTypeHandlerRegistry; |
30 | /** @var UserOptionsLookup */ |
31 | private $userOptionsLookup; |
32 | /** @var PrefixingStatsdDataFactoryProxy */ |
33 | private $perDbNameStatsdDataFactory; |
34 | /** @var IConnectionProvider */ |
35 | private $connectionProvider; |
36 | /** @var UserIdentityUtils */ |
37 | private $userIdentityUtils; |
38 | /** @var Config|null */ |
39 | private $config; |
40 | /** @var UserIdentity|null */ |
41 | private $user; |
42 | |
43 | private ChangeTagsStore $changeTagsStore; |
44 | |
45 | /** |
46 | * @param UserOptionsLookup $userOptionsLookup |
47 | * @param TaskTypeHandlerRegistry $taskTypeHandlerRegistry |
48 | * @param ConfigurationLoader $configurationLoader |
49 | * @param PrefixingStatsdDataFactoryProxy $perDbNameStatsdDataFactory |
50 | * @param RevisionLookup $revisionLookup |
51 | * @param IConnectionProvider $connectionProvider |
52 | * @param UserIdentityUtils $userIdentityUtils |
53 | * @param ChangeTagsStore $changeTagsStore |
54 | * @param Config|null $config |
55 | * @param UserIdentity|null $user |
56 | * FIXME $config and $user should be mandatory and injected by a factory |
57 | */ |
58 | public function __construct( |
59 | UserOptionsLookup $userOptionsLookup, |
60 | TaskTypeHandlerRegistry $taskTypeHandlerRegistry, |
61 | ConfigurationLoader $configurationLoader, |
62 | PrefixingStatsdDataFactoryProxy $perDbNameStatsdDataFactory, |
63 | RevisionLookup $revisionLookup, |
64 | IConnectionProvider $connectionProvider, |
65 | UserIdentityUtils $userIdentityUtils, |
66 | ChangeTagsStore $changeTagsStore, |
67 | ?Config $config = null, |
68 | ?UserIdentity $user = null |
69 | ) { |
70 | $this->configurationLoader = $configurationLoader; |
71 | $this->revisionLookup = $revisionLookup; |
72 | $this->taskTypeHandlerRegistry = $taskTypeHandlerRegistry; |
73 | $this->userOptionsLookup = $userOptionsLookup; |
74 | $this->perDbNameStatsdDataFactory = $perDbNameStatsdDataFactory; |
75 | $this->connectionProvider = $connectionProvider; |
76 | $this->userIdentityUtils = $userIdentityUtils; |
77 | $this->changeTagsStore = $changeTagsStore; |
78 | $this->config = $config; |
79 | $this->user = $user; |
80 | } |
81 | |
82 | /** |
83 | * Apply change tags to a newcomer task. |
84 | * |
85 | * Note that this should only be used with non-VisualEditor based edits. VE edits are handled via |
86 | * the onVisualEditorApiVisualEditorEditPreSave hook, which also allows for displaying the change |
87 | * tags in the RecentChanges feed. |
88 | * |
89 | * Also note that using this method will set the tags for display in article history but it will |
90 | * not appear in RecentChanges (T24509). |
91 | * |
92 | * @param string $taskTypeId |
93 | * @param int $revisionId |
94 | * @param UserIdentity $userIdentity |
95 | * @return StatusValue |
96 | */ |
97 | public function apply( string $taskTypeId, int $revisionId, UserIdentity $userIdentity ): StatusValue { |
98 | $result = $this->getTags( $taskTypeId, $userIdentity ); |
99 | |
100 | if ( !$result->isGood() ) { |
101 | return $result; |
102 | } |
103 | $tags = $result->getValue(); |
104 | |
105 | $revision = $this->revisionLookup->getRevisionById( $revisionId ); |
106 | if ( !$revision ) { |
107 | return StatusValue::newFatal( $revisionId . ' is not a valid revision ID.' ); |
108 | } |
109 | $revisionUserId = $revision->getUser()->getId(); |
110 | $authorityUserId = $userIdentity->getId(); |
111 | if ( $revisionUserId !== $authorityUserId ) { |
112 | return StatusValue::newFatal( |
113 | sprintf( |
114 | 'User ID %d on revision does not match logged-in user ID %d.', $revisionUserId, $authorityUserId |
115 | ) |
116 | ); |
117 | } |
118 | |
119 | $result = $this->checkExistingTags( $revisionId ); |
120 | if ( !$result->isGood() ) { |
121 | return $result; |
122 | } |
123 | |
124 | $rc_id = null; |
125 | $log_id = null; |
126 | $result = $this->changeTagsStore->updateTags( |
127 | $tags, |
128 | null, |
129 | $rc_id, |
130 | $revisionId, |
131 | $log_id, |
132 | null, |
133 | null, |
134 | $userIdentity |
135 | ); |
136 | LoggerFactory::getInstance( 'GrowthExperiments' )->debug( |
137 | 'ChangeTagsStore::updateTags() result in NewcomerTaskCompleteHandler: ' . json_encode( $result ) |
138 | ); |
139 | // This is needed for non-VE edits. |
140 | // VE edits are incremented in the post-save VisualEditor hook. |
141 | $this->perDbNameStatsdDataFactory->increment( |
142 | 'GrowthExperiments.NewcomerTask.' . $taskTypeId . '.Save' |
143 | ); |
144 | return StatusValue::newGood( $result ); |
145 | } |
146 | |
147 | /** |
148 | * @param int $revId |
149 | * @return StatusValue |
150 | */ |
151 | private function checkExistingTags( int $revId ): StatusValue { |
152 | $rc_id = null; |
153 | $log_id = null; |
154 | $existingTags = $this->changeTagsStore->getTags( |
155 | $this->connectionProvider->getReplicaDatabase(), |
156 | $rc_id, |
157 | $revId, |
158 | $log_id |
159 | ); |
160 | |
161 | // Guard against duplicate submissions, or re-tagging older revisions. |
162 | if ( in_array( TaskTypeHandler::NEWCOMER_TASK_TAG, $existingTags ) ) { |
163 | return StatusValue::newFatal( 'Revision already has newcomer task tag.' ); |
164 | } |
165 | return StatusValue::newGood(); |
166 | } |
167 | |
168 | /** |
169 | * @param UserIdentity $userIdentity |
170 | * @return StatusValue |
171 | */ |
172 | private function checkUserAccess( UserIdentity $userIdentity ): StatusValue { |
173 | if ( !$this->userIdentityUtils->isNamed( $userIdentity ) ) { |
174 | return StatusValue::newFatal( 'You must be logged-in' ); |
175 | } |
176 | if ( !$this->config || !$this->user ) { |
177 | $ctx = RequestContext::getMain(); |
178 | $this->config = $ctx->getConfig(); |
179 | $this->user = $ctx->getUser(); |
180 | } |
181 | if ( !SuggestedEdits::isEnabled( $this->config ) || |
182 | !SuggestedEdits::isActivated( $this->user, $this->userOptionsLookup ) |
183 | ) { |
184 | return StatusValue::newFatal( 'Suggested edits are not enabled or activated for your user.' ); |
185 | } |
186 | return StatusValue::newGood(); |
187 | } |
188 | |
189 | /** |
190 | * @param string $taskTypeId |
191 | * @param UserIdentity $userIdentity |
192 | * @return StatusValue |
193 | */ |
194 | public function getTags( string $taskTypeId, UserIdentity $userIdentity ): StatusValue { |
195 | $result = $this->checkUserAccess( $userIdentity ); |
196 | if ( !$result->isGood() ) { |
197 | return $result; |
198 | } |
199 | $taskType = $this->configurationLoader->getTaskTypes()[$taskTypeId] ?? null; |
200 | if ( !$taskType instanceof TaskType ) { |
201 | return StatusValue::newFatal( 'Invalid task type ID: ' . $taskTypeId ); |
202 | } |
203 | $taskTypeHandler = $this->taskTypeHandlerRegistry->getByTaskType( $taskType ); |
204 | $tags = $taskTypeHandler->getChangeTags( $taskType->getId() ); |
205 | return StatusValue::newGood( $tags ); |
206 | } |
207 | |
208 | } |