Passed
Push — master ( b25a2f...9287df )
by MusikAnimal
06:36
created

EditCounterRepository::formatProjectsWikiEdits()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the EditCounterRepository class.
4
 */
5
6
declare(strict_types = 1);
7
8
namespace AppBundle\Repository;
9
10
use AppBundle\Model\Project;
11
use AppBundle\Model\User;
12
use Mediawiki\Api\SimpleRequest;
13
14
/**
15
 * An EditCounterRepository is responsible for retrieving edit count information from the
16
 * databases and API. It doesn't do any post-processing of that information.
17
 * @codeCoverageIgnore
18
 */
19
class EditCounterRepository extends UserRightsRepository
20
{
21
    /**
22
     * Get data about revisions, pages, etc.
23
     * @param Project $project The project.
24
     * @param User $user The user.
25
     * @return string[] With keys: 'deleted', 'live', 'total', '24h', '7d', '30d',
26
     * '365d', 'small', 'large', 'with_comments', and 'minor_edits', ...
27
     */
28
    public function getPairData(Project $project, User $user): array
29
    {
30
        // Set up cache.
31
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_pairdata');
32
        if ($this->cache->hasItem($cacheKey)) {
33
            return $this->cache->getItem($cacheKey)->get();
34
        }
35
36
        // Prepare the queries and execute them.
37
        $archiveTable = $project->getTableName('archive');
38
        $revisionTable = $project->getTableName('revision');
39
40
        $sql = "
41
            -- Revision counts.
42
            (SELECT 'deleted' AS `key`, COUNT(ar_id) AS val FROM $archiveTable
43
                WHERE ar_actor = :actorId
44
            ) UNION (
45
            SELECT 'live' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
46
                WHERE rev_actor = :actorId
47
            ) UNION (
48
            SELECT 'day' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
49
                WHERE rev_actor = :actorId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)
50
            ) UNION (
51
            SELECT 'week' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
52
                WHERE rev_actor = :actorId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
53
            ) UNION (
54
            SELECT 'month' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
55
                WHERE rev_actor = :actorId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
56
            ) UNION (
57
            SELECT 'year' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
58
                WHERE rev_actor = :actorId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
59
            ) UNION (
60
            SELECT 'minor' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
61
                WHERE rev_actor = :actorId AND rev_minor_edit = 1
62
63
            -- Page counts.
64
            ) UNION (
65
            SELECT 'edited-live' AS `key`, COUNT(DISTINCT rev_page) AS `val`
66
                FROM $revisionTable
67
                WHERE rev_actor = :actorId
68
            ) UNION (
69
            SELECT 'edited-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val`
70
                FROM $archiveTable
71
                WHERE ar_actor = :actorId
72
            ) UNION (
73
            SELECT 'created-live' AS `key`, COUNT(DISTINCT rev_page) AS `val`
74
                FROM $revisionTable
75
                WHERE rev_actor = :actorId AND rev_parent_id = 0
76
            ) UNION (
77
            SELECT 'created-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val`
78
                FROM $archiveTable
79
                WHERE ar_actor = :actorId AND ar_parent_id = 0
80
            )
81
        ";
82
83
        $resultQuery = $this->executeProjectsQuery($sql, [
84
            'actorId' => $user->getActorId($project),
85
        ]);
86
87
        $revisionCounts = [];
88
        while ($result = $resultQuery->fetch()) {
89
            $revisionCounts[$result['key']] = (int)$result['val'];
90
        }
91
92
        // Cache and return.
93
        return $this->setCache($cacheKey, $revisionCounts);
94
    }
95
96
    /**
97
     * Get log totals for a user.
98
     * @param Project $project The project.
99
     * @param User $user The user.
100
     * @return integer[] Keys are "<log>-<action>" strings, values are counts.
101
     */
102
    public function getLogCounts(Project $project, User $user): array
103
    {
104
        // Set up cache.
105
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_logcounts');
106
        if ($this->cache->hasItem($cacheKey)) {
107
            return $this->cache->getItem($cacheKey)->get();
108
        }
109
110
        // Query.
111
        $loggingTable = $this->getTableName($project->getDatabaseName(), 'logging');
112
        $sql = "
113
        (SELECT CONCAT(log_type, '-', log_action) AS source, COUNT(log_id) AS value
114
            FROM $loggingTable
115
            WHERE log_actor = :actorId
116
            GROUP BY log_type, log_action
117
        )";
118
119
        $results = $this->executeProjectsQuery($sql, [
120
            'actorId' => $user->getActorId($project),
121
        ])->fetchAll();
122
123
        $logCounts = array_combine(
124
            array_map(function ($e) {
125
                return $e['source'];
126
            }, $results),
127
            array_map(function ($e) {
128
                return (int)$e['value'];
129
            }, $results)
130
        );
131
132
        // Make sure there is some value for each of the wanted counts.
133
        $requiredCounts = [
134
            'thanks-thank',
135
            'review-approve',
136
            'newusers-create2',
137
            'newusers-byemail',
138
            'patrol-patrol',
139
            'block-block',
140
            'block-reblock',
141
            'block-unblock',
142
            'protect-protect',
143
            'protect-modify',
144
            'protect-unprotect',
145
            'rights-rights',
146
            'move-move',
147
            'delete-delete',
148
            'delete-revision',
149
            'delete-restore',
150
            'import-import',
151
            'import-interwiki',
152
            'import-upload',
153
            'upload-upload',
154
            'upload-overwrite',
155
            'abusefilter-modify',
156
            'merge-merge',
157
        ];
158
        foreach ($requiredCounts as $req) {
159
            if (!isset($logCounts[$req])) {
160
                $logCounts[$req] = 0;
161
            }
162
        }
163
164
        // Add Commons upload count, if applicable.
165
        $logCounts['files_uploaded_commons'] = 0;
166
        if ($this->isLabs() && !$user->isAnon()) {
167
            $sql = "SELECT COUNT(log_id)
168
                    FROM commonswiki_p.logging_userindex
169
                    JOIN commonswiki_p.actor ON actor_id = log_actor
170
                    WHERE log_type = 'upload' AND log_action = 'upload'
171
                        AND actor_name = :username";
172
            $resultQuery = $this->executeProjectsQuery($sql, [
173
                'username' => $user->getUsername(),
174
            ]);
175
            $logCounts['files_uploaded_commons'] = (int)$resultQuery->fetchColumn();
176
        }
177
178
        // Cache and return.
179
        return $this->setCache($cacheKey, $logCounts);
180
    }
181
182
    /**
183
     * Get the IDs and timestamps of the latest edit and logged action by the given user.
184
     * @param Project $project
185
     * @param User $user
186
     * @return string[] With keys 'rev_first', 'rev_latest', 'log_latest'.
187
     */
188
    public function getFirstAndLatestActions(Project $project, User $user): array
189
    {
190
        $loggingTable = $project->getTableName('logging', 'userindex');
191
        $revisionTable = $project->getTableName('revision');
192
193
        $sql = "(
194
                    SELECT 'rev_first' AS `key`, rev_id AS `id`,
195
                        rev_timestamp AS `timestamp`, NULL as `type`
196
                    FROM $revisionTable
197
                    WHERE rev_actor = :actorId
198
                    LIMIT 1
199
                ) UNION (
200
                    SELECT 'rev_latest' AS `key`, rev_id AS `id`,
201
                        rev_timestamp AS `timestamp`, NULL as `type`
202
                    FROM $revisionTable
203
                    WHERE rev_actor = :actorId
204
                    ORDER BY rev_timestamp DESC LIMIT 1
205
                ) UNION (
206
                    SELECT 'log_latest' AS `key`, log_id AS `id`,
207
                        log_timestamp AS `timestamp`, log_type AS `type`
208
                    FROM $loggingTable
209
                    WHERE log_actor = :actorId
210
                    ORDER BY log_timestamp DESC LIMIT 1
211
                )";
212
213
        $resultQuery = $this->executeProjectsQuery($sql, [
214
            'actorId' => $user->getActorId($project),
215
        ]);
216
217
        $actions = [];
218
        while ($result = $resultQuery->fetch()) {
219
            $actions[$result['key']] = [
220
                'id' => $result['id'],
221
                'timestamp' => $result['timestamp'],
222
                'type' => $result['type'],
223
            ];
224
        }
225
226
        return $actions;
227
    }
228
229
    /**
230
     * Get data for all blocks set on the given user.
231
     * @param Project $project
232
     * @param User $user
233
     * @return array
234
     */
235
    public function getBlocksReceived(Project $project, User $user): array
236
    {
237
        $loggingTable = $this->getTableName($project->getDatabaseName(), 'logging', 'logindex');
238
        $sql = "SELECT log_action, log_timestamp, log_params FROM $loggingTable
239
                WHERE log_type = 'block'
240
                AND log_action IN ('block', 'reblock', 'unblock')
241
                AND log_timestamp > 0
242
                AND log_title = :username
243
                AND log_namespace = 2
244
                ORDER BY log_timestamp ASC";
245
        $username = str_replace(' ', '_', $user->getUsername());
246
247
        return $this->executeProjectsQuery($sql, [
248
            'username' => $username,
249
        ])->fetchAll();
250
    }
251
252
    /**
253
     * Get a user's edit count for each project.
254
     * @see EditCounterRepository::globalEditCountsFromCentralAuth()
255
     * @see EditCounterRepository::globalEditCountsFromDatabases()
256
     * @param User $user The user.
257
     * @param Project $project The project to start from.
258
     * @return mixed[] Elements are arrays with 'project' (Project), and 'total' (int).
259
     */
260
    public function globalEditCounts(User $user, Project $project): array
261
    {
262
        // Get the edit counts from CentralAuth or database.
263
        $editCounts = $this->globalEditCountsFromCentralAuth($user, $project);
264
        if (false === $editCounts) {
265
            $editCounts = $this->globalEditCountsFromDatabases($user, $project);
266
        }
267
268
        // Pre-populate all projects' metadata, to prevent each project call from fetching it.
269
        $project->getRepository()->getAll();
0 ignored issues
show
Bug introduced by
The method getAll() does not exist on AppBundle\Repository\Repository. It seems like you code against a sub-type of AppBundle\Repository\Repository such as AppBundle\Repository\ProjectRepository. ( Ignorable by Annotation )

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

269
        $project->getRepository()->/** @scrutinizer ignore-call */ getAll();
Loading history...
270
271
        // Compile the output.
272
        $out = [];
273
        foreach ($editCounts as $editCount) {
274
            $out[] = [
275
                'project' => ProjectRepository::getProject($editCount['dbName'], $this->container),
276
                'total' => $editCount['total'],
277
            ];
278
        }
279
        return $out;
280
    }
281
282
    /**
283
     * Get a user's total edit count on one or more project.
284
     * Requires the CentralAuth extension to be installed on the project.
285
     *
286
     * @param User $user The user.
287
     * @param Project $project The project to start from.
288
     * @return mixed[]|false Elements are arrays with 'dbName' (string), and 'total' (int). False for logged out users.
289
     */
290
    protected function globalEditCountsFromCentralAuth(User $user, Project $project)
291
    {
292
        if (true === $user->isAnon()) {
293
            return false;
294
        }
295
296
        // Set up cache.
297
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_globaleditcounts');
298
        if ($this->cache->hasItem($cacheKey)) {
299
            return $this->cache->getItem($cacheKey)->get();
300
        }
301
302
        $this->log->debug(__METHOD__." Getting global edit counts from for ".$user->getUsername());
303
304
        // Load all projects, so it doesn't have to request metadata about each one as it goes.
305
        $project->getRepository()->getAll();
306
307
        $api = $this->getMediawikiApi($project);
308
        $params = [
309
            'meta' => 'globaluserinfo',
310
            'guiprop' => 'editcount|merged',
311
            'guiuser' => $user->getUsername(),
312
        ];
313
        $query = new SimpleRequest('query', $params);
314
        $result = $api->getRequest($query);
315
        if (!isset($result['query']['globaluserinfo']['merged'])) {
316
            return [];
317
        }
318
        $out = [];
319
        foreach ($result['query']['globaluserinfo']['merged'] as $result) {
320
            $out[] = [
321
                'dbName' => $result['wiki'],
322
                'total' => $result['editcount'],
323
            ];
324
        }
325
326
        // Cache and return.
327
        return $this->setCache($cacheKey, $out);
328
    }
329
330
    /**
331
     * Get total edit counts from all projects for this user.
332
     * @param User $user The user.
333
     * @param Project $project The project to start from.
334
     * @return mixed[] Elements are arrays with 'dbName' (string), and 'total' (int).
335
     * @see EditCounterRepository::globalEditCountsFromCentralAuth()
336
     */
337
    protected function globalEditCountsFromDatabases(User $user, Project $project): array
338
    {
339
        $this->log->debug(__METHOD__." Getting global edit counts for ".$user->getUsername());
340
        $allProjects = $project->getRepository()->getAll();
341
        $topEditCounts = [];
342
        foreach ($allProjects as $projectMeta) {
343
            $revisionTableName = $this->getTableName($projectMeta['dbName'], 'revision');
344
            $actorTable = $this->getTableName($projectMeta['dbName'], 'actor', 'revision');
345
346
            $sql = "SELECT COUNT(rev_id)
347
                FROM $revisionTableName
348
                JOIN $actorTable ON rev_actor = actor_id
349
                WHERE actor_name = :actor";
350
351
            $resultQuery = $this->executeProjectsQuery($sql, [
352
                'actor' => $user->getUsername(),
353
            ]);
354
            $total = (int)$resultQuery->fetchColumn();
355
            $topEditCounts[] = [
356
                'dbName' => $projectMeta['dbName'],
357
                'total' => $total,
358
            ];
359
        }
360
        return $topEditCounts;
361
    }
362
363
    /**
364
     * Needed because we can't cache objects (Project in this case).
365
     * @see self::getProjectsWikiEdits()
366
     * @param string[] $databases
367
     * @return mixed[] Keys are database names, values are Project objects.
368
     */
369
    private function formatProjectsWikiEdits(array $databases): array
370
    {
371
        $projects = [];
372
        foreach ($databases as $database) {
373
            $projects[$database] = ProjectRepository::getProject($database, $this->container);
374
        }
375
        return $projects;
376
    }
377
378
    /**
379
     * Get Projects that the user has made at least one edit on.
380
     * @param User $user
381
     * @return mixed[] Keys are database names, values are Projects.
382
     */
383
    public function getProjectsWithEdits(User $user): array
384
    {
385
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_projects_with_edits');
386
        if ($this->cache->hasItem($cacheKey)) {
387
            return $this->formatProjectsWikiEdits($this->cache->getItem($cacheKey)->get());
388
        }
389
390
        $projectRepo = new ProjectRepository();
391
        $projectRepo->setContainer($this->container);
392
        $allProjects = $projectRepo->getAll();
393
        $databases = [];
394
395
        foreach ($allProjects as $projectMeta) {
396
            $actorTable = $this->getTableName($projectMeta['dbName'], 'actor', 'revision');
397
            $sql = "SELECT 1 FROM $actorTable WHERE actor_name = :actor";
398
399
            $resultQuery = $this->executeProjectsQuery($sql, [
400
                'actor' => $user->getUsername(),
401
            ]);
402
            if ($resultQuery->fetch()) {
403
                $databases[] = $projectMeta['dbName'];
404
            }
405
        }
406
407
        $projects = $this->setCache($cacheKey, $databases);
408
409
        return $this->formatProjectsWikiEdits($projects);
410
    }
411
412
    /**
413
     * Get the given user's total edit counts per namespace on the given project.
414
     * @param Project $project The project.
415
     * @param User $user The user.
416
     * @return array Array keys are namespace IDs, values are the edit counts.
417
     */
418
    public function getNamespaceTotals(Project $project, User $user): array
419
    {
420
        // Cache?
421
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_namespacetotals');
422
        if ($this->cache->hasItem($cacheKey)) {
423
            return $this->cache->getItem($cacheKey)->get();
424
        }
425
426
        // Query.
427
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
428
        $pageTable = $this->getTableName($project->getDatabaseName(), 'page');
429
        $sql = "SELECT page_namespace, COUNT(rev_id) AS total
430
            FROM $pageTable p JOIN $revisionTable r ON (r.rev_page = p.page_id)
431
            WHERE r.rev_actor = :actorId
432
            GROUP BY page_namespace";
433
434
        $results = $this->executeProjectsQuery($sql, [
435
            'actorId' => $user->getActorId($project),
436
        ])->fetchAll();
437
438
        $namespaceTotals = array_combine(array_map(function ($e) {
439
            return $e['page_namespace'];
440
        }, $results), array_map(function ($e) {
441
            return (int)$e['total'];
442
        }, $results));
443
444
        // Cache and return.
445
        return $this->setCache($cacheKey, $namespaceTotals);
446
    }
447
448
    /**
449
     * Get revisions by this user across the given Projects.
450
     * @param Project[] $projects The projects.
451
     * @param User $user The user.
452
     * @param int $limit The maximum number of revisions to fetch from each project.
453
     * @param int $offset Offset results by this number of rows.
454
     * @return array|mixed
455
     */
456
    public function getRevisions(array $projects, User $user, int $limit = 30, int $offset = 0)
457
    {
458
        // Check cache.
459
        $cacheKey = $this->getCacheKey('ec_globalcontribs.'.$user->getCacheKey().'.'.$limit.'.'.$offset);
460
        if ($this->cache->hasItem($cacheKey)) {
461
            return $this->cache->getItem($cacheKey)->get();
462
        }
463
464
        $username = $this->getProjectsConnection()->quote($user->getUsername(), \PDO::PARAM_STR);
465
466
        // Assemble queries.
467
        $queries = [];
468
        foreach ($projects as $project) {
469
            $revisionTable = $project->getTableName('revision');
470
            $pageTable = $project->getTableName('page');
471
            $commentTable = $project->getTableName('comment', 'revision');
472
            $actorId = $user->getActorId($project);
473
            $sql = "SELECT
474
                    '".$project->getDatabaseName()."' AS project_name,
475
                    revs.rev_id AS id,
476
                    revs.rev_timestamp AS timestamp,
477
                    UNIX_TIMESTAMP(revs.rev_timestamp) AS unix_timestamp,
478
                    revs.rev_minor_edit AS minor,
479
                    revs.rev_deleted AS deleted,
480
                    revs.rev_len AS length,
481
                    (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change,
482
                    revs.rev_parent_id AS parent_id,
483
                    $username AS username,
484
                    page.page_title,
485
                    page.page_namespace,
486
                    comment_text AS `comment`
487
                FROM $revisionTable AS revs
488
                    JOIN $pageTable AS page ON (rev_page = page_id)
489
                    LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
490
                    LEFT OUTER JOIN $commentTable ON revs.rev_comment_id = comment_id
491
                WHERE revs.rev_actor = $actorId";
492
            $queries[] = $sql;
493
        }
494
        $sql = "SELECT * FROM ((\n" . join("\n) UNION (\n", $queries) . ")) a ORDER BY timestamp DESC LIMIT $limit";
495
496
        if (is_numeric($offset)) {
0 ignored issues
show
introduced by
The condition is_numeric($offset) is always true.
Loading history...
497
            $sql .= " OFFSET $offset";
498
        }
499
500
        $revisions = $this->executeProjectsQuery($sql)->fetchAll();
501
502
        // Cache and return.
503
        return $this->setCache($cacheKey, $revisions);
504
    }
505
506
    /**
507
     * Get data for a bar chart of monthly edit totals per namespace.
508
     * @param Project $project The project.
509
     * @param User $user The user.
510
     * @return string[] [
511
     *                      [
512
     *                          'year' => <year>,
513
     *                          'month' => <month>,
514
     *                          'page_namespace' => <namespace>,
515
     *                          'count' => <count>,
516
     *                      ],
517
     *                      ...
518
     *                  ]
519
     */
520
    public function getMonthCounts(Project $project, User $user): array
521
    {
522
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_monthcounts');
523
        if ($this->cache->hasItem($cacheKey)) {
524
            return $this->cache->getItem($cacheKey)->get();
525
        }
526
527
        $revisionTable = $project->getTableName('revision');
528
        $pageTable = $project->getTableName('page');
529
        $sql =
530
            "SELECT "
531
            . "     YEAR(rev_timestamp) AS `year`,"
532
            . "     MONTH(rev_timestamp) AS `month`,"
533
            . "     page_namespace,"
534
            . "     COUNT(rev_id) AS `count` "
535
            .  " FROM $revisionTable JOIN $pageTable ON (rev_page = page_id)"
536
            . " WHERE rev_actor = :actorId"
537
            . " GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), page_namespace";
538
539
        $totals = $this->executeProjectsQuery($sql, [
540
            'actorId' => $user->getActorId($project),
541
        ])->fetchAll();
542
543
        // Cache and return.
544
        return $this->setCache($cacheKey, $totals);
545
    }
546
547
    /**
548
     * Get data for the timecard chart, with totals grouped by day and to the nearest two-hours.
549
     * @param Project $project
550
     * @param User $user
551
     * @return string[]
552
     */
553
    public function getTimeCard(Project $project, User $user): array
554
    {
555
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_timecard');
556
        if ($this->cache->hasItem($cacheKey)) {
557
            return $this->cache->getItem($cacheKey)->get();
558
        }
559
560
        $hourInterval = 2;
561
        $xCalc = "ROUND(HOUR(rev_timestamp)/$hourInterval) * $hourInterval";
562
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
563
        $sql = "SELECT "
564
            . "     DAYOFWEEK(rev_timestamp) AS `day_of_week`, "
565
            . "     $xCalc AS `hour`, "
566
            . "     COUNT(rev_id) AS `value` "
567
            . " FROM $revisionTable"
568
            . " WHERE rev_actor = :actorId"
569
            . " GROUP BY DAYOFWEEK(rev_timestamp), $xCalc ";
570
571
        $totals = $this->executeProjectsQuery($sql, [
572
            'actorId' => $user->getActorId($project),
573
        ])->fetchAll();
574
575
        // Cache and return.
576
        return $this->setCache($cacheKey, $totals);
577
    }
578
579
    /**
580
     * Get various data about edit sizes of the past 5,000 edits.
581
     * Will cache the result for 10 minutes.
582
     * @param Project $project The project.
583
     * @param User $user The user.
584
     * @return string[] Values with for keys 'average_size',
585
     *                  'small_edits' and 'large_edits'
586
     */
587
    public function getEditSizeData(Project $project, User $user): array
588
    {
589
        // Set up cache.
590
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_editsizes');
591
        if ($this->cache->hasItem($cacheKey)) {
592
            return $this->cache->getItem($cacheKey)->get();
593
        }
594
595
        // Prepare the queries and execute them.
596
        $revisionTable = $project->getTableName('revision');
597
        $sql = "SELECT AVG(sizes.size) AS average_size,
598
                COUNT(CASE WHEN sizes.size < 20 THEN 1 END) AS small_edits,
599
                COUNT(CASE WHEN sizes.size > 1000 THEN 1 END) AS large_edits
600
                FROM (
601
                    SELECT (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS size
602
                    FROM $revisionTable AS revs
603
                    LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
604
                    WHERE revs.rev_actor = :actorId
605
                    ORDER BY revs.rev_timestamp DESC
606
                    LIMIT 5000
607
                ) sizes";
608
        $results = $this->executeProjectsQuery($sql, [
609
            'actorId' => $user->getActorId($project),
610
        ])->fetch();
611
612
        // Cache and return.
613
        return $this->setCache($cacheKey, $results);
614
    }
615
616
    /**
617
     * Get the number of edits this user made using semi-automated tools.
618
     * @param Project $project
619
     * @param User $user
620
     * @return int Result of query, see below.
621
     */
622
    public function countAutomatedEdits(Project $project, User $user): int
623
    {
624
        $autoEditsRepo = new AutoEditsRepository();
625
        $autoEditsRepo->setContainer($this->container);
626
        return $autoEditsRepo->countAutomatedEdits($project, $user);
627
    }
628
}
629