Passed
Push — master ( 48da0f...5f527e )
by MusikAnimal
04:29
created

UserRepository::getAutomatedCountsSql()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 43
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 24
nc 3
nop 4
dl 0
loc 43
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the UserRepository class.
4
 */
5
6
namespace Xtools;
7
8
use Exception;
9
use Mediawiki\Api\SimpleRequest;
10
use Symfony\Component\DependencyInjection\Container;
11
use Symfony\Component\HttpFoundation\Session\Session;
12
13
/**
14
 * This class provides data for the User class.
15
 * @codeCoverageIgnore
16
 */
17
class UserRepository extends Repository
18
{
19
20
    /**
21
     * Convenience method to get a new User object.
22
     * @param string $username The username.
23
     * @param Container $container The DI container.
24
     * @return User
25
     */
26
    public static function getUser($username, Container $container)
27
    {
28
        $user = new User($username);
29
        $userRepo = new UserRepository();
30
        $userRepo->setContainer($container);
31
        $user->setRepository($userRepo);
32
        return $user;
33
    }
34
35
    /**
36
     * Get the user's ID.
37
     * @param string $databaseName The database to query.
38
     * @param string $username The username to find.
39
     * @return int
40
     */
41 View Code Duplication
    public function getId($databaseName, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
42
    {
43
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_id');
44
        if ($this->cache->hasItem($cacheKey)) {
45
            return $this->cache->getItem($cacheKey)->get();
46
        }
47
48
        $userTable = $this->getTableName($databaseName, 'user');
49
        $sql = "SELECT user_id FROM $userTable WHERE user_name = :username LIMIT 1";
50
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
51
        $resultQuery->bindParam('username', $username);
52
        $resultQuery->execute();
53
        $userId = (int)$resultQuery->fetchColumn();
54
55
        // Cache for 10 minutes and return.
56
        $this->setCache($cacheKey, $userId);
57
        return $userId;
58
    }
59
60
    /**
61
     * Get the user's registration date.
62
     * @param string $databaseName The database to query.
63
     * @param string $username The username to find.
64
     * @return string|null As returned by the database.
65
     */
66 View Code Duplication
    public function getRegistrationDate($databaseName, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
67
    {
68
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_registration');
69
        if ($this->cache->hasItem($cacheKey)) {
70
            return $this->cache->getItem($cacheKey)->get();
71
        }
72
73
        $userTable = $this->getTableName($databaseName, 'user');
74
        $sql = "SELECT user_registration FROM $userTable WHERE user_name = :username LIMIT 1";
75
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
76
        $resultQuery->bindParam('username', $username);
77
        $resultQuery->execute();
78
        $registrationDate = $resultQuery->fetchColumn();
79
80
        // Cache and return.
81
        $this->setCache($cacheKey, $registrationDate);
82
        return $registrationDate;
83
    }
84
85
    /**
86
     * Get the user's (system) edit count.
87
     * @param string $databaseName The database to query.
88
     * @param string $username The username to find.
89
     * @return int|null As returned by the database.
90
     */
91 View Code Duplication
    public function getEditCount($databaseName, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
92
    {
93
        $userTable = $this->getTableName($databaseName, 'user');
94
        $sql = "SELECT user_editcount FROM $userTable WHERE user_name = :username LIMIT 1";
95
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
96
        $resultQuery->bindParam('username', $username);
97
        $resultQuery->execute();
98
        return $resultQuery->fetchColumn();
99
    }
100
101
    /**
102
     * Get group names of the given user.
103
     * @param Project $project The project.
104
     * @param string $username The username.
105
     * @return string[]
106
     */
107
    public function getGroups(Project $project, $username)
108
    {
109
        // Use md5 to ensure the key does not contain reserved characters.
110
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_groups');
111
        if ($this->cache->hasItem($cacheKey)) {
112
            return $this->cache->getItem($cacheKey)->get();
113
        }
114
115
        $this->stopwatch->start($cacheKey, 'XTools');
116
        $api = $this->getMediawikiApi($project);
117
        $params = [
118
            'list' => 'users',
119
            'ususers' => $username,
120
            'usprop' => 'groups'
121
        ];
122
        $query = new SimpleRequest('query', $params);
123
        $result = [];
124
        $res = $api->getRequest($query);
125
        if (isset($res['batchcomplete']) && isset($res['query']['users'][0]['groups'])) {
126
            $result = $res['query']['users'][0]['groups'];
127
        }
128
129
        // Cache for 10 minutes, and return.
130
        $this->setCache($cacheKey, $result);
131
        $this->stopwatch->stop($cacheKey);
132
133
        return $result;
134
    }
135
136
    /**
137
     * Get a user's global group membership (starting at XTools' default project if none is
138
     * provided). This requires the CentralAuth extension to be installed.
139
     * @link https://www.mediawiki.org/wiki/Extension:CentralAuth
140
     * @param string $username The username.
141
     * @param Project $project The project to query.
142
     * @return string[]
143
     */
144
    public function getGlobalGroups($username, Project $project = null)
145
    {
146
        // Get the default project if not provided.
147
        if (!$project instanceof Project) {
148
            $project = ProjectRepository::getDefaultProject($this->container);
149
        }
150
151
        // Create the API query.
152
        $api = $this->getMediawikiApi($project);
153
        $params = [
154
            'meta' => 'globaluserinfo',
155
            'guiuser' => $username,
156
            'guiprop' => 'groups'
157
        ];
158
        $query = new SimpleRequest('query', $params);
159
160
        // Get the result.
161
        $res = $api->getRequest($query);
162
        $result = [];
163
        if (isset($res['batchcomplete']) && isset($res['query']['globaluserinfo']['groups'])) {
164
            $result = $res['query']['globaluserinfo']['groups'];
165
        }
166
        return $result;
167
    }
168
169
    /**
170
     * Search the ipblocks table to see if the user is currently blocked
171
     * and return the expiry if they are.
172
     * @param $databaseName The database to query.
0 ignored issues
show
Bug introduced by
The type Xtools\The was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
173
     * @param $userid The ID of the user to search for.
174
     * @return bool|string Expiry of active block or false
175
     */
176 View Code Duplication
    public function getBlockExpiry($databaseName, $userid)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
177
    {
178
        $ipblocksTable = $this->getTableName($databaseName, 'ipblocks');
179
        $sql = "SELECT ipb_expiry
180
                FROM $ipblocksTable
181
                WHERE ipb_user = :userid
182
                LIMIT 1";
183
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
184
        $resultQuery->bindParam('userid', $userid);
185
        $resultQuery->execute();
186
        return $resultQuery->fetchColumn();
187
    }
188
189
    /**
190
     * Get pages created by a user
191
     * @param Project $project
192
     * @param User $user
193
     * @param string|int $namespace Namespace ID or 'all'
194
     * @param string $redirects One of 'noredirects', 'onlyredirects' or blank for both
195
     * @return string[] Result of query, see below. Includes live and deleted pages.
196
     */
197
    public function getPagesCreated(Project $project, User $user, $namespace, $redirects)
198
    {
199
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_pages_created');
200
        if ($this->cache->hasItem($cacheKey)) {
201
            return $this->cache->getItem($cacheKey)->get();
202
        }
203
        $this->stopwatch->start($cacheKey, 'XTools');
204
205
        $pageTable = $project->getTableName('page');
206
        $pageAssessmentsTable = $project->getTableName('page_assessments');
207
        $revisionTable = $project->getTableName('revision');
208
        $archiveTable = $project->getTableName('archive');
209
        $logTable = $project->getTableName('logging', 'logindex');
210
211
        $username = $user->getUsername();
212
        $userId = $user->getId($project);
213
214
        $namespaceConditionArc = '';
215
        $namespaceConditionRev = '';
216
217
        if ($namespace != 'all') {
218
            $namespaceConditionRev = " AND page_namespace = '".intval($namespace)."' ";
219
            $namespaceConditionArc = " AND ar_namespace = '".intval($namespace)."' ";
220
        }
221
222
        $redirectCondition = '';
223
224
        if ($redirects == 'onlyredirects') {
225
            $redirectCondition = " AND page_is_redirect = '1' ";
226
        } elseif ($redirects == 'noredirects') {
227
            $redirectCondition = " AND page_is_redirect = '0' ";
228
        }
229
230
        if ($userId == 0) { // IP Editor or undefined username.
231
            $whereRev = " rev_user_text = '$username' AND rev_user = '0' ";
232
            $whereArc = " ar_user_text = '$username' AND ar_user = '0' ";
233
        } else {
234
            $whereRev = " rev_user = '$userId' AND rev_timestamp > 1 ";
235
            $whereArc = " ar_user = '$userId' AND ar_timestamp > 1 ";
236
        }
237
238
        $hasPageAssessments = $this->isLabs() && $project->hasPageAssessments();
239
        $paSelects = $hasPageAssessments ? ', pa_class, pa_importance, pa_page_revision' : '';
240
        $paSelectsArchive = $hasPageAssessments ?
241
            ', NULL AS pa_class, NULL AS pa_page_id, NULL AS pa_page_revision'
242
            : '';
243
        $paJoin = $hasPageAssessments ? "LEFT JOIN $pageAssessmentsTable ON rev_page = pa_page_id" : '';
244
245
        $sql = "
246
            (
247
                SELECT DISTINCT page_namespace AS namespace, 'rev' AS type, page_title AS page_title,
248
                    page_len, page_is_redirect, rev_timestamp AS rev_timestamp,
249
                    rev_user, rev_user_text AS username, rev_len, rev_id $paSelects
250
                FROM $pageTable
251
                JOIN $revisionTable ON page_id = rev_page
252
                $paJoin
253
                WHERE $whereRev AND rev_parent_id = '0' $namespaceConditionRev $redirectCondition" .
254
                ($hasPageAssessments ? 'GROUP BY rev_page' : '') . "
255
            )
256
257
            UNION
258
259
            (
260
                SELECT ar_namespace AS namespace, 'arc' AS type, ar_title AS page_title,
261
                    0 AS page_len, '0' AS page_is_redirect, MIN(ar_timestamp) AS rev_timestamp,
262
                    ar_user AS rev_user, ar_user_text AS username, ar_len AS rev_len,
263
                    ar_rev_id AS rev_id $paSelectsArchive
264
                FROM $archiveTable
265
                LEFT JOIN $logTable ON log_namespace = ar_namespace AND log_title = ar_title
266
                    AND log_user = ar_user AND (log_action = 'move' OR log_action = 'move_redir')
267
                    AND log_type = 'move'
268
                WHERE $whereArc AND ar_parent_id = '0' $namespaceConditionArc AND log_action IS NULL
269
                GROUP BY ar_namespace, ar_title
270
            )";
271
272
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
273
        $resultQuery->execute();
274
        $result = $resultQuery->fetchAll();
275
276
        // Cache for 10 minutes, and return.
277
        $this->setCache($cacheKey, $result);
278
        $this->stopwatch->stop($cacheKey);
279
280
        return $result;
281
    }
282
283
    /**
284
     * Get edit count within given timeframe and namespace.
285
     * @param Project $project
286
     * @param User $user
287
     * @param int|string $namespace Namespace ID or 'all' for all namespaces
288
     * @param string $start Start date in a format accepted by strtotime()
289
     * @param string $end End date in a format accepted by strtotime()
290
     */
291
    public function countEdits(Project $project, User $user, $namespace = 'all', $start = '', $end = '')
292
    {
293
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_editcount');
294
        if ($this->cache->hasItem($cacheKey)) {
295
            return $this->cache->getItem($cacheKey)->get();
296
        }
297
298
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
299
        list($pageJoin, $condNamespace) = $this->getPageAndNamespaceSql($project, $namespace);
300
        $revisionTable = $project->getTableName('revision');
301
302
        $sql = "SELECT COUNT(rev_id)
303
                FROM $revisionTable
304
                $pageJoin
305
                WHERE rev_user_text = :username
306
                $condNamespace
307
                $condBegin
308
                $condEnd";
309
310
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
311
        $result = $resultQuery->fetchColumn();
312
313
        // Cache for 10 minutes, and return.
314
        $this->setCache($cacheKey, $result);
315
316
        return $result;
317
    }
318
319
    /**
320
     * Get information about the currently-logged in user.
321
     * @return array
322
     */
323
    public function getXtoolsUserInfo()
324
    {
325
        /** @var Session $session */
326
        $session = $this->container->get('session');
327
        return $session->get('logged_in_user');
328
    }
329
330
    /**
331
     * Maximum number of edits to process, based on configuration.
332
     * @return int
333
     */
334
    public function maxEdits()
335
    {
336
        return $this->container->getParameter('app.max_user_edits');
337
    }
338
339
    /**
340
     * Get SQL clauses for joining on `page` and restricting to a namespace.
341
     * @param  Project $project
342
     * @param  int|string $namespace Namespace ID or 'all' for all namespaces.
343
     * @return array [page join clause, page namespace clause]
344
     */
345
    protected function getPageAndNamespaceSql(Project $project, $namespace)
346
    {
347
        if ($namespace === 'all') {
348
            return [null, null];
349
        }
350
351
        $pageTable = $project->getTableName('page');
352
        $pageJoin = $namespace !== 'all' ? "LEFT JOIN $pageTable ON rev_page = page_id" : null;
353
        $condNamespace = 'AND page_namespace = :namespace';
354
355
        return [$pageJoin, $condNamespace];
356
    }
357
358
    /**
359
     * Get SQL clauses for rev_timestamp, based on whether values for
360
     * the given start and end parameters exist.
361
     * @param  string $start
362
     * @param  string $end
363
     * @return string[] Clauses for start and end timestamps.
364
     */
365
    protected function getRevTimestampConditions($start, $end)
366
    {
367
        $condBegin = '';
368
        $condEnd = '';
369
370
        if (!empty($start)) {
371
            $condBegin = 'AND rev_timestamp >= :start ';
372
        }
373
        if (!empty($end)) {
374
            $condEnd = 'AND rev_timestamp <= :end ';
375
        }
376
377
        return [$condBegin, $condEnd];
378
    }
379
380
    /**
381
     * Prepare the given SQL, bind the given parameters, and execute the Doctrine Statement.
382
     * @param  string $sql
383
     * @param  User   $user
384
     * @param  string $namespace
385
     * @param  string $start
386
     * @param  string $end
387
     * @return Doctrine\DBAL\Statement
0 ignored issues
show
Bug introduced by
The type Xtools\Doctrine\DBAL\Statement was not found. Did you mean Doctrine\DBAL\Statement? If so, make sure to prefix the type with \.
Loading history...
388
     */
389
    protected function executeQuery($sql, User $user, $namespace = 'all', $start = '', $end = '')
390
    {
391
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
392
        $username = $user->getUsername();
393
        $resultQuery->bindParam('username', $username);
394
395
        if (!empty($start)) {
396
            $start = date('Ymd000000', strtotime($start));
0 ignored issues
show
Bug introduced by
It seems like strtotime($start) can also be of type false; however, parameter $timestamp of date() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

396
            $start = date('Ymd000000', /** @scrutinizer ignore-type */ strtotime($start));
Loading history...
397
            $resultQuery->bindParam('start', $start);
398
        }
399
        if (!empty($end)) {
400
            $end = date('Ymd235959', strtotime($end));
401
            $resultQuery->bindParam('end', $end);
402
        }
403
        if ($namespace !== 'all') {
404
            $resultQuery->bindParam('namespace', $namespace);
405
        }
406
407
        $resultQuery->execute();
408
409
        return $resultQuery;
410
    }
411
}
412