Passed
Push — master ( e9dfe5...fa6a28 )
by MusikAnimal
05:21
created

EditCounterRepository::getPairData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 73
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 25
nc 3
nop 2
dl 0
loc 73
rs 9.0675
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file contains only the EditCounterRepository class.
4
 */
5
6
namespace Xtools;
7
8
use Mediawiki\Api\SimpleRequest;
9
10
/**
11
 * An EditCounterRepository is responsible for retrieving edit count information from the
12
 * databases and API. It doesn't do any post-processing of that information.
13
 * @codeCoverageIgnore
14
 */
15
class EditCounterRepository extends UserRightsRepository
16
{
17
    /**
18
     * Get data about revisions, pages, etc.
19
     * @param Project $project The project.
20
     * @param User $user The user.
21
     * @return string[] With keys: 'deleted', 'live', 'total', 'first', 'last', '24h', '7d', '30d',
22
     * '365d', 'small', 'large', 'with_comments', and 'minor_edits', ...
23
     */
24
    public function getPairData(Project $project, User $user)
25
    {
26
        // Set up cache.
27
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_pairdata');
28
        if ($this->cache->hasItem($cacheKey)) {
29
            return $this->cache->getItem($cacheKey)->get();
30
        }
31
32
        // Prepare the queries and execute them.
33
        $archiveTable = $this->getTableName($project->getDatabaseName(), 'archive');
34
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
35
        $sql = "
36
            -- Revision counts.
37
            (SELECT 'deleted' AS `key`, COUNT(ar_id) AS val FROM $archiveTable
38
                WHERE ar_user = :userId
39
            ) UNION (
40
            SELECT 'live' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
41
                WHERE rev_user = :userId
42
            ) UNION (
43
            SELECT 'day' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
44
                WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)
45
            ) UNION (
46
            SELECT 'week' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
47
                WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 WEEK)
48
            ) UNION (
49
            SELECT 'month' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
50
                WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 MONTH)
51
            ) UNION (
52
            SELECT 'year' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
53
                WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
54
            ) UNION (
55
            SELECT 'with_comments' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
56
                WHERE rev_user = :userId AND rev_comment != ''
57
            ) UNION (
58
            SELECT 'minor' AS `key`, COUNT(rev_id) AS val FROM $revisionTable
59
                WHERE rev_user = :userId AND rev_minor_edit = 1
60
61
            -- Dates.
62
            ) UNION (
63
            SELECT 'first' AS `key`, rev_timestamp AS `val` FROM $revisionTable
64
                WHERE rev_user = :userId ORDER BY rev_timestamp ASC LIMIT 1
65
            ) UNION (
66
            SELECT 'last' AS `key`, rev_timestamp AS `date` FROM $revisionTable
67
                WHERE rev_user = :userId ORDER BY rev_timestamp DESC LIMIT 1
68
69
            -- Page counts.
70
            ) UNION (
71
            SELECT 'edited-live' AS `key`, COUNT(DISTINCT rev_page) AS `val`
72
                FROM $revisionTable
73
                WHERE rev_user = :userId
74
            ) UNION (
75
            SELECT 'edited-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val`
76
                FROM $archiveTable
77
                WHERE ar_user = :userId
78
            ) UNION (
79
            SELECT 'created-live' AS `key`, COUNT(DISTINCT rev_page) AS `val`
80
                FROM $revisionTable
81
                WHERE rev_user = :userId AND rev_parent_id = 0
82
            ) UNION (
83
            SELECT 'created-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val`
84
                FROM $archiveTable
85
                WHERE ar_user = :userId AND ar_parent_id = 0
86
            )
87
        ";
88
89
        $resultQuery = $this->executeProjectsQuery($sql, ['userId' => $user->getId($project)]);
90
        $revisionCounts = [];
91
        while ($result = $resultQuery->fetch()) {
92
            $revisionCounts[$result['key']] = $result['val'];
93
        }
94
95
        // Cache and return.
96
        return $this->setCache($cacheKey, $revisionCounts);
97
    }
98
99
    /**
100
     * Get log totals for a user.
101
     * @param Project $project The project.
102
     * @param User $user The user.
103
     * @return integer[] Keys are "<log>-<action>" strings, values are counts.
104
     */
105
    public function getLogCounts(Project $project, User $user)
106
    {
107
        // Set up cache.
108
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_logcounts');
109
        if ($this->cache->hasItem($cacheKey)) {
110
            return $this->cache->getItem($cacheKey)->get();
111
        }
112
        $this->stopwatch->start($cacheKey, 'XTools');
113
114
        // Query.
115
        $loggingTable = $this->getTableName($project->getDatabaseName(), 'logging');
116
        $sql = "
117
        (SELECT CONCAT(log_type, '-', log_action) AS source, COUNT(log_id) AS value
118
            FROM $loggingTable
119
            WHERE log_user = :userId
120
            GROUP BY log_type, log_action
121
        )";
122
123
        $results = $this->executeProjectsQuery($sql, [
124
            'userId' => $user->getId($project)
125
        ])->fetchAll();
126
127
        $logCounts = array_combine(
128
            array_map(function ($e) {
129
                return $e['source'];
130
            }, $results),
131
            array_map(function ($e) {
132
                return $e['value'];
133
            }, $results)
134
        );
135
136
        // Make sure there is some value for each of the wanted counts.
137
        $requiredCounts = [
138
            'thanks-thank',
139
            'review-approve',
140
            'newusers-create2',
141
            'newusers-byemail',
142
            'patrol-patrol',
143
            'block-block',
144
            'block-reblock',
145
            'block-unblock',
146
            'protect-protect',
147
            'protect-modify',
148
            'protect-unprotect',
149
            'rights-rights',
150
            'move-move',
151
            'delete-delete',
152
            'delete-revision',
153
            'delete-restore',
154
            'import-import',
155
            'import-interwiki',
156
            'import-upload',
157
            'upload-upload',
158
            'upload-overwrite',
159
        ];
160
        foreach ($requiredCounts as $req) {
161
            if (!isset($logCounts[$req])) {
162
                $logCounts[$req] = 0;
163
            }
164
        }
165
166
        // Add Commons upload count, if applicable.
167
        $logCounts['files_uploaded_commons'] = 0;
168
        if ($this->isLabs()) {
169
            $commons = ProjectRepository::getProject('commonswiki', $this->container);
170
            $userId = $user->getId($commons);
171
            if ($userId) {
172
                $sql = "SELECT COUNT(log_id) FROM commonswiki_p.logging_userindex
173
                    WHERE log_type = 'upload' AND log_action = 'upload' AND log_user = :userId";
174
                $resultQuery = $this->executeProjectsQuery($sql, ['userId' => $userId]);
175
                $logCounts['files_uploaded_commons'] = $resultQuery->fetchColumn();
176
            }
177
        }
178
179
        // Cache and return.
180
        $this->stopwatch->stop($cacheKey);
181
        return $this->setCache($cacheKey, $logCounts);
182
    }
183
184
    /**
185
     * Get data for all blocks set on the given user.
186
     * @param Project $project
187
     * @param User $user
188
     * @return array
189
     */
190
    public function getBlocksReceived(Project $project, User $user)
191
    {
192
        $loggingTable = $this->getTableName($project->getDatabaseName(), 'logging', 'logindex');
193
        $sql = "SELECT log_action, log_timestamp, log_params FROM $loggingTable
194
                WHERE log_type = 'block'
195
                AND log_action IN ('block', 'reblock', 'unblock')
196
                AND log_timestamp > 0
197
                AND log_title = :username
198
                AND log_namespace = 2
199
                ORDER BY log_timestamp ASC";
200
        $username = str_replace(' ', '_', $user->getUsername());
201
202
        return $this->executeProjectsQuery($sql, [
203
            'username' => $username
204
        ])->fetchAll();
205
    }
206
207
    /**
208
     * Get a user's total edit count on all projects.
209
     * @see EditCounterRepository::globalEditCountsFromCentralAuth()
210
     * @see EditCounterRepository::globalEditCountsFromDatabases()
211
     * @param User $user The user.
212
     * @param Project $project The project to start from.
213
     * @return mixed[] Elements are arrays with 'project' (Project), and 'total' (int).
214
     */
215
    public function globalEditCounts(User $user, Project $project)
216
    {
217
        // Get the edit counts from CentralAuth or database.
218
        $editCounts = $this->globalEditCountsFromCentralAuth($user, $project);
219
        if ($editCounts === false) {
0 ignored issues
show
introduced by
The condition $editCounts === false is always false.
Loading history...
220
            $editCounts = $this->globalEditCountsFromDatabases($user, $project);
221
        }
222
223
        // Pre-populate all projects' metadata, to prevent each project call from fetching it.
224
        $project->getRepository()->getAll();
225
226
        // Compile the output.
227
        $out = [];
228
        foreach ($editCounts as $editCount) {
229
            $out[] = [
230
                'project' => ProjectRepository::getProject($editCount['dbName'], $this->container),
231
                'total' => $editCount['total'],
232
            ];
233
        }
234
        return $out;
235
    }
236
237
    /**
238
     * Get a user's total edit count on one or more project.
239
     * Requires the CentralAuth extension to be installed on the project.
240
     *
241
     * @param User $user The user.
242
     * @param Project $project The project to start from.
243
     * @return mixed[] Elements are arrays with 'dbName' (string), and 'total' (int).
244
     */
245
    protected function globalEditCountsFromCentralAuth(User $user, Project $project)
246
    {
247
        // Set up cache and stopwatch.
248
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_globaleditcounts');
249
        if ($this->cache->hasItem($cacheKey)) {
250
            return $this->cache->getItem($cacheKey)->get();
251
        }
252
        $this->stopwatch->start($cacheKey, 'XTools');
253
254
        $this->log->debug(__METHOD__." Getting global edit counts from for ".$user->getUsername());
255
256
        // Load all projects, so it doesn't have to request metadata about each one as it goes.
257
        $project->getRepository()->getAll();
258
259
        $api = $this->getMediawikiApi($project);
260
        $params = [
261
            'meta' => 'globaluserinfo',
262
            'guiprop' => 'editcount|merged',
263
            'guiuser' => $user->getUsername(),
264
        ];
265
        $query = new SimpleRequest('query', $params);
266
        $result = $api->getRequest($query);
267
        if (!isset($result['query']['globaluserinfo']['merged'])) {
268
            return [];
269
        }
270
        $out = [];
271
        foreach ($result['query']['globaluserinfo']['merged'] as $result) {
272
            $out[] = [
273
                'dbName' => $result['wiki'],
274
                'total' => $result['editcount'],
275
            ];
276
        }
277
278
        // Cache and return.
279
        $this->stopwatch->stop($cacheKey);
280
        return $this->setCache($cacheKey, $out);
281
    }
282
283
    /**
284
     * Get total edit counts from all projects for this user.
285
     * @see EditCounterRepository::globalEditCountsFromCentralAuth()
286
     * @param User $user The user.
287
     * @param Project $project The project to start from.
288
     * @return mixed[] Elements are arrays with 'dbName' (string), and 'total' (int).
289
     */
290
    protected function globalEditCountsFromDatabases(User $user, Project $project)
291
    {
292
        $this->log->debug(__METHOD__." Getting global edit counts for ".$user->getUsername());
293
        $stopwatchName = 'globalRevisionCounts.'.$user->getUsername();
294
        $allProjects = $project->getRepository()->getAll();
295
        $topEditCounts = [];
296
        foreach ($allProjects as $projectMeta) {
297
            $revisionTableName = $this->getTableName($projectMeta['dbName'], 'revision');
298
            $sql = "SELECT COUNT(rev_id) FROM $revisionTableName WHERE rev_user_text = :username";
299
300
            $resultQuery = $this->executeProjectsQuery($sql, [
301
                'username' => $user->getUsername()
302
            ]);
303
            $total = (int)$resultQuery->fetchColumn();
304
            $topEditCounts[] = [
305
                'dbName' => $projectMeta['dbName'],
306
                'total' => $total,
307
            ];
308
            $this->stopwatch->lap($stopwatchName);
309
        }
310
        return $topEditCounts;
311
    }
312
313
    /**
314
     * Get the given user's total edit counts per namespace on the given project.
315
     * @param Project $project The project.
316
     * @param User $user The user.
317
     * @return integer[] Array keys are namespace IDs, values are the edit counts.
318
     */
319
    public function getNamespaceTotals(Project $project, User $user)
320
    {
321
        // Cache?
322
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_namespacetotals');
323
        $this->stopwatch->start($cacheKey, 'XTools');
324
        if ($this->cache->hasItem($cacheKey)) {
325
            return $this->cache->getItem($cacheKey)->get();
326
        }
327
328
        // Query.
329
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
330
        $pageTable = $this->getTableName($project->getDatabaseName(), 'page');
331
        $sql = "SELECT page_namespace, COUNT(rev_id) AS total
332
            FROM $pageTable p JOIN $revisionTable r ON (r.rev_page = p.page_id)
333
            WHERE r.rev_user = :id
334
            GROUP BY page_namespace";
335
336
        $results = $this->executeProjectsQuery($sql, [
337
            'id' => $user->getId($project),
338
        ])->fetchAll();
339
340
        $namespaceTotals = array_combine(array_map(function ($e) {
341
            return $e['page_namespace'];
342
        }, $results), array_map(function ($e) {
343
            return $e['total'];
344
        }, $results));
345
346
        // Cache and return.
347
        $this->stopwatch->stop($cacheKey);
348
        return $this->setCache($cacheKey, $namespaceTotals);
349
    }
350
351
    /**
352
     * Get revisions by this user.
353
     * @param Project[] $projects The projects.
354
     * @param User $user The user.
355
     * @param int $lim The maximum number of revisions to fetch from each project.
356
     * @return array|mixed
357
     */
358
    public function getRevisions($projects, User $user, $lim = 40)
359
    {
360
        // Check cache.
361
        $cacheKey = $this->getCacheKey('ec_globalcontribs.'.$user->getCacheKey().'.'.$lim);
362
        $this->stopwatch->start($cacheKey, 'XTools');
363
        if ($this->cache->hasItem($cacheKey)) {
364
            return $this->cache->getItem($cacheKey)->get();
365
        }
366
367
        // Assemble queries.
368
        $queries = [];
369
        foreach ($projects as $project) {
370
            $revisionTable = $project->getTableName('revision');
371
            $pageTable = $project->getTableName('page');
372
            $sql = "SELECT
373
                    '".$project->getDatabaseName()."' AS project_name,
374
                    revs.rev_id AS id,
375
                    revs.rev_timestamp AS timestamp,
376
                    UNIX_TIMESTAMP(revs.rev_timestamp) AS unix_timestamp,
377
                    revs.rev_minor_edit AS minor,
378
                    revs.rev_deleted AS deleted,
379
                    revs.rev_len AS length,
380
                    (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change,
381
                    revs.rev_parent_id AS parent_id,
382
                    revs.rev_comment AS comment,
383
                    revs.rev_user_text AS username,
384
                    page.page_title,
385
                    page.page_namespace
386
                FROM $revisionTable AS revs
387
                    JOIN $pageTable AS page ON (rev_page = page_id)
388
                    LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
389
                WHERE revs.rev_user_text = :username
390
                ORDER BY revs.rev_timestamp DESC";
391
            if (is_numeric($lim)) {
392
                $sql .= " LIMIT $lim";
393
            }
394
            $queries[] = $sql;
395
        }
396
        $sql = "(\n" . join("\n) UNION (\n", $queries) . ")\n";
397
398
        $revisions = $this->executeProjectsQuery($sql, [
399
            'username' => $user->getUsername(),
400
        ])->fetchAll();
401
402
        // Cache and return.
403
        $this->stopwatch->stop($cacheKey);
404
        return $this->setCache($cacheKey, $revisions);
405
    }
406
407
    /**
408
     * Get data for a bar chart of monthly edit totals per namespace.
409
     * @param Project $project The project.
410
     * @param User $user The user.
411
     * @return string[] [
412
     *                      [
413
     *                          'year' => <year>,
414
     *                          'month' => <month>,
415
     *                          'page_namespace' => <namespace>,
416
     *                          'count' => <count>,
417
     *                      ],
418
     *                      ...
419
     *                  ]
420
     */
421
    public function getMonthCounts(Project $project, User $user)
422
    {
423
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_monthcounts');
424
        $this->stopwatch->start($cacheKey, 'XTools');
425
        if ($this->cache->hasItem($cacheKey)) {
426
            return $this->cache->getItem($cacheKey)->get();
427
        }
428
429
        $revisionTable = $project->getTableName('revision');
430
        $pageTable = $project->getTableName('page');
431
        $sql =
432
            "SELECT "
433
            . "     YEAR(rev_timestamp) AS `year`,"
434
            . "     MONTH(rev_timestamp) AS `month`,"
435
            . "     page_namespace,"
436
            . "     COUNT(rev_id) AS `count` "
437
            .  " FROM $revisionTable JOIN $pageTable ON (rev_page = page_id)"
438
            . " WHERE rev_user_text = :username"
439
            . " GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), page_namespace";
440
441
        $totals = $this->executeProjectsQuery($sql, [
442
            'username' => $user->getUsername(),
443
        ])->fetchAll();
444
445
        // Cache and return.
446
        $this->stopwatch->stop($cacheKey);
447
        return $this->setCache($cacheKey, $totals);
448
    }
449
450
    /**
451
     * Get data for the timecard chart, with totals grouped by day and to the nearest two-hours.
452
     * @param Project $project
453
     * @param User $user
454
     * @return string[]
455
     */
456
    public function getTimeCard(Project $project, User $user)
457
    {
458
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_timecard');
459
        $this->stopwatch->start($cacheKey, 'XTools');
460
        if ($this->cache->hasItem($cacheKey)) {
461
            return $this->cache->getItem($cacheKey)->get();
462
        }
463
464
        $hourInterval = 2;
465
        $xCalc = "ROUND(HOUR(rev_timestamp)/$hourInterval) * $hourInterval";
466
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
467
        $sql = "SELECT "
468
            . "     DAYOFWEEK(rev_timestamp) AS `y`, "
469
            . "     $xCalc AS `x`, "
470
            . "     COUNT(rev_id) AS `value` "
471
            . " FROM $revisionTable"
472
            . " WHERE rev_user_text = :username"
473
            . " GROUP BY DAYOFWEEK(rev_timestamp), $xCalc ";
474
475
        $totals = $this->executeProjectsQuery($sql, [
476
            'username' => $user->getUsername(),
477
        ])->fetchAll();
478
479
        // Cache and return.
480
        $this->stopwatch->stop($cacheKey);
481
        return $this->setCache($cacheKey, $totals);
482
    }
483
484
    /**
485
     * Get various data about edit sizes of the past 5,000 edits.
486
     * Will cache the result for 10 minutes.
487
     * @param Project $project The project.
488
     * @param User $user The user.
489
     * @return string[] Values with for keys 'average_size',
490
     *                  'small_edits' and 'large_edits'
491
     */
492
    public function getEditSizeData(Project $project, User $user)
493
    {
494
        // Set up cache.
495
        $cacheKey = $this->getCacheKey(func_get_args(), 'ec_editsizes');
496
        $this->stopwatch->start($cacheKey, 'XTools');
497
        if ($this->cache->hasItem($cacheKey)) {
498
            return $this->cache->getItem($cacheKey)->get();
499
        }
500
501
        // Prepare the queries and execute them.
502
        $revisionTable = $this->getTableName($project->getDatabaseName(), 'revision');
503
        $sql = "SELECT AVG(sizes.size) AS average_size,
504
                COUNT(CASE WHEN sizes.size < 20 THEN 1 END) AS small_edits,
505
                COUNT(CASE WHEN sizes.size > 1000 THEN 1 END) AS large_edits
506
                FROM (
507
                    SELECT (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS size
508
                    FROM $revisionTable AS revs
509
                    LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
510
                    WHERE revs.rev_user = :userId
511
                    ORDER BY revs.rev_timestamp DESC
512
                    LIMIT 5000
513
                ) sizes";
514
515
        $results = $this->executeProjectsQuery($sql, [
516
            'userId' => $user->getId($project),
517
        ])->fetch();
518
519
        // Cache and return.
520
        $this->stopwatch->stop($cacheKey);
521
        return $this->setCache($cacheKey, $results);
522
    }
523
524
    /**
525
     * Get the number of edits this user made using semi-automated tools.
526
     * @param Project $project
527
     * @param User $user
528
     * @return int Result of query, see below.
529
     */
530
    public function countAutomatedEdits(Project $project, User $user)
531
    {
532
        $autoEditsRepo = new AutoEditsRepository();
533
        $autoEditsRepo->setContainer($this->container);
534
        return $autoEditsRepo->countAutomatedEdits($project, $user);
535
    }
536
}
537