Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
6.00% |
9 / 150 |
|
16.67% |
1 / 6 |
CRAP | |
0.00% |
0 / 1 |
SpecialCheckUserLog | |
6.00% |
9 / 150 |
|
16.67% |
1 / 6 |
318.84 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
0.00% |
0 / 57 |
|
0.00% |
0 / 1 |
156 | |||
addSubtitle | |
0.00% |
0 / 33 |
|
0.00% |
0 / 1 |
12 | |||
displaySearchForm | |
0.00% |
0 / 49 |
|
0.00% |
0 / 1 |
2 | |||
verifyInitiator | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CheckUser\CheckUser; |
4 | |
5 | use HTMLForm; |
6 | use MediaWiki\Cache\LinkBatchFactory; |
7 | use MediaWiki\CheckUser\CheckUser\Pagers\CheckUserLogPager; |
8 | use MediaWiki\CheckUser\Services\CheckUserLogService; |
9 | use MediaWiki\CommentFormatter\CommentFormatter; |
10 | use MediaWiki\CommentStore\CommentStore; |
11 | use MediaWiki\Html\Html; |
12 | use MediaWiki\Pager\ContribsPager; |
13 | use MediaWiki\Permissions\PermissionManager; |
14 | use MediaWiki\SpecialPage\SpecialPage; |
15 | use MediaWiki\Title\Title; |
16 | use MediaWiki\User\ActorStore; |
17 | use MediaWiki\User\UserFactory; |
18 | use UserBlockedError; |
19 | use Wikimedia\Rdbms\IReadableDatabase; |
20 | use Wikimedia\Rdbms\LBFactory; |
21 | |
22 | class SpecialCheckUserLog extends SpecialPage { |
23 | /** |
24 | * @var array an array of nullable string/integer options. |
25 | */ |
26 | protected array $opts; |
27 | |
28 | private IReadableDatabase $dbr; |
29 | |
30 | private LinkBatchFactory $linkBatchFactory; |
31 | private PermissionManager $permissionManager; |
32 | private CommentStore $commentStore; |
33 | private CommentFormatter $commentFormatter; |
34 | private CheckUserLogService $checkUserLogService; |
35 | private UserFactory $userFactory; |
36 | private ActorStore $actorStore; |
37 | |
38 | /** |
39 | * @param LinkBatchFactory $linkBatchFactory |
40 | * @param PermissionManager $permissionManager |
41 | * @param CommentStore $commentStore |
42 | * @param CommentFormatter $commentFormatter |
43 | * @param CheckUserLogService $checkUserLogService |
44 | * @param UserFactory $userFactory |
45 | * @param ActorStore $actorStore |
46 | * @param LBFactory $lbFactory |
47 | */ |
48 | public function __construct( |
49 | LinkBatchFactory $linkBatchFactory, |
50 | PermissionManager $permissionManager, |
51 | CommentStore $commentStore, |
52 | CommentFormatter $commentFormatter, |
53 | CheckUserLogService $checkUserLogService, |
54 | UserFactory $userFactory, |
55 | ActorStore $actorStore, |
56 | LBFactory $lbFactory |
57 | ) { |
58 | parent::__construct( 'CheckUserLog', 'checkuser-log' ); |
59 | $this->linkBatchFactory = $linkBatchFactory; |
60 | $this->permissionManager = $permissionManager; |
61 | $this->commentStore = $commentStore; |
62 | $this->commentFormatter = $commentFormatter; |
63 | $this->checkUserLogService = $checkUserLogService; |
64 | $this->userFactory = $userFactory; |
65 | $this->actorStore = $actorStore; |
66 | $this->dbr = $lbFactory->getReplicaDatabase(); |
67 | } |
68 | |
69 | /** |
70 | * @inheritDoc |
71 | */ |
72 | public function execute( $par ) { |
73 | $this->setHeaders(); |
74 | $this->addHelpLink( 'Extension:CheckUser' ); |
75 | $this->checkPermissions(); |
76 | |
77 | // Blocked users are not allowed to run checkuser queries (bug T157883) |
78 | $block = $this->getUser()->getBlock(); |
79 | if ( $block && $block->isSitewide() ) { |
80 | throw new UserBlockedError( $block ); |
81 | } |
82 | |
83 | $out = $this->getOutput(); |
84 | $out->addModules( [ 'ext.checkUser' ] ); |
85 | $out->addModuleStyles( [ |
86 | 'ext.checkUser.styles', |
87 | 'mediawiki.interface.helpers.styles' |
88 | ] ); |
89 | $request = $this->getRequest(); |
90 | |
91 | $this->opts = []; |
92 | |
93 | // Normalise target parameter and ignore if not valid (T217713) |
94 | // It must be valid when making a link to Special:CheckUserLog/<user>. |
95 | // Do not normalize an empty target, as that means "everything" (T265606) |
96 | $this->opts['target'] = trim( $request->getVal( 'cuSearch', $par ?? '' ) ); |
97 | if ( $this->opts['target'] !== '' ) { |
98 | $userTitle = Title::makeTitleSafe( NS_USER, $this->opts['target'] ); |
99 | $this->opts['target'] = $userTitle ? $userTitle->getText() : ''; |
100 | } |
101 | |
102 | $this->opts['initiator'] = trim( $request->getVal( 'cuInitiator', '' ) ); |
103 | |
104 | $this->opts['reason'] = trim( $request->getVal( 'cuReasonSearch', '' ) ); |
105 | |
106 | // From SpecialContributions.php |
107 | $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) === 'prev'; |
108 | # Offset overrides year/month selection |
109 | if ( !$skip ) { |
110 | $this->opts['year'] = $request->getIntOrNull( 'year' ); |
111 | $this->opts['month'] = $request->getIntOrNull( 'month' ); |
112 | |
113 | $this->opts['start'] = $request->getVal( 'start' ); |
114 | $this->opts['end'] = $request->getVal( 'end' ); |
115 | } |
116 | |
117 | $this->opts = ContribsPager::processDateFilter( $this->opts ); |
118 | |
119 | $this->addSubtitle(); |
120 | |
121 | $this->displaySearchForm(); |
122 | |
123 | $errorMessageKey = null; |
124 | |
125 | if ( |
126 | $this->opts['target'] !== '' && |
127 | $this->checkUserLogService->verifyTarget( $this->opts['target'] ) === false |
128 | ) { |
129 | $errorMessageKey = 'checkuser-target-nonexistent'; |
130 | } |
131 | if ( $this->opts['initiator'] !== '' && $this->verifyInitiator( $this->opts['initiator'] ) === false ) { |
132 | $errorMessageKey = 'checkuser-initiator-nonexistent'; |
133 | } |
134 | |
135 | if ( $errorMessageKey !== null ) { |
136 | // Invalid target was input so show an error message and stop from here |
137 | $out->addHTML( |
138 | Html::errorBox( |
139 | $out->msg( $errorMessageKey )->parse() |
140 | ) |
141 | ); |
142 | return; |
143 | } |
144 | |
145 | $pager = new CheckUserLogPager( |
146 | $this->getContext(), |
147 | $this->opts, |
148 | $this->linkBatchFactory, |
149 | $this->commentStore, |
150 | $this->commentFormatter, |
151 | $this->checkUserLogService, |
152 | $this->userFactory, |
153 | $this->actorStore |
154 | ); |
155 | |
156 | $out->addHTML( |
157 | $pager->getNavigationBar() . |
158 | $pager->getBody() . |
159 | $pager->getNavigationBar() |
160 | ); |
161 | } |
162 | |
163 | /** |
164 | * Add subtitle links to the page |
165 | */ |
166 | private function addSubtitle(): void { |
167 | if ( $this->permissionManager->userHasRight( $this->getUser(), 'checkuser' ) ) { |
168 | $links = [ |
169 | $this->getLinkRenderer()->makeKnownLink( |
170 | SpecialPage::getTitleFor( 'CheckUser' ), |
171 | $this->msg( 'checkuser-showmain' )->text() |
172 | ), |
173 | $this->getLinkRenderer()->makeKnownLink( |
174 | SpecialPage::getTitleFor( 'Investigate' ), |
175 | $this->msg( 'checkuser-show-investigate' )->text() |
176 | ), |
177 | ]; |
178 | |
179 | if ( $this->opts['target'] ) { |
180 | $links[] = $this->getLinkRenderer()->makeKnownLink( |
181 | SpecialPage::getTitleFor( 'CheckUser', $this->opts['target'] ), |
182 | $this->msg( 'checkuser-check-this-user' )->text() |
183 | ); |
184 | |
185 | $links[] = $this->getLinkRenderer()->makeKnownLink( |
186 | SpecialPage::getTitleFor( 'Investigate' ), |
187 | $this->msg( 'checkuser-investigate-this-user' )->text(), |
188 | [], |
189 | [ 'targets' => $this->opts['target'] ] |
190 | ); |
191 | } |
192 | |
193 | $this->getOutput()->addSubtitle( Html::rawElement( |
194 | 'span', |
195 | [ "class" => "mw-checkuser-links-no-parentheses" ], |
196 | Html::openElement( 'span' ) . |
197 | implode( |
198 | Html::closeElement( 'span' ) . Html::openElement( 'span' ), |
199 | $links |
200 | ) . |
201 | Html::closeElement( 'span' ) |
202 | ) |
203 | ); |
204 | } |
205 | } |
206 | |
207 | /** |
208 | * Use an HTMLForm to create and output the search form used on this page. |
209 | */ |
210 | protected function displaySearchForm() { |
211 | $fields = [ |
212 | 'target' => [ |
213 | 'type' => 'user', |
214 | // validation in execute() currently |
215 | 'exists' => false, |
216 | 'ipallowed' => true, |
217 | 'name' => 'cuSearch', |
218 | 'size' => 40, |
219 | 'label-message' => 'checkuser-log-search-target', |
220 | 'default' => $this->opts['target'], |
221 | 'id' => 'mw-target-user-or-ip' |
222 | ], |
223 | 'initiator' => [ |
224 | 'type' => 'user', |
225 | // validation in execute() currently |
226 | 'exists' => false, |
227 | 'ipallowed' => true, |
228 | 'name' => 'cuInitiator', |
229 | 'size' => 40, |
230 | 'label-message' => 'checkuser-log-search-initiator', |
231 | 'default' => $this->opts['initiator'] |
232 | ], |
233 | 'reason' => [ |
234 | 'type' => 'text', |
235 | 'name' => 'cuReasonSearch', |
236 | 'size' => 40, |
237 | 'label-message' => 'checkuser-log-search-reason', |
238 | 'default' => $this->opts['reason'], |
239 | 'help-message' => 'checkuser-log-search-reason-help' |
240 | ], |
241 | 'start' => [ |
242 | 'type' => 'date', |
243 | 'default' => '', |
244 | 'id' => 'mw-date-start', |
245 | 'label' => $this->msg( 'date-range-from' )->text(), |
246 | 'name' => 'start' |
247 | ], |
248 | 'end' => [ |
249 | 'type' => 'date', |
250 | 'default' => '', |
251 | 'id' => 'mw-date-end', |
252 | 'label' => $this->msg( 'date-range-to' )->text(), |
253 | 'name' => 'end' |
254 | ] |
255 | ]; |
256 | |
257 | $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() ); |
258 | $form->setMethod( 'get' ) |
259 | ->setWrapperLegendMsg( 'checkuser-search' ) |
260 | ->setSubmitTextMsg( 'checkuser-search-submit' ) |
261 | ->prepareForm() |
262 | ->displayForm( false ); |
263 | } |
264 | |
265 | /** |
266 | * Verify if the initiator is valid. |
267 | * |
268 | * This is defined by a user having a valid actor ID. |
269 | * Any user without an actor ID cannot be a valid initiator |
270 | * as making a check causes an actor ID to be created. |
271 | * |
272 | * @param string $initiator The name of the initiator that is to be verified |
273 | * @return bool|int |
274 | */ |
275 | private function verifyInitiator( string $initiator ) { |
276 | return $this->actorStore->findActorIdByName( $initiator, $this->dbr ) ?? false; |
277 | } |
278 | |
279 | /** |
280 | * @inheritDoc |
281 | */ |
282 | protected function getGroupName() { |
283 | return 'changes'; |
284 | } |
285 | } |