Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.35% |
119 / 121 |
|
85.71% |
6 / 7 |
CRAP | |
0.00% |
0 / 1 |
RCVariableGenerator | |
98.35% |
119 / 121 |
|
85.71% |
6 / 7 |
20 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getVars | |
90.48% |
19 / 21 |
|
0.00% |
0 / 1 |
9.07 | |||
addMoveVars | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
addCreateAccountVars | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
4 | |||
addDeleteVars | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
addUploadVars | |
100.00% |
29 / 29 |
|
100.00% |
1 / 1 |
2 | |||
addEditVarsForRow | |
100.00% |
27 / 27 |
|
100.00% |
1 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\VariableGenerator; |
4 | |
5 | use LogicException; |
6 | use MediaWiki\Extension\AbuseFilter\Hooks\AbuseFilterHookRunner; |
7 | use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder; |
8 | use MediaWiki\Logger\LoggerFactory; |
9 | use MediaWiki\Page\WikiPageFactory; |
10 | use MediaWiki\Title\Title; |
11 | use MediaWiki\User\User; |
12 | use MediaWiki\User\UserFactory; |
13 | use MimeAnalyzer; |
14 | use MWFileProps; |
15 | use RecentChange; |
16 | use RepoGroup; |
17 | |
18 | /** |
19 | * This class contains the logic used to create variable holders used to |
20 | * examine a RecentChanges row. |
21 | */ |
22 | class RCVariableGenerator extends VariableGenerator { |
23 | /** |
24 | * @var RecentChange |
25 | */ |
26 | private $rc; |
27 | |
28 | /** @var User */ |
29 | private $contextUser; |
30 | |
31 | /** @var MimeAnalyzer */ |
32 | private $mimeAnalyzer; |
33 | /** @var RepoGroup */ |
34 | private $repoGroup; |
35 | /** @var WikiPageFactory */ |
36 | private $wikiPageFactory; |
37 | |
38 | /** |
39 | * @param AbuseFilterHookRunner $hookRunner |
40 | * @param UserFactory $userFactory |
41 | * @param MimeAnalyzer $mimeAnalyzer |
42 | * @param RepoGroup $repoGroup |
43 | * @param WikiPageFactory $wikiPageFactory |
44 | * @param RecentChange $rc |
45 | * @param User $contextUser |
46 | * @param VariableHolder|null $vars |
47 | */ |
48 | public function __construct( |
49 | AbuseFilterHookRunner $hookRunner, |
50 | UserFactory $userFactory, |
51 | MimeAnalyzer $mimeAnalyzer, |
52 | RepoGroup $repoGroup, |
53 | WikiPageFactory $wikiPageFactory, |
54 | RecentChange $rc, |
55 | User $contextUser, |
56 | VariableHolder $vars = null |
57 | ) { |
58 | parent::__construct( $hookRunner, $userFactory, $vars ); |
59 | |
60 | $this->mimeAnalyzer = $mimeAnalyzer; |
61 | $this->repoGroup = $repoGroup; |
62 | $this->wikiPageFactory = $wikiPageFactory; |
63 | $this->rc = $rc; |
64 | $this->contextUser = $contextUser; |
65 | } |
66 | |
67 | /** |
68 | * @return VariableHolder|null |
69 | */ |
70 | public function getVars(): ?VariableHolder { |
71 | if ( $this->rc->getAttribute( 'rc_type' ) == RC_LOG ) { |
72 | switch ( $this->rc->getAttribute( 'rc_log_type' ) ) { |
73 | case 'move': |
74 | $this->addMoveVars(); |
75 | break; |
76 | case 'newusers': |
77 | $this->addCreateAccountVars(); |
78 | break; |
79 | case 'delete': |
80 | $this->addDeleteVars(); |
81 | break; |
82 | case 'upload': |
83 | $this->addUploadVars(); |
84 | break; |
85 | default: |
86 | return null; |
87 | } |
88 | } elseif ( $this->rc->getAttribute( 'rc_this_oldid' ) ) { |
89 | // It's an edit (or a page creation). |
90 | $this->addEditVarsForRow(); |
91 | } elseif ( |
92 | !$this->hookRunner->onAbuseFilterGenerateVarsForRecentChange( |
93 | $this, $this->rc, $this->vars, $this->contextUser ) |
94 | ) { |
95 | // @codeCoverageIgnoreStart |
96 | throw new LogicException( 'Cannot understand the given recentchanges row!' ); |
97 | // @codeCoverageIgnoreEnd |
98 | } |
99 | |
100 | $this->addGenericVars( $this->rc ); |
101 | |
102 | return $this->vars; |
103 | } |
104 | |
105 | /** |
106 | * @return $this |
107 | */ |
108 | private function addMoveVars(): self { |
109 | $userIdentity = $this->rc->getPerformerIdentity(); |
110 | |
111 | $oldTitle = $this->rc->getTitle(); |
112 | $newTitle = Title::newFromText( $this->rc->getParam( '4::target' ) ); |
113 | |
114 | $this->addUserVars( $userIdentity, $this->rc ) |
115 | ->addTitleVars( $oldTitle, 'moved_from', $this->rc ) |
116 | ->addTitleVars( $newTitle, 'moved_to', $this->rc ); |
117 | |
118 | $this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) ); |
119 | $this->vars->setVar( 'action', 'move' ); |
120 | |
121 | $this->vars->setLazyLoadVar( |
122 | 'moved_from_last_edit_age', |
123 | 'previous-revision-age', |
124 | // rc_last_oldid is zero (RecentChange::newLogEntry) |
125 | [ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ) ] |
126 | ); |
127 | // TODO: add moved_to_last_edit_age (is it possible?) |
128 | // TODO: add old_wikitext etc. (T320347) |
129 | |
130 | return $this; |
131 | } |
132 | |
133 | /** |
134 | * @return $this |
135 | */ |
136 | private function addCreateAccountVars(): self { |
137 | $this->vars->setVar( |
138 | 'action', |
139 | $this->rc->getAttribute( 'rc_log_action' ) === 'autocreate' |
140 | ? 'autocreateaccount' |
141 | : 'createaccount' |
142 | ); |
143 | |
144 | $name = $this->rc->getTitle()->getText(); |
145 | // Add user data if the account was created by a registered user |
146 | $userIdentity = $this->rc->getPerformerIdentity(); |
147 | if ( $userIdentity->isRegistered() && $name !== $userIdentity->getName() ) { |
148 | $this->addUserVars( $userIdentity, $this->rc ); |
149 | } else { |
150 | // Set the user_type so that creations of temporary accounts vs named accounts can be filtered for an |
151 | // abuse filter that matches account creations. |
152 | $this->vars->setLazyLoadVar( |
153 | 'user_type', |
154 | 'user-type', |
155 | [ 'user-identity' => $userIdentity ] |
156 | ); |
157 | } |
158 | |
159 | $this->vars->setVar( 'accountname', $name ); |
160 | |
161 | return $this; |
162 | } |
163 | |
164 | /** |
165 | * @return $this |
166 | */ |
167 | private function addDeleteVars(): self { |
168 | $title = $this->rc->getTitle(); |
169 | $userIdentity = $this->rc->getPerformerIdentity(); |
170 | |
171 | $this->addUserVars( $userIdentity, $this->rc ) |
172 | ->addTitleVars( $title, 'page', $this->rc ); |
173 | |
174 | $this->vars->setVar( 'action', 'delete' ); |
175 | $this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) ); |
176 | // TODO: add page_last_edit_age |
177 | // TODO: add old_wikitext etc. (T173663) |
178 | |
179 | return $this; |
180 | } |
181 | |
182 | /** |
183 | * @return $this |
184 | */ |
185 | private function addUploadVars(): self { |
186 | $title = $this->rc->getTitle(); |
187 | $userIdentity = $this->rc->getPerformerIdentity(); |
188 | |
189 | $this->addUserVars( $userIdentity, $this->rc ) |
190 | ->addTitleVars( $title, 'page', $this->rc ); |
191 | |
192 | $this->vars->setVar( 'action', 'upload' ); |
193 | $this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) ); |
194 | |
195 | $this->vars->setLazyLoadVar( |
196 | 'page_last_edit_age', |
197 | 'previous-revision-age', |
198 | // rc_last_oldid is zero (RecentChange::newLogEntry) |
199 | [ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ) ] |
200 | ); |
201 | |
202 | $time = $this->rc->getParam( 'img_timestamp' ); |
203 | $file = $this->repoGroup->findFile( |
204 | $title, [ 'time' => $time, 'private' => $this->contextUser ] |
205 | ); |
206 | if ( !$file ) { |
207 | // @fixme Ensure this cannot happen! |
208 | // @codeCoverageIgnoreStart |
209 | $logger = LoggerFactory::getInstance( 'AbuseFilter' ); |
210 | $logger->warning( "Cannot find file from RC row with title $title" ); |
211 | return $this; |
212 | // @codeCoverageIgnoreEnd |
213 | } |
214 | |
215 | // This is the same as FilteredActionsHandler::filterUpload, but from a different source |
216 | $this->vars->setVar( 'file_sha1', \Wikimedia\base_convert( $file->getSha1(), 36, 16, 40 ) ); |
217 | $this->vars->setVar( 'file_size', $file->getSize() ); |
218 | |
219 | $this->vars->setVar( 'file_mime', $file->getMimeType() ); |
220 | $this->vars->setVar( |
221 | 'file_mediatype', |
222 | $this->mimeAnalyzer->getMediaType( null, $file->getMimeType() ) |
223 | ); |
224 | $this->vars->setVar( 'file_width', $file->getWidth() ); |
225 | $this->vars->setVar( 'file_height', $file->getHeight() ); |
226 | |
227 | $mwProps = new MWFileProps( $this->mimeAnalyzer ); |
228 | $bits = $mwProps->getPropsFromPath( $file->getLocalRefPath(), true )['bits']; |
229 | $this->vars->setVar( 'file_bits_per_channel', $bits ); |
230 | |
231 | return $this; |
232 | } |
233 | |
234 | /** |
235 | * @return $this |
236 | */ |
237 | private function addEditVarsForRow(): self { |
238 | $title = $this->rc->getTitle(); |
239 | $userIdentity = $this->rc->getPerformerIdentity(); |
240 | |
241 | $this->addUserVars( $userIdentity, $this->rc ) |
242 | ->addTitleVars( $title, 'page', $this->rc ); |
243 | |
244 | $this->vars->setVar( 'action', 'edit' ); |
245 | $this->vars->setVar( 'summary', $this->rc->getAttribute( 'rc_comment' ) ); |
246 | |
247 | $this->vars->setLazyLoadVar( 'new_wikitext', 'revision-text-by-id', |
248 | [ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ), 'contextUser' => $this->contextUser ] ); |
249 | $this->vars->setLazyLoadVar( 'new_content_model', 'content-model-by-id', |
250 | [ 'revid' => $this->rc->getAttribute( 'rc_this_oldid' ) ] ); |
251 | |
252 | $parentId = $this->rc->getAttribute( 'rc_last_oldid' ); |
253 | if ( $parentId ) { |
254 | $this->vars->setLazyLoadVar( 'old_wikitext', 'revision-text-by-id', |
255 | [ 'revid' => $parentId, 'contextUser' => $this->contextUser ] ); |
256 | $this->vars->setLazyLoadVar( 'old_content_model', 'content-model-by-id', |
257 | [ 'revid' => $parentId ] ); |
258 | $this->vars->setLazyLoadVar( 'page_last_edit_age', 'revision-age-by-id', |
259 | [ 'revid' => $parentId, 'asof' => $this->rc->getAttribute( 'rc_timestamp' ) ] ); |
260 | } else { |
261 | $this->vars->setVar( 'old_wikitext', '' ); |
262 | $this->vars->setVar( 'old_content_model', '' ); |
263 | $this->vars->setVar( 'page_last_edit_age', null ); |
264 | } |
265 | |
266 | $this->addEditVars( |
267 | $this->wikiPageFactory->newFromTitle( $title ), |
268 | $this->contextUser, |
269 | false |
270 | ); |
271 | |
272 | return $this; |
273 | } |
274 | } |