Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.97% covered (warning)
81.97%
100 / 122
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryCheckUserLog
81.97% covered (warning)
81.97%
100 / 122
60.00% covered (warning)
60.00%
3 / 5
27.38
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 execute
79.49% covered (warning)
79.49%
62 / 78
0.00% covered (danger)
0.00%
0 / 1
23.45
 getAllowedParams
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace MediaWiki\CheckUser\Api;
4
5use ApiBase;
6use ApiQuery;
7use ApiQueryBase;
8use MediaWiki\CheckUser\Services\CheckUserLogService;
9use MediaWiki\CommentStore\CommentStore;
10use MediaWiki\User\UserFactory;
11use Wikimedia\IPUtils;
12use Wikimedia\ParamValidator\ParamValidator;
13use Wikimedia\ParamValidator\TypeDef\IntegerDef;
14
15/**
16 * CheckUser API Query Module
17 */
18class ApiQueryCheckUserLog extends ApiQueryBase {
19    private CommentStore $commentStore;
20    private CheckUserLogService $checkUserLogService;
21    private UserFactory $userFactory;
22
23    /**
24     * @param ApiQuery $query
25     * @param string $moduleName
26     * @param CommentStore $commentStore
27     * @param CheckUserLogService $checkUserLogService
28     * @param UserFactory $userFactory
29     */
30    public function __construct(
31        $query, $moduleName,
32        CommentStore $commentStore,
33        CheckUserLogService $checkUserLogService,
34        UserFactory $userFactory
35    ) {
36        parent::__construct( $query, $moduleName, 'cul' );
37        $this->commentStore = $commentStore;
38        $this->checkUserLogService = $checkUserLogService;
39        $this->userFactory = $userFactory;
40    }
41
42    public function execute() {
43        $db = $this->getDB();
44        $params = $this->extractRequestParams();
45        $this->checkUserRightsAny( 'checkuser-log' );
46
47        $limit = $params['limit'];
48        $continue = $params['continue'];
49        $dir = $params['dir'];
50
51        $this->addTables( [ 'cu_log', 'actor' ] );
52        $this->addOption( 'LIMIT', $limit + 1 );
53        $this->addTimestampWhereRange( 'cul_timestamp', $dir, $params['from'], $params['to'] );
54        $fields = [
55            'cul_id', 'cul_timestamp', 'cul_type', 'cul_target_text', 'actor_name'
56        ];
57        $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=cul_actor' ] ] );
58
59        $reasonCommentQuery = $this->commentStore->getJoin( 'cul_reason' );
60        $this->addTables( $reasonCommentQuery['tables'] );
61        $this->addJoinConds( $reasonCommentQuery['joins'] );
62        $fields += $reasonCommentQuery['fields'];
63
64        if ( isset( $params['reason'] ) ) {
65            $plaintextReasonCommentQuery = $this->commentStore->getJoin( 'cul_reason_plaintext' );
66            $this->addTables( $plaintextReasonCommentQuery['tables'] );
67            $this->addJoinConds( $plaintextReasonCommentQuery['joins'] );
68            $fields += $plaintextReasonCommentQuery['fields'];
69        }
70
71        $this->addFields( $fields );
72
73        // Order by both timestamp and id
74        $order = ( $dir === 'newer' ? '' : ' DESC' );
75        $this->addOption( 'ORDER BY', [ 'cul_timestamp' . $order, 'cul_id' . $order ] );
76
77        if ( isset( $params['user'] ) ) {
78            $this->addWhereFld( 'actor_name', $params['user'] );
79        }
80        if ( isset( $params['target'] ) ) {
81            $cond = $this->checkUserLogService->getTargetSearchConds( $params['target'] );
82            if ( !$cond ) {
83                if ( IPUtils::isIPAddress( $params['target'] ) ) {
84                    $this->dieWithError( 'apierror-badip', 'invalidip' );
85                } else {
86                    $this->dieWithError( 'apierror-checkuser-nosuchuser', 'nosuchuser' );
87                }
88            }
89            $this->addWhere( $cond );
90            if ( IPUtils::isIPAddress( $params['target'] ) ) {
91                // Use the cul_target_hex index on the query if the target is an IP
92                // otherwise the query could take a long time (T342639)
93                $this->addOption( 'USE INDEX', [ 'cu_log' => 'cul_target_hex' ] );
94            }
95        }
96
97        if ( isset( $params['reason'] ) ) {
98            $plaintextReason = $this->checkUserLogService->getPlaintextReason( $params['reason'] );
99            $this->addWhereFld(
100                'comment_cul_reason_plaintext.comment_text',
101                $plaintextReason
102            );
103        }
104
105        if ( $continue !== null ) {
106            $cont = $this->parseContinueParamOrDie( $continue, [ 'timestamp', 'int' ] );
107            $op = $dir === 'older' ? '<=' : '>=';
108            $this->addWhere( $db->buildComparison( $op, [
109                'cul_timestamp' => $db->timestamp( $cont[0] ),
110                'cul_id' => $cont[1],
111            ] ) );
112        }
113
114        $res = $this->select( __METHOD__ );
115        $result = $this->getResult();
116
117        $count = 0;
118        foreach ( $res as $row ) {
119            if ( ++$count > $limit ) {
120                $this->setContinueEnumParameter( 'continue', "$row->cul_timestamp|$row->cul_id" );
121                break;
122            }
123            $log = [
124                'timestamp' => wfTimestamp( TS_ISO_8601, $row->cul_timestamp ),
125                'checkuser' => $row->actor_name,
126                'type'      => $row->cul_type,
127                'reason'    => $this->commentStore->getComment( 'cul_reason', $row )->text,
128                'target'    => $row->cul_target_text,
129            ];
130
131            $checkUser = $this->userFactory->newFromName( $row->actor_name );
132            if (
133                $checkUser &&
134                $checkUser->isHidden() &&
135                !$this->getAuthority()->isAllowed( 'hideuser' )
136            ) {
137                $log['checkuser'] = $this->msg( 'rev-deleted-user' )->plain();
138            }
139
140            $targetUser = $this->userFactory->newFromName( $row->cul_target_text );
141            if (
142                $targetUser &&
143                $targetUser->isHidden() &&
144                !$this->getAuthority()->isAllowed( 'hideuser' )
145            ) {
146                $log['target'] = $this->msg( 'rev-deleted-user' )->plain();
147            }
148            $fit = $result->addValue( [ 'query', $this->getModuleName(), 'entries' ], null, $log );
149            if ( !$fit ) {
150                $this->setContinueEnumParameter( 'continue', "$row->cul_timestamp|$row->cul_id" );
151                break;
152            }
153        }
154
155        $result->addIndexedTagName( [ 'query', $this->getModuleName(), 'entries' ], 'entry' );
156    }
157
158    /** @inheritDoc */
159    public function getAllowedParams() {
160        return [
161            'user'   => null,
162            'target' => null,
163            'reason' => null,
164            'limit'  => [
165                ParamValidator::PARAM_DEFAULT => 10,
166                ParamValidator::PARAM_TYPE => 'limit',
167                IntegerDef::PARAM_MIN  => 1,
168                IntegerDef::PARAM_MAX  => ApiBase::LIMIT_BIG1,
169                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
170            ],
171            'dir' => [
172                ParamValidator::PARAM_DEFAULT => 'older',
173                ParamValidator::PARAM_TYPE => [
174                    'newer',
175                    'older'
176                ],
177                ApiBase::PARAM_HELP_MSG => 'checkuser-api-help-param-direction',
178                ApiBase::PARAM_HELP_MSG_PER_VALUE => [
179                    'newer' => 'checkuser-api-help-paramvalue-direction-newer',
180                    'older' => 'checkuser-api-help-paramvalue-direction-older',
181                ],
182            ],
183            'from'  => [
184                ParamValidator::PARAM_TYPE => 'timestamp',
185            ],
186            'to'    => [
187                ParamValidator::PARAM_TYPE => 'timestamp',
188            ],
189            'continue' => [
190                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
191            ],
192        ];
193    }
194
195    /** @inheritDoc */
196    protected function getExamplesMessages() {
197        return [
198            'action=query&list=checkuserlog&culuser=Example&cullimit=25'
199                => 'apihelp-query+checkuserlog-example-1',
200            'action=query&list=checkuserlog&cultarget=192.0.2.0/24&culfrom=2011-10-15T23:00:00Z'
201                => 'apihelp-query+checkuserlog-example-2',
202        ];
203    }
204
205    /**
206     * @inheritDoc
207     */
208    public function getHelpUrls() {
209        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:CheckUser#API';
210    }
211}