Passed
Push — master ( 4c7d84...48da0f )
by MusikAnimal
04:20
created

UserRepository::getGlobalGroups()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 13
nc 4
nop 2
dl 0
loc 23
rs 8.7972
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 the number of edits this user made using semi-automated tools.
321
     * @param Project $project
322
     * @param User $user
323
     * @param string|int $namespace Namespace ID or 'all'
324
     * @param string $start Start date in a format accepted by strtotime()
325
     * @param string $end End date in a format accepted by strtotime()
326
     * @return int Result of query, see below.
327
     */
328
    public function countAutomatedEdits(Project $project, User $user, $namespace = 'all', $start = '', $end = '')
329
    {
330
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoeditcount');
331
        if ($this->cache->hasItem($cacheKey)) {
332
            return $this->cache->getItem($cacheKey)->get();
333
        }
334
        $this->stopwatch->start($cacheKey, 'XTools');
335
336
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
337
338
        // Get the combined regex and tags for the tools
339
        list($regex, $tags) = $this->getToolRegexAndTags($project->getDomain());
340
341
        list($pageJoin, $condNamespace) = $this->getPageAndNamespaceSql($project, $namespace);
342
343
        $revisionTable = $project->getTableName('revision');
344
        $tagTable = $project->getTableName('change_tag');
345
        $tagJoin = '';
346
347
        // Build SQL for detecting autoedits via regex and/or tags
348
        $condTools = [];
349
        if ($regex != '') {
350
            $condTools[] = "rev_comment REGEXP $regex";
351
        }
352
        if ($tags != '') {
353
            $tagJoin = $tags != '' ? "LEFT OUTER JOIN $tagTable ON ct_rev_id = rev_id" : '';
354
            $condTools[] = "ct_tag IN ($tags)";
355
        }
356
        $condTool = 'AND (' . implode(' OR ', $condTools) . ')';
357
358
        $sql = "SELECT COUNT(DISTINCT(rev_id))
359
                FROM $revisionTable
360
                $pageJoin
361
                $tagJoin
362
                WHERE rev_user_text = :username
363
                $condTool
364
                $condNamespace
365
                $condBegin
366
                $condEnd";
367
368
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
369
        $result = (int) $resultQuery->fetchColumn();
370
371
        // Cache for 10 minutes, and return.
372
        $this->setCache($cacheKey, $result);
373
        $this->stopwatch->stop($cacheKey);
374
375
        return $result;
376
    }
377
378
    /**
379
     * Get non-automated contributions for the given user.
380
     * @param Project $project
381
     * @param User $user
382
     * @param string|int $namespace Namespace ID or 'all'
383
     * @param string $start Start date in a format accepted by strtotime()
384
     * @param string $end End date in a format accepted by strtotime()
385
     * @param int $offset Used for pagination, offset results by N edits
386
     * @return string[] Result of query, with columns 'page_title',
387
     *   'page_namespace', 'rev_id', 'timestamp', 'minor',
388
     *   'length', 'length_change', 'comment'
389
     */
390
    public function getNonAutomatedEdits(
391
        Project $project,
392
        User $user,
393
        $namespace = 'all',
394
        $start = '',
395
        $end = '',
396
        $offset = 0
397
    ) {
398
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_nonautoedits');
399
        if ($this->cache->hasItem($cacheKey)) {
400
            return $this->cache->getItem($cacheKey)->get();
401
        }
402
        $this->stopwatch->start($cacheKey, 'XTools');
403
404
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
405
406
        // Get the combined regex and tags for the tools
407
        list($regex, $tags) = $this->getToolRegexAndTags($project->getDomain());
408
409
        $pageTable = $project->getTableName('page');
410
        $revisionTable = $project->getTableName('revision');
411
        $tagTable = $project->getTableName('change_tag');
412
        $condNamespace = $namespace === 'all' ? '' : 'AND page_namespace = :namespace';
413
        $tagJoin = $tags != '' ? "LEFT OUTER JOIN $tagTable ON (ct_rev_id = revs.rev_id)" : '';
414
        $condTag = $tags != '' ? "AND (ct_tag NOT IN ($tags) OR ct_tag IS NULL)" : '';
415
        $sql = "SELECT
416
                    page_title,
417
                    page_namespace,
418
                    revs.rev_id AS rev_id,
419
                    revs.rev_timestamp AS timestamp,
420
                    revs.rev_minor_edit AS minor,
421
                    revs.rev_len AS length,
422
                    (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change,
423
                    revs.rev_comment AS comment
424
                FROM $pageTable
425
                JOIN $revisionTable AS revs ON (page_id = revs.rev_page)
426
                LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
427
                $tagJoin
428
                WHERE revs.rev_user_text = :username
429
                AND revs.rev_timestamp > 0
430
                AND revs.rev_comment NOT RLIKE $regex
431
                $condTag
432
                $condBegin
433
                $condEnd
434
                $condNamespace
435
                ORDER BY revs.rev_timestamp DESC
436
                LIMIT 50
437
                OFFSET $offset";
438
439
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
440
        $result = $resultQuery->fetchAll();
441
442
        // Cache for 10 minutes, and return.
443
        $this->setCache($cacheKey, $result);
444
        $this->stopwatch->stop($cacheKey);
445
446
        return $result;
447
    }
448
449
    /**
450
     * Get counts of known automated tools used by the given user.
451
     * @param Project $project
452
     * @param User $user
453
     * @param string|int $namespace Namespace ID or 'all'.
454
     * @param string $start Start date in a format accepted by strtotime()
455
     * @param string $end End date in a format accepted by strtotime()
456
     * @return string[] Each tool that they used along with the count and link:
457
     *                  [
458
     *                      'Twinkle' => [
459
     *                          'count' => 50,
460
     *                          'link' => 'Wikipedia:Twinkle',
461
     *                      ],
462
     *                  ]
463
     */
464
    public function getAutomatedCounts(
465
        Project $project,
466
        User $user,
467
        $namespace = 'all',
468
        $start = '',
469
        $end = ''
470
    ) {
471
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_autotoolcounts');
472
        if ($this->cache->hasItem($cacheKey)) {
473
            return $this->cache->getItem($cacheKey)->get();
474
        }
475
        $this->stopwatch->start($cacheKey, 'XTools');
476
477
        $sql = $this->getAutomatedCountsSql($project, $namespace, $start, $end);
478
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
479
480
        $automatedEditsHelper = $this->container->get('app.automated_edits_helper');
481
        $tools = $automatedEditsHelper->getTools($project->getDomain());
482
483
        // handling results
484
        $results = [];
485
486
        while ($row = $resultQuery->fetch()) {
487
            // Only track tools that they've used at least once
488
            $tool = $row['toolname'];
489
            if ($row['count'] > 0) {
490
                $results[$tool] = [
491
                    'link' => $tools[$tool]['link'],
492
                    'count' => $row['count'],
493
                ];
494
            }
495
        }
496
497
        // Sort the array by count
498
        uasort($results, function ($a, $b) {
499
            return $b['count'] - $a['count'];
500
        });
501
502
        // Cache for 10 minutes, and return.
503
        $this->setCache($cacheKey, $results);
504
        $this->stopwatch->stop($cacheKey);
505
506
        return $results;
507
    }
508
509
    /**
510
     * Get SQL for getting counts of known automated tools used by the given user.
511
     * @see self::getAutomatedCounts()
512
     * @param Project $project
513
     * @param string|int $namespace Namespace ID or 'all'.
514
     * @param string $start Start date in a format accepted by strtotime()
515
     * @param string $end End date in a format accepted by strtotime()
516
     * @return string The SQL.
517
     */
518
    private function getAutomatedCountsSql(Project $project, $namespace, $start, $end)
519
    {
520
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
521
522
        // Load the semi-automated edit types.
523
        $automatedEditsHelper = $this->container->get('app.automated_edits_helper');
524
        $tools = $automatedEditsHelper->getTools($project->getDomain());
525
526
        // Create a collection of queries that we're going to run.
527
        $queries = [];
528
529
        $revisionTable = $project->getTableName('revision');
530
        $tagTable = $project->getTableName('change_tag');
531
532
        list($pageJoin, $condNamespace) = $this->getPageAndNamespaceSql($project, $namespace);
533
534
        $conn = $this->getProjectsConnection();
535
536
        foreach ($tools as $toolname => $values) {
537
            list($condTool, $tagJoin) = $this->getInnerAutomatedCountsSql($tagTable, $values);
538
539
            $toolname = $conn->quote($toolname, \PDO::PARAM_STR);
540
541
            // Developer error, no regex or tag provided for this tool.
542
            if ($condTool === '') {
543
                throw new Exception("No regex or tag found for the tool $toolname. " .
544
                    "Please verify this entry in semi_automated.yml");
545
            }
546
547
            $queries[] .= "
548
                SELECT $toolname AS toolname, COUNT(rev_id) AS count
549
                FROM $revisionTable
550
                $pageJoin
551
                $tagJoin
552
                WHERE rev_user_text = :username
553
                AND $condTool
554
                $condNamespace
555
                $condBegin
556
                $condEnd";
557
        }
558
559
        // Combine to one big query.
560
        return implode(' UNION ', $queries);
561
    }
562
563
    /**
564
     * Get SQL clauses for joining on `page` and restricting to a namespace.
565
     * @param  Project $project
566
     * @param  int|string $namespace Namespace ID or 'all' for all namespaces.
567
     * @return array [page join clause, page namespace clause]
568
     */
569
    private function getPageAndNamespaceSql(Project $project, $namespace)
570
    {
571
        if ($namespace === 'all') {
572
            return [null, null];
573
        }
574
575
        $pageTable = $project->getTableName('page');
576
        $pageJoin = $namespace !== 'all' ? "LEFT JOIN $pageTable ON rev_page = page_id" : null;
577
        $condNamespace = 'AND page_namespace = :namespace';
578
579
        return [$pageJoin, $condNamespace];
580
    }
581
582
    /**
583
     * Get some of the inner SQL for self::getAutomatedCountsSql().
584
     * @param  string $tagTable Name of the `change_tag` table.
585
     * @param  string[] $values Values as defined in semi_automated.yml
586
     * @return string[] [Equality clause, JOIN clause]
587
     */
588
    private function getInnerAutomatedCountsSql($tagTable, $values)
589
    {
590
        $conn = $this->getProjectsConnection();
591
        $tagJoin = '';
592
        $condTool = '';
593
594
        if (isset($values['regex'])) {
595
            $regex = $conn->quote($values['regex'], \PDO::PARAM_STR);
596
            $condTool = "rev_comment REGEXP $regex";
597
        }
598
        if (isset($values['tag'])) {
599
            $tagJoin = "LEFT OUTER JOIN $tagTable ON ct_rev_id = rev_id";
600
            $tag = $conn->quote($values['tag'], \PDO::PARAM_STR);
601
602
            // Append to regex clause if already present.
603
            // Tags are more reliable but may not be present for edits made with
604
            //   older versions of the tool, before it started adding tags.
605
            if ($condTool === '') {
606
                $condTool = "ct_tag = $tag";
607
            } else {
608
                $condTool = '(' . $condTool . " OR ct_tag = $tag)";
609
            }
610
        }
611
612
        return [$condTool, $tagJoin];
613
    }
614
615
    /**
616
     * Get information about the currently-logged in user.
617
     * @return array
618
     */
619
    public function getXtoolsUserInfo()
620
    {
621
        /** @var Session $session */
622
        $session = $this->container->get('session');
623
        return $session->get('logged_in_user');
624
    }
625
626
    /**
627
     * Maximum number of edits to process, based on configuration.
628
     * @return int
629
     */
630
    public function maxEdits()
631
    {
632
        return $this->container->getParameter('app.max_user_edits');
633
    }
634
635
    /**
636
     * Get the combined regex and tags for all semi-automated tools,
637
     *   ready to be used in a query.
638
     * @param string $projectDomain Such as en.wikipedia.org
639
     * @return string[] In the format:
640
     *    ['combined|regex', 'combined,tags']
641
     */
642
    private function getToolRegexAndTags($projectDomain)
643
    {
644
        $conn = $this->getProjectsConnection();
645
        $automatedEditsHelper = $this->container->get('app.automated_edits_helper');
646
        $tools = $automatedEditsHelper->getTools($projectDomain);
647
        $regexes = [];
648
        $tags = [];
649
650
        foreach ($tools as $tool => $values) {
651
            if (isset($values['regex'])) {
652
                $regexes[] = $values['regex'];
653
            }
654
            if (isset($values['tag'])) {
655
                $tags[] = $conn->quote($values['tag'], \PDO::PARAM_STR);
656
            }
657
        }
658
659
        return [
660
            $conn->quote(implode('|', $regexes), \PDO::PARAM_STR),
661
            implode(',', $tags),
662
        ];
663
    }
664
665
    /**
666
     * Get SQL clauses for rev_timestamp, based on whether values for
667
     * the given start and end parameters exist.
668
     * @param  string $start
669
     * @param  string $end
670
     * @return string[] Clauses for start and end timestamps.
671
     */
672
    private function getRevTimestampConditions($start, $end)
673
    {
674
        $condBegin = '';
675
        $condEnd = '';
676
677
        if (!empty($start)) {
678
            $condBegin = 'AND rev_timestamp >= :start ';
679
        }
680
        if (!empty($end)) {
681
            $condEnd = 'AND rev_timestamp <= :end ';
682
        }
683
684
        return [$condBegin, $condEnd];
685
    }
686
687
    /**
688
     * Prepare the given SQL, bind the given parameters, and execute the Doctrine Statement.
689
     * @param  string $sql
690
     * @param  User   $user
691
     * @param  string $namespace
692
     * @param  string $start
693
     * @param  string $end
694
     * @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...
695
     */
696
    private function executeQuery($sql, User $user, $namespace = 'all', $start = '', $end = '')
697
    {
698
        $resultQuery = $this->getProjectsConnection()->prepare($sql);
699
        $username = $user->getUsername();
700
        $resultQuery->bindParam('username', $username);
701
702
        if (!empty($start)) {
703
            $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

703
            $start = date('Ymd000000', /** @scrutinizer ignore-type */ strtotime($start));
Loading history...
704
            $resultQuery->bindParam('start', $start);
705
        }
706
        if (!empty($end)) {
707
            $end = date('Ymd235959', strtotime($end));
708
            $resultQuery->bindParam('end', $end);
709
        }
710
        if ($namespace !== 'all') {
711
            $resultQuery->bindParam('namespace', $namespace);
712
        }
713
714
        $resultQuery->execute();
715
716
        return $resultQuery;
717
    }
718
}
719