1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file contains only the EditCounterRepository class. |
4
|
|
|
*/ |
5
|
|
|
|
6
|
|
|
namespace Xtools; |
7
|
|
|
|
8
|
|
|
use AppBundle\Helper\AutomatedEditsHelper; |
9
|
|
|
use DateInterval; |
10
|
|
|
use DateTime; |
11
|
|
|
use Mediawiki\Api\SimpleRequest; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* An EditCounterRepository is responsible for retrieving edit count information from the |
15
|
|
|
* databases and API. It doesn't do any post-processing of that information. |
16
|
|
|
*/ |
17
|
|
|
class EditCounterRepository extends Repository |
18
|
|
|
{ |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* Get revision counts for the given user. |
22
|
|
|
* @param Project $project The project. |
23
|
|
|
* @param User $user The user. |
24
|
|
|
* @returns string[] With keys: 'deleted', 'live', 'total', 'first', 'last', '24h', '7d', '30d', |
25
|
|
|
* '365d', 'small', 'large', 'with_comments', and 'minor_edits'. |
26
|
|
|
*/ |
27
|
|
|
public function getRevisionCounts(Project $project, User $user) |
28
|
|
|
{ |
29
|
|
|
// Set up cache. |
30
|
|
|
$cacheKey = 'revisioncounts.' . $project->getDatabaseName() . '.' . $user->getUsername(); |
31
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
32
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
// Prepare the queries and execute them. |
36
|
|
|
$archiveTable = $this->getTableName($project->getDatabaseName(), 'archive'); |
37
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
38
|
|
|
$queries = [ |
39
|
|
|
'deleted' => "SELECT COUNT(ar_id) FROM $archiveTable |
40
|
|
|
WHERE ar_user = :userId", |
41
|
|
|
'live' => "SELECT COUNT(rev_id) FROM $revisionTable |
42
|
|
|
WHERE rev_user = :userId", |
43
|
|
|
'day' => "SELECT COUNT(rev_id) FROM $revisionTable |
44
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)", |
45
|
|
|
'week' => "SELECT COUNT(rev_id) FROM $revisionTable |
46
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 WEEK)", |
47
|
|
|
'month' => "SELECT COUNT(rev_id) FROM $revisionTable |
48
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 MONTH)", |
49
|
|
|
'year' => "SELECT COUNT(rev_id) FROM $revisionTable |
50
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 YEAR)", |
51
|
|
|
'small' => "SELECT COUNT(rev_id) FROM $revisionTable |
52
|
|
|
WHERE rev_user = :userId AND rev_len < 20", |
53
|
|
|
'large' => "SELECT COUNT(rev_id) FROM $revisionTable |
54
|
|
|
WHERE rev_user = :userId AND rev_len > 1000", |
55
|
|
|
'with_comments' => "SELECT COUNT(rev_id) FROM $revisionTable |
56
|
|
|
WHERE rev_user = :userId AND rev_comment = ''", |
57
|
|
|
'minor' => "SELECT COUNT(rev_id) FROM $revisionTable |
58
|
|
|
WHERE rev_user = :userId AND rev_minor_edit = 1", |
59
|
|
|
'average_size' => "SELECT AVG(rev_len) FROM $revisionTable |
60
|
|
|
WHERE rev_user = :userId", |
61
|
|
|
]; |
62
|
|
|
$this->stopwatch->start($cacheKey); |
63
|
|
|
$revisionCounts = []; |
64
|
|
|
foreach ($queries as $varName => $query) { |
65
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($query); |
66
|
|
|
$userId = $user->getId($project); |
67
|
|
|
$resultQuery->bindParam("userId", $userId); |
68
|
|
|
$resultQuery->execute(); |
69
|
|
|
$val = $resultQuery->fetchColumn(); |
70
|
|
|
$revisionCounts[$varName] = $val ?: 0; |
71
|
|
|
$this->stopwatch->lap($cacheKey); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
75
|
|
|
$this->stopwatch->stop($cacheKey); |
76
|
|
|
$cacheItem = $this->cache->getItem($cacheKey) |
77
|
|
|
->set($revisionCounts) |
78
|
|
|
->expiresAfter(new DateInterval('PT10M')); |
79
|
|
|
$this->cache->save($cacheItem); |
80
|
|
|
|
81
|
|
|
return $revisionCounts; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Get the first and last revision dates (in MySQL YYYYMMDDHHMMSS format). |
86
|
|
|
* @param Project $project The project. |
87
|
|
|
* @param User $user The user. |
88
|
|
|
* @return string[] With keys 'first' and 'last'. |
89
|
|
|
*/ |
90
|
|
|
public function getRevisionDates(Project $project, User $user) |
91
|
|
|
{ |
92
|
|
|
// Set up cache. |
93
|
|
|
$cacheKey = 'revisiondates.' . $project->getDatabaseName() . '.' . $user->getUsername(); |
94
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
95
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
$this->stopwatch->start($cacheKey); |
99
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
100
|
|
|
$query = "(SELECT 'first' AS `key`, rev_timestamp AS `date` FROM $revisionTable |
101
|
|
|
WHERE rev_user = :userId ORDER BY rev_timestamp ASC LIMIT 1) |
102
|
|
|
UNION |
103
|
|
|
(SELECT 'last' AS `key`, rev_timestamp AS `date` FROM $revisionTable |
104
|
|
|
WHERE rev_user = :userId ORDER BY rev_timestamp DESC LIMIT 1)"; |
105
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($query); |
106
|
|
|
$userId = $user->getId($project); |
107
|
|
|
$resultQuery->bindParam("userId", $userId); |
108
|
|
|
$resultQuery->execute(); |
109
|
|
|
$result = $resultQuery->fetchAll(); |
110
|
|
|
$revisionDates = []; |
111
|
|
|
foreach ($result as $res) { |
112
|
|
|
$revisionDates[$res['key']] = $res['date']; |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
116
|
|
|
$cacheItem = $this->cache->getItem($cacheKey) |
117
|
|
|
->set($revisionDates) |
118
|
|
|
->expiresAfter(new DateInterval('PT10M')); |
119
|
|
|
$this->cache->save($cacheItem); |
120
|
|
|
$this->stopwatch->stop($cacheKey); |
121
|
|
|
return $revisionDates; |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
/** |
125
|
|
|
* Get page counts for the given user, both for live and deleted pages/revisions. |
126
|
|
|
* @param Project $project The project. |
127
|
|
|
* @param User $user The user. |
128
|
|
|
* @return int[] With keys: edited-live, edited-deleted, created-live, created-deleted. |
129
|
|
|
*/ |
130
|
|
|
public function getPageCounts(Project $project, User $user) |
131
|
|
|
{ |
132
|
|
|
// Set up cache. |
133
|
|
|
$cacheKey = 'pagecounts.'.$project->getDatabaseName().'.'.$user->getUsername(); |
134
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
135
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
136
|
|
|
} |
137
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
138
|
|
|
|
139
|
|
|
// Build and execute query. |
140
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
141
|
|
|
$archiveTable = $this->getTableName($project->getDatabaseName(), 'archive'); |
142
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare(" |
143
|
|
|
(SELECT 'edited-live' AS source, COUNT(DISTINCT rev_page) AS value |
144
|
|
|
FROM $revisionTable |
145
|
|
|
WHERE rev_user_text=:username) |
146
|
|
|
UNION |
147
|
|
|
(SELECT 'edited-deleted' AS source, COUNT(DISTINCT ar_page_id) AS value |
148
|
|
|
FROM $archiveTable |
149
|
|
|
WHERE ar_user_text=:username) |
150
|
|
|
UNION |
151
|
|
|
(SELECT 'created-live' AS source, COUNT(DISTINCT rev_page) AS value |
152
|
|
|
FROM $revisionTable |
153
|
|
|
WHERE rev_user_text=:username AND rev_parent_id=0) |
154
|
|
|
UNION |
155
|
|
|
(SELECT 'created-deleted' AS source, COUNT(DISTINCT ar_page_id) AS value |
156
|
|
|
FROM $archiveTable |
157
|
|
|
WHERE ar_user_text=:username AND ar_parent_id=0) |
158
|
|
|
"); |
159
|
|
|
$username = $user->getUsername(); |
160
|
|
|
$resultQuery->bindParam("username", $username); |
161
|
|
|
$resultQuery->execute(); |
162
|
|
|
$results = $resultQuery->fetchAll(); |
163
|
|
|
|
164
|
|
|
$pageCounts = array_combine( |
165
|
|
|
array_map(function ($e) { |
166
|
|
|
return $e['source']; |
167
|
|
|
}, $results), |
168
|
|
|
array_map(function ($e) { |
169
|
|
|
return $e['value']; |
170
|
|
|
}, $results) |
171
|
|
|
); |
172
|
|
|
|
173
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
174
|
|
|
$cacheItem = $this->cache->getItem($cacheKey) |
175
|
|
|
->set($pageCounts) |
176
|
|
|
->expiresAfter(new DateInterval('PT10M')); |
177
|
|
|
$this->cache->save($cacheItem); |
178
|
|
|
$this->stopwatch->stop($cacheKey); |
179
|
|
|
return $pageCounts; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Get log totals for a user. |
184
|
|
|
* @param Project $project The project. |
185
|
|
|
* @param User $user The user. |
186
|
|
|
* @return integer[] Keys are "<log>-<action>" strings, values are counts. |
187
|
|
|
*/ |
188
|
|
|
public function getLogCounts(Project $project, User $user) |
189
|
|
|
{ |
190
|
|
|
// Set up cache. |
191
|
|
|
$cacheKey = 'logcounts.'.$project->getDatabaseName().'.'.$user->getUsername(); |
192
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
193
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
194
|
|
|
} |
195
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
196
|
|
|
|
197
|
|
|
// Query. |
198
|
|
|
$sql = "SELECT CONCAT(log_type, '-', log_action) AS source, COUNT(log_id) AS value |
199
|
|
|
FROM " . $this->getTableName($project->getDatabaseName(), 'logging') . " |
200
|
|
|
WHERE log_user = :userId |
201
|
|
|
GROUP BY log_type, log_action"; |
202
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
203
|
|
|
$userId = $user->getId($project); |
204
|
|
|
$resultQuery->bindParam('userId', $userId); |
205
|
|
|
$resultQuery->execute(); |
206
|
|
|
$results = $resultQuery->fetchAll(); |
207
|
|
|
$logCounts = array_combine( |
208
|
|
|
array_map(function ($e) { |
209
|
|
|
return $e['source']; |
210
|
|
|
}, $results), |
211
|
|
|
array_map(function ($e) { |
212
|
|
|
return $e['value']; |
213
|
|
|
}, $results) |
214
|
|
|
); |
215
|
|
|
|
216
|
|
|
// Make sure there is some value for each of the wanted counts. |
217
|
|
|
$requiredCounts = [ |
218
|
|
|
'thanks-thank', |
219
|
|
|
'review-approve', |
220
|
|
|
'patrol-patrol', |
221
|
|
|
'block-block', |
222
|
|
|
'block-unblock', |
223
|
|
|
'protect-protect', |
224
|
|
|
'protect-unprotect', |
225
|
|
|
'move-move', |
226
|
|
|
'delete-delete', |
227
|
|
|
'delete-revision', |
228
|
|
|
'delete-restore', |
229
|
|
|
'import-import', |
230
|
|
|
'upload-upload', |
231
|
|
|
'upload-overwrite', |
232
|
|
|
]; |
233
|
|
|
foreach ($requiredCounts as $req) { |
234
|
|
|
if (!isset($logCounts[$req])) { |
235
|
|
|
$logCounts[$req] = 0; |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
// Add Commons upload count, if applicable. |
240
|
|
|
$logCounts['files_uploaded_commons'] = 0; |
241
|
|
|
if ($this->isLabs()) { |
242
|
|
|
$sql = "SELECT COUNT(log_id) FROM commonswiki_p.logging_userindex |
243
|
|
|
WHERE log_type = 'upload' AND log_action = 'upload' AND log_user = :userId"; |
244
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
245
|
|
|
$resultQuery->bindParam('userId', $userId); |
246
|
|
|
$resultQuery->execute(); |
247
|
|
|
$logCounts['files_uploaded_commons'] = $resultQuery->fetchColumn(); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
251
|
|
|
$cacheItem = $this->cache->getItem($cacheKey) |
252
|
|
|
->set($logCounts) |
253
|
|
|
->expiresAfter(new DateInterval('PT10M')); |
254
|
|
|
$this->cache->save($cacheItem); |
255
|
|
|
$this->stopwatch->stop($cacheKey); |
256
|
|
|
|
257
|
|
|
return $logCounts; |
|
|
|
|
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
/** |
261
|
|
|
* Get a user's total edit count on all projects. |
262
|
|
|
* @see EditCounterRepository::globalEditCountsFromCentralAuth() |
263
|
|
|
* @see EditCounterRepository::globalEditCountsFromDatabases() |
264
|
|
|
* @param User $user The user. |
265
|
|
|
* @param Project $project The project to start from. |
266
|
|
|
* @return mixed[] Elements are arrays with 'project' (Project), and 'total' (int). |
267
|
|
|
*/ |
268
|
|
|
public function globalEditCounts(User $user, Project $project) { |
269
|
|
|
$editCounts = $this->globalEditCountsFromCentralAuth($user, $project); |
270
|
|
|
if ($editCounts === false) { |
271
|
|
|
$editCounts = $this->globalEditCountsFromDatabases($user, $project); |
272
|
|
|
} |
273
|
|
|
$out = []; |
274
|
|
|
foreach ($editCounts as $editCount) { |
275
|
|
|
$out[] = [ |
276
|
|
|
'project' => ProjectRepository::getProject($editCount['dbname'], $this->container), |
277
|
|
|
'total' => $editCount['total'], |
278
|
|
|
]; |
279
|
|
|
} |
280
|
|
|
return $out; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Get a user's total edit count on one or more project. |
285
|
|
|
* Requires the CentralAuth extension to be installed on the project. |
286
|
|
|
* |
287
|
|
|
* @param User $user The user. |
288
|
|
|
* @param Project $project The project to start from. |
289
|
|
|
* @return mixed[] Elements are arrays with 'dbname' (string), and 'total' (int). |
290
|
|
|
*/ |
291
|
|
|
protected function globalEditCountsFromCentralAuth(User $user, Project $project) |
292
|
|
|
{ |
293
|
|
|
// Set up cache and stopwatch. |
294
|
|
|
$cacheKey = 'globalRevisionCounts.'.$user->getUsername(); |
295
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
296
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
297
|
|
|
} |
298
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
299
|
|
|
|
300
|
|
|
// Load all projects, so it doesn't have to request metadata about each one as it goes. |
301
|
|
|
$project->getRepository()->getAll(); |
|
|
|
|
302
|
|
|
|
303
|
|
|
$api = $this->getMediawikiApi($project); |
304
|
|
|
$params = [ |
305
|
|
|
'meta' => 'globaluserinfo', |
306
|
|
|
'guiprop' => 'editcount|merged', |
307
|
|
|
'guiuser' => $user->getUsername(), |
308
|
|
|
]; |
309
|
|
|
$query = new SimpleRequest('query', $params); |
310
|
|
|
$result = $api->getRequest($query); |
311
|
|
|
if (!isset($result['query']['globaluserinfo']['merged'])) { |
312
|
|
|
return []; |
313
|
|
|
} |
314
|
|
|
$out = []; |
315
|
|
|
foreach ($result['query']['globaluserinfo']['merged'] as $result) { |
316
|
|
|
$out[] = [ |
317
|
|
|
'dbname' => $result['wiki'], |
318
|
|
|
'total' => $result['editcount'], |
319
|
|
|
]; |
320
|
|
|
} |
321
|
|
|
|
322
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
323
|
|
|
$cacheItem = $this->cache->getItem($cacheKey) |
324
|
|
|
->set($out) |
325
|
|
|
->expiresAfter(new DateInterval('PT10M')); |
326
|
|
|
$this->cache->save($cacheItem); |
327
|
|
|
$this->stopwatch->stop($cacheKey); |
328
|
|
|
|
329
|
|
|
return $out; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* Get total edit counts from all projects for this user. |
334
|
|
|
* @see EditCounterRepository::globalEditCountsFromCentralAuth() |
335
|
|
|
* @param User $user The user. |
336
|
|
|
* @param Project $project The project to start from. |
337
|
|
|
* @return mixed[] Elements are arrays with 'dbname' (string), and 'total' (int). |
338
|
|
|
*/ |
339
|
|
|
protected function globalEditCountsFromDatabases(User $user, Project $project) { |
340
|
|
|
$stopwatchName = 'globalRevisionCounts.'.$user->getUsername(); |
341
|
|
|
$allProjects = $project->getRepository()->getAll(); |
|
|
|
|
342
|
|
|
$topEditCounts = []; |
343
|
|
|
foreach ($allProjects as $projectMeta) { |
344
|
|
|
$revisionTableName = $this->getTableName($projectMeta['dbName'], 'revision'); |
345
|
|
|
$sql = "SELECT COUNT(rev_id) FROM $revisionTableName WHERE rev_user_text=:username"; |
346
|
|
|
$stmt = $this->getProjectsConnection()->prepare($sql); |
347
|
|
|
$stmt->bindParam("username", $user->getUsername()); |
|
|
|
|
348
|
|
|
$stmt->execute(); |
349
|
|
|
$total = (int)$stmt->fetchColumn(); |
350
|
|
|
$topEditCounts[] = [ |
351
|
|
|
'dbname' => $projectMeta['dbName'], |
352
|
|
|
'total' => $total, |
353
|
|
|
]; |
354
|
|
|
$this->stopwatch->lap($stopwatchName); |
355
|
|
|
} |
356
|
|
|
return $topEditCounts; |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* Get the given user's total edit counts per namespace on the given project. |
361
|
|
|
* @param Project $project The project. |
362
|
|
|
* @param User $user The user. |
363
|
|
|
* @return integer[] Array keys are namespace IDs, values are the edit counts. |
364
|
|
|
*/ |
365
|
|
|
public function getNamespaceTotals(Project $project, User $user) |
366
|
|
|
{ |
367
|
|
|
// Cache? |
368
|
|
|
$userId = $user->getId($project); |
369
|
|
|
$cacheKey = "ec.namespacetotals.{$project->getDatabaseName()}.$userId"; |
370
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
371
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
372
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
// Query. |
376
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
377
|
|
|
$pageTable = $this->getTableName($project->getDatabaseName(), 'page'); |
378
|
|
|
$sql = "SELECT page_namespace, COUNT(rev_id) AS total |
379
|
|
|
FROM $revisionTable r JOIN $pageTable p on r.rev_page = p.page_id |
380
|
|
|
WHERE r.rev_user = :id |
381
|
|
|
GROUP BY page_namespace"; |
382
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
383
|
|
|
$resultQuery->bindParam(":id", $userId); |
384
|
|
|
$resultQuery->execute(); |
385
|
|
|
$results = $resultQuery->fetchAll(); |
386
|
|
|
$namespaceTotals = array_combine(array_map(function ($e) { |
387
|
|
|
return $e['page_namespace']; |
388
|
|
|
}, $results), array_map(function ($e) { |
389
|
|
|
return $e['total']; |
390
|
|
|
}, $results)); |
391
|
|
|
|
392
|
|
|
// Cache and return. |
393
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
394
|
|
|
$cacheItem->set($namespaceTotals); |
395
|
|
|
$cacheItem->expiresAfter(new DateInterval('PT15M')); |
396
|
|
|
$this->cache->save($cacheItem); |
397
|
|
|
$this->stopwatch->stop($cacheKey); |
398
|
|
|
return $namespaceTotals; |
399
|
|
|
} |
400
|
|
|
|
401
|
|
|
/** |
402
|
|
|
* Get revisions by this user. |
403
|
|
|
* @param Project $project The project. |
404
|
|
|
* @param User $user The user. |
405
|
|
|
* @param DateTime $oldest The oldest revision time of interest (only return greater than this). |
406
|
|
|
* @param int $lim The number of results to return. |
407
|
|
|
* @return array|mixed |
408
|
|
|
*/ |
409
|
|
|
public function getRevisions(Project $project, User $user, DateTime $oldest = null, $lim = null) |
410
|
|
|
{ |
411
|
|
|
$username = $user->getUsername(); |
412
|
|
|
$cacheKey = "globalcontribs.{$project->getDatabaseName()}.$username"; |
413
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
414
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
415
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
416
|
|
|
} |
417
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
418
|
|
|
$pageTable = $this->getTableName($project->getDatabaseName(), 'page'); |
419
|
|
|
$whereTimestamp = ($oldest instanceof DateTime) |
420
|
|
|
? ' AND revs.rev_timestamp > '.$oldest->format('YmdHis') |
421
|
|
|
: ''; |
422
|
|
|
// Column aliases here should match those used in PagesRepository::getRevisions() |
423
|
|
|
$sql = "SELECT |
424
|
|
|
revs.rev_id AS id, |
425
|
|
|
revs.rev_timestamp AS timestamp, |
426
|
|
|
UNIX_TIMESTAMP(revs.rev_timestamp) AS unix_timestamp, |
427
|
|
|
revs.rev_minor_edit AS minor, |
428
|
|
|
revs.rev_deleted AS deleted, |
429
|
|
|
revs.rev_len AS length, |
430
|
|
|
(CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change, |
431
|
|
|
revs.rev_parent_id AS parent_id, |
432
|
|
|
revs.rev_comment AS comment, |
433
|
|
|
revs.rev_user_text AS username, |
434
|
|
|
page.page_title, |
435
|
|
|
page.page_namespace |
436
|
|
|
FROM $revisionTable AS revs |
437
|
|
|
JOIN $pageTable AS page ON (rev_page = page_id) |
438
|
|
|
LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id) |
439
|
|
|
WHERE revs.rev_user_text LIKE :username $whereTimestamp |
440
|
|
|
ORDER BY revs.rev_timestamp DESC"; |
441
|
|
|
if (is_numeric($lim)) { |
442
|
|
|
$sql .= " LIMIT $lim"; |
443
|
|
|
} |
444
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
445
|
|
|
$resultQuery->bindParam(":username", $username); |
446
|
|
|
$resultQuery->execute(); |
447
|
|
|
$revisions = $resultQuery->fetchAll(); |
448
|
|
|
|
449
|
|
|
// Cache this. |
450
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
451
|
|
|
$cacheItem->set($revisions); |
452
|
|
|
$cacheItem->expiresAfter(new DateInterval('PT15M')); |
453
|
|
|
$this->cache->save($cacheItem); |
454
|
|
|
|
455
|
|
|
$this->stopwatch->stop($cacheKey); |
456
|
|
|
return $revisions; |
457
|
|
|
} |
458
|
|
|
|
459
|
|
|
/** |
460
|
|
|
* Get data for a bar chart of monthly edit totals per namespace. |
461
|
|
|
* @param Project $project The project. |
462
|
|
|
* @param User $user The user. |
463
|
|
|
* @return string[] |
464
|
|
|
*/ |
465
|
|
View Code Duplication |
public function getMonthCounts(Project $project, User $user) |
|
|
|
|
466
|
|
|
{ |
467
|
|
|
$username = $user->getUsername(); |
468
|
|
|
$cacheKey = "monthcounts.$username"; |
469
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
470
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
471
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
475
|
|
|
$pageTable = $this->getTableName($project->getDatabaseName(), 'page'); |
476
|
|
|
$sql = |
477
|
|
|
"SELECT " |
478
|
|
|
. " YEAR(rev_timestamp) AS `year`," |
479
|
|
|
. " MONTH(rev_timestamp) AS `month`," |
480
|
|
|
. " page_namespace," |
481
|
|
|
. " COUNT(rev_id) AS `count` " |
482
|
|
|
. " FROM $revisionTable JOIN $pageTable ON (rev_page = page_id)" |
483
|
|
|
. " WHERE rev_user_text = :username" |
484
|
|
|
. " GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), page_namespace " |
485
|
|
|
. " ORDER BY rev_timestamp DESC"; |
486
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
487
|
|
|
$resultQuery->bindParam(":username", $username); |
488
|
|
|
$resultQuery->execute(); |
489
|
|
|
$totals = $resultQuery->fetchAll(); |
490
|
|
|
|
491
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
492
|
|
|
$cacheItem->expiresAfter(new DateInterval('PT10M')); |
493
|
|
|
$cacheItem->set($totals); |
494
|
|
|
$this->cache->save($cacheItem); |
495
|
|
|
|
496
|
|
|
$this->stopwatch->stop($cacheKey); |
497
|
|
|
return $totals; |
498
|
|
|
} |
499
|
|
|
|
500
|
|
|
/** |
501
|
|
|
* Get yearly edit totals for this user, grouped by namespace. |
502
|
|
|
* @param Project $project The project. |
503
|
|
|
* @param User $user The user. |
504
|
|
|
* @return string[] ['<namespace>' => ['<year>' => 'total', ... ], ... ] |
505
|
|
|
*/ |
506
|
|
View Code Duplication |
public function getYearCounts(Project $project, User $user) |
|
|
|
|
507
|
|
|
{ |
508
|
|
|
$username = $user->getUsername(); |
509
|
|
|
$cacheKey = "yearcounts.$username"; |
510
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
511
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
512
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
513
|
|
|
} |
514
|
|
|
|
515
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
516
|
|
|
$pageTable = $this->getTableName($project->getDatabaseName(), 'page'); |
517
|
|
|
$sql = "SELECT " |
518
|
|
|
. " YEAR(rev_timestamp) AS `year`," |
519
|
|
|
. " page_namespace," |
520
|
|
|
. " COUNT(rev_id) AS `count` " |
521
|
|
|
. " FROM $revisionTable JOIN $pageTable ON (rev_page = page_id)" |
522
|
|
|
. " WHERE rev_user_text = :username" |
523
|
|
|
. " GROUP BY YEAR(rev_timestamp), page_namespace " |
524
|
|
|
. " ORDER BY rev_timestamp DESC "; |
525
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
526
|
|
|
$resultQuery->bindParam(":username", $username); |
527
|
|
|
$resultQuery->execute(); |
528
|
|
|
$totals = $resultQuery->fetchAll(); |
529
|
|
|
|
530
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
531
|
|
|
$cacheItem->set($totals); |
532
|
|
|
$cacheItem->expiresAfter(new DateInterval('P10M')); |
533
|
|
|
$this->cache->save($cacheItem); |
534
|
|
|
|
535
|
|
|
$this->stopwatch->stop($cacheKey); |
536
|
|
|
return $totals; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
/** |
540
|
|
|
* Get data for the timecard chart, with totals grouped by day and to the nearest two-hours. |
541
|
|
|
* @param Project $project |
542
|
|
|
* @param User $user |
543
|
|
|
* @return string[] |
544
|
|
|
*/ |
545
|
|
|
public function getTimeCard(Project $project, User $user) |
546
|
|
|
{ |
547
|
|
|
$username = $user->getUsername(); |
548
|
|
|
$cacheKey = "timecard.".$username; |
549
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
550
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
551
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
$hourInterval = 2; |
555
|
|
|
$xCalc = "ROUND(HOUR(rev_timestamp)/$hourInterval) * $hourInterval"; |
556
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
557
|
|
|
$sql = "SELECT " |
558
|
|
|
. " DAYOFWEEK(rev_timestamp) AS `y`, " |
559
|
|
|
. " $xCalc AS `x`, " |
560
|
|
|
. " COUNT(rev_id) AS `r` " |
561
|
|
|
. " FROM $revisionTable" |
562
|
|
|
. " WHERE rev_user_text = :username" |
563
|
|
|
. " GROUP BY DAYOFWEEK(rev_timestamp), $xCalc "; |
564
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
565
|
|
|
$resultQuery->bindParam(":username", $username); |
566
|
|
|
$resultQuery->execute(); |
567
|
|
|
$totals = $resultQuery->fetchAll(); |
568
|
|
|
// Scale the radii: get the max, then scale each radius. |
569
|
|
|
// This looks inefficient, but there's a max of 72 elements in this array. |
570
|
|
|
$max = 0; |
571
|
|
|
foreach ($totals as $total) { |
572
|
|
|
$max = max($max, $total['r']); |
573
|
|
|
} |
574
|
|
|
foreach ($totals as &$total) { |
575
|
|
|
$total['r'] = round($total['r'] / $max * 100); |
576
|
|
|
} |
577
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
578
|
|
|
$cacheItem->expiresAfter(new DateInterval('PT10M')); |
579
|
|
|
$cacheItem->set($totals); |
580
|
|
|
$this->cache->save($cacheItem); |
581
|
|
|
|
582
|
|
|
$this->stopwatch->stop($cacheKey); |
583
|
|
|
return $totals; |
584
|
|
|
} |
585
|
|
|
|
586
|
|
|
/** |
587
|
|
|
* Get a summary of automated edits made by the given user in their last 1000 edits. |
588
|
|
|
* Will cache the result for 10 minutes. |
589
|
|
|
* @param Project $project The project. |
590
|
|
|
* @param User $user The user. |
591
|
|
|
* @return integer[] Array of edit counts, keyed by all tool names from |
592
|
|
|
* app/config/semi_automated.yml |
593
|
|
|
* @TODO This currently uses AutomatedEditsHelper but that could probably be refactored. |
594
|
|
|
*/ |
595
|
|
|
public function countAutomatedRevisions(Project $project, User $user) |
596
|
|
|
{ |
597
|
|
|
$userId = $user->getId($project); |
598
|
|
|
$cacheKey = "automatedEdits.".$project->getDatabaseName().'.'.$userId; |
599
|
|
|
$this->stopwatch->start($cacheKey, 'XTools'); |
600
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
601
|
|
|
$this->log->debug("Using cache for $cacheKey"); |
602
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
603
|
|
|
} |
604
|
|
|
|
605
|
|
|
/** @var AutomatedEditsHelper $automatedEditsHelper */ |
606
|
|
|
$automatedEditsHelper = $this->container->get("app.automated_edits_helper"); |
607
|
|
|
|
608
|
|
|
// Get the most recent 1000 edit summaries. |
609
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
610
|
|
|
$sql = "SELECT rev_comment FROM $revisionTable |
611
|
|
|
WHERE rev_user=:userId ORDER BY rev_timestamp DESC LIMIT 1000"; |
612
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
613
|
|
|
$resultQuery->bindParam("userId", $userId); |
614
|
|
|
$resultQuery->execute(); |
615
|
|
|
$results = $resultQuery->fetchAll(); |
616
|
|
|
$editCounts = []; |
617
|
|
|
foreach ($results as $result) { |
618
|
|
|
$toolName = $automatedEditsHelper->getTool($result['rev_comment']); |
619
|
|
|
if ($toolName) { |
620
|
|
|
if (!isset($editCounts[$toolName])) { |
621
|
|
|
$editCounts[$toolName] = 0; |
622
|
|
|
} |
623
|
|
|
$editCounts[$toolName]++; |
624
|
|
|
} |
625
|
|
|
} |
626
|
|
|
arsort($editCounts); |
627
|
|
|
|
628
|
|
|
// Cache for 10 minutes. |
629
|
|
|
$this->log->debug("Saving $cacheKey to cache", [$editCounts]); |
630
|
|
|
$cacheItem = $this->cache->getItem($cacheKey); |
631
|
|
|
$cacheItem->set($editCounts); |
632
|
|
|
$cacheItem->expiresAfter(new DateInterval('PT10M')); |
633
|
|
|
$this->cache->save($cacheItem); |
634
|
|
|
|
635
|
|
|
$this->stopwatch->stop($cacheKey); |
636
|
|
|
return $editCounts; |
637
|
|
|
} |
638
|
|
|
} |
639
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.