1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Xtools; |
4
|
|
|
|
5
|
|
|
use Mediawiki\Api\SimpleRequest; |
6
|
|
|
|
7
|
|
|
class EditCounterRepository extends Repository |
8
|
|
|
{ |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Get revision counts for the given user. |
12
|
|
|
* @param User $user The user. |
13
|
|
|
* @returns string[] With keys: 'deleted', 'live', 'total', 'first', 'last', '24h', '7d', '30d', |
14
|
|
|
* '365d', 'small', 'large', 'with_comments', and 'minor_edits'. |
15
|
|
|
*/ |
16
|
|
|
public function getRevisionCounts(Project $project, User $user) |
17
|
|
|
{ |
18
|
|
|
// Set up cache. |
19
|
|
|
$cacheKey = 'revisioncounts.' . $user->getId($project); |
20
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
21
|
|
|
$msg = "Using logged revision counts"; |
22
|
|
|
$this->log->debug($msg, [$project->getDatabaseName(), $user->getUsername()]); |
23
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
// Prepare the queries and execute them. |
27
|
|
|
$start = microtime(); |
28
|
|
|
$archiveTable = $this->getTableName($project->getDatabaseName(), 'archive'); |
29
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
30
|
|
|
$queries = [ |
31
|
|
|
'deleted' => "SELECT COUNT(ar_id) FROM $archiveTable |
32
|
|
|
WHERE ar_user = :userId", |
33
|
|
|
'live' => "SELECT COUNT(rev_id) FROM $revisionTable |
34
|
|
|
WHERE rev_user = :userId", |
35
|
|
|
'day' => "SELECT COUNT(rev_id) FROM $revisionTable |
36
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY)", |
37
|
|
|
'week' => "SELECT COUNT(rev_id) FROM $revisionTable |
38
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 WEEK)", |
39
|
|
|
'month' => "SELECT COUNT(rev_id) FROM $revisionTable |
40
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 MONTH)", |
41
|
|
|
'year' => "SELECT COUNT(rev_id) FROM $revisionTable |
42
|
|
|
WHERE rev_user = :userId AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 YEAR)", |
43
|
|
|
'small' => "SELECT COUNT(rev_id) FROM $revisionTable |
44
|
|
|
WHERE rev_user = :userId AND rev_len < 20", |
45
|
|
|
'large' => "SELECT COUNT(rev_id) FROM $revisionTable |
46
|
|
|
WHERE rev_user = :userId AND rev_len > 1000", |
47
|
|
|
'with_comments' => "SELECT COUNT(rev_id) FROM $revisionTable |
48
|
|
|
WHERE rev_user = :userId AND rev_comment = ''", |
49
|
|
|
'minor' => "SELECT COUNT(rev_id) FROM $revisionTable |
50
|
|
|
WHERE rev_user = :userId AND rev_minor_edit = 1", |
51
|
|
|
]; |
52
|
|
|
$revisionCounts = []; |
53
|
|
|
foreach ($queries as $varName => $query) { |
54
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($query); |
55
|
|
|
$userId = $user->getId($project); |
56
|
|
|
$resultQuery->bindParam("userId", $userId); |
57
|
|
|
$resultQuery->execute(); |
58
|
|
|
$val = $resultQuery->fetchColumn(); |
59
|
|
|
$revisionCounts[$varName] = $val ?: 0; |
60
|
|
|
} |
61
|
|
|
$duration = microtime() - $start; |
62
|
|
|
$this->log->debug( |
63
|
|
|
"Retrieved revision counts in $duration", |
64
|
|
|
[$project->getDatabaseName(), $user->getUsername()] |
65
|
|
|
); |
66
|
|
|
|
67
|
|
|
// Cache for 10 minutes, and return. |
|
|
|
|
68
|
|
|
$cacheItem = |
69
|
|
|
$this->cache->getItem($cacheKey) |
70
|
|
|
->set($revisionCounts) |
71
|
|
|
->expiresAfter(new \DateInterval('PT10M')); |
72
|
|
|
$this->cache->save($cacheItem); |
73
|
|
|
|
74
|
|
|
return $revisionCounts; |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Get the first and last revision dates (in MySQL YYYYMMDDHHMMSS format). |
79
|
|
|
* @return string[] With keys 'first' and 'last'. |
80
|
|
|
*/ |
81
|
|
|
public function getRevisionDates(Project $project, User $user) |
82
|
|
|
{ |
83
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
84
|
|
|
$query = "(SELECT 'first' AS `key`, rev_timestamp AS `date` FROM $revisionTable |
85
|
|
|
WHERE rev_user = :userId ORDER BY rev_timestamp ASC LIMIT 1) |
86
|
|
|
UNION |
87
|
|
|
(SELECT 'last' AS `key`, rev_timestamp AS `date` FROM $revisionTable |
88
|
|
|
WHERE rev_user = :userId ORDER BY rev_timestamp DESC LIMIT 1)"; |
89
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($query); |
90
|
|
|
$userId = $user->getId($project); |
91
|
|
|
$resultQuery->bindParam("userId", $userId); |
92
|
|
|
$resultQuery->execute(); |
93
|
|
|
$result = $resultQuery->fetchAll(); |
94
|
|
|
$out = []; |
95
|
|
|
foreach ($result as $res) { |
96
|
|
|
$out[$res['key']] = $res['date']; |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
return $out; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Get page counts for the given user. |
104
|
|
|
* @param User $user |
105
|
|
|
* @return int[] |
106
|
|
|
*/ |
107
|
|
|
public function getPageCounts(Project $project, User $user) |
108
|
|
|
{ |
109
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
110
|
|
|
$archiveTable = $this->getTableName($project->getDatabaseName(), 'archive'); |
111
|
|
|
$loggingTable = $this->getTableName($project->getDatabaseName(), 'logging'); |
112
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare(" |
113
|
|
|
(SELECT 'edited-total' as source, COUNT(rev_page) as value |
114
|
|
|
FROM $revisionTable where rev_user_text=:username) |
115
|
|
|
UNION |
116
|
|
|
(SELECT 'edited-unique' as source, COUNT(distinct rev_page) as value |
117
|
|
|
FROM $revisionTable where rev_user_text=:username) |
118
|
|
|
UNION |
119
|
|
|
(SELECT 'created-live' as source, COUNT(*) as value from $revisionTable |
120
|
|
|
WHERE rev_user_text=:username and rev_parent_id=0) |
121
|
|
|
UNION |
122
|
|
|
(SELECT 'created-deleted' as source, COUNT(*) as value from $archiveTable |
123
|
|
|
WHERE ar_user_text=:username and ar_parent_id=0) |
124
|
|
|
UNION |
125
|
|
|
(SELECT 'moved' as source, count(*) as value from $loggingTable |
126
|
|
|
WHERE log_type='move' and log_action='move' and log_user_text=:username) |
127
|
|
|
"); |
128
|
|
|
$username = $user->getUsername(); |
129
|
|
|
$resultQuery->bindParam("username", $username); |
130
|
|
|
$resultQuery->execute(); |
131
|
|
|
$results = $resultQuery->fetchAll(); |
132
|
|
|
|
133
|
|
|
$pageCounts = array_combine(array_map(function ($e) { |
134
|
|
|
return $e['source']; |
135
|
|
|
}, $results), array_map(function ($e) { |
136
|
|
|
return $e['value']; |
137
|
|
|
}, $results)); |
138
|
|
|
|
139
|
|
|
return $pageCounts; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Get log totals for a user. |
144
|
|
|
* @param Project $project The project. |
145
|
|
|
* @param User $user The user. |
146
|
|
|
* @return integer[] Keys are "<log>-<action>" strings, values are counts. |
147
|
|
|
*/ |
148
|
|
|
public function getLogCounts(Project $project, User $user) |
149
|
|
|
{ |
150
|
|
|
$sql = "SELECT CONCAT(log_type, '-', log_action) AS source, COUNT(log_id) AS value |
151
|
|
|
FROM " . $this->getTableName($project->getDatabaseName(), 'logging') . " |
152
|
|
|
WHERE log_user = :userId |
153
|
|
|
GROUP BY log_type, log_action"; |
154
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
155
|
|
|
$userId = $user->getId($project); |
156
|
|
|
$resultQuery->bindParam('userId', $userId); |
157
|
|
|
$resultQuery->execute(); |
158
|
|
|
$results = $resultQuery->fetchAll(); |
159
|
|
|
$logCounts = array_combine( |
160
|
|
|
array_map(function ($e) { |
161
|
|
|
return $e['source']; |
162
|
|
|
}, $results), |
163
|
|
|
array_map(function ($e) { |
164
|
|
|
return $e['value']; |
165
|
|
|
}, $results) |
166
|
|
|
); |
167
|
|
|
|
168
|
|
|
// Make sure there is some value for each of the wanted counts. |
169
|
|
|
$requiredCounts = [ |
170
|
|
|
'thanks-thank', |
171
|
|
|
'review-approve', |
172
|
|
|
'patrol-patrol', |
173
|
|
|
'block-block', |
174
|
|
|
'block-unblock', |
175
|
|
|
'protect-protect', |
176
|
|
|
'protect-unprotect', |
177
|
|
|
'delete-delete', |
178
|
|
|
'delete-revision', |
179
|
|
|
'delete-restore', |
180
|
|
|
'import-import', |
181
|
|
|
'upload-upload', |
182
|
|
|
'upload-overwrite', |
183
|
|
|
]; |
184
|
|
|
foreach ($requiredCounts as $req) { |
185
|
|
|
if (!isset($logCounts[$req])) { |
186
|
|
|
$logCounts[$req] = 0; |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// Add Commons upload count, if applicable. |
191
|
|
|
$logCounts['files_uploaded_commons'] = 0; |
192
|
|
|
if ($this->isLabs()) { |
193
|
|
|
$sql = "SELECT COUNT(log_id) FROM commonswiki_p.logging_userindex |
194
|
|
|
WHERE log_type = 'upload' AND log_action = 'upload' AND log_user = :userId"; |
195
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
196
|
|
|
$resultQuery->bindParam('userId', $userId); |
197
|
|
|
$resultQuery->execute(); |
198
|
|
|
$logCounts['files_uploaded_commons'] = $resultQuery->fetchColumn(); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
return $logCounts; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Get a user's total edit count on one or more project. |
206
|
|
|
* Requires the CentralAuth extension to be installed on the project. |
207
|
|
|
* |
208
|
|
|
* @param string $username The username. |
209
|
|
|
* @param Project $project The project. |
210
|
|
|
* @return mixed[]|boolean Array of total edit counts, or false if none could be found. |
211
|
|
|
*/ |
212
|
|
|
public function getRevisionCountsAllProjects($username, Project $project) |
213
|
|
|
{ |
214
|
|
|
$api = $this->getMediawikiApi($project); |
215
|
|
|
$params = [ |
216
|
|
|
'meta' => 'globaluserinfo', |
217
|
|
|
'guiprop' => 'editcount|merged', |
218
|
|
|
'guiuser' => $username, |
219
|
|
|
]; |
220
|
|
|
$query = new SimpleRequest('query', $params); |
221
|
|
|
$result = $api->getRequest($query); |
222
|
|
|
if (!isset($result['query']['globaluserinfo']['merged'])) { |
223
|
|
|
return false; |
224
|
|
|
} |
225
|
|
|
$out = []; |
226
|
|
|
foreach ($result['query']['globaluserinfo']['merged'] as $merged) { |
227
|
|
|
// The array structure here should match what's done in |
228
|
|
|
// EditCounterHelper::getTopProjectsEditCounts() |
|
|
|
|
229
|
|
|
$out[$merged['wiki']] = [ |
230
|
|
|
'total' => $merged['editcount'], |
231
|
|
|
]; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
return $out; |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* Get total edit counts for the top 10 projects for this user. |
239
|
|
|
* @param string $username The username. |
240
|
|
|
* @return string[] Elements are arrays with 'dbName', 'url', 'name', and 'total'. |
241
|
|
|
*/ |
242
|
|
|
public function getTopProjectsEditCounts($projectUrl, $username, $numProjects = 10) |
243
|
|
|
{ |
244
|
|
|
$this->debug("Getting top project edit counts for $username"); |
|
|
|
|
245
|
|
|
$cacheKey = 'topprojectseditcounts.' . $username; |
246
|
|
|
if ($this->cacheHas($cacheKey)) { |
|
|
|
|
247
|
|
|
return $this->cacheGet($cacheKey); |
|
|
|
|
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
// First try to get the edit count from the API (if CentralAuth is installed). |
251
|
|
|
/** @var ApiHelper */ |
252
|
|
|
$api = $this->container->get('app.api_helper'); |
253
|
|
|
$topEditCounts = $api->getEditCount($username, $projectUrl); |
254
|
|
|
if (false === $topEditCounts) { |
255
|
|
|
// If no CentralAuth, fall back to querying each database in turn. |
256
|
|
|
foreach ($this->labsHelper->getProjectsInfo() as $project) { |
257
|
|
|
$this->container->get('logger')->debug('Getting edit count for ' . $project['url']); |
258
|
|
|
$revisionTableName = $this->labsHelper->getTable('revision', $project['dbName']); |
|
|
|
|
259
|
|
|
$sql = "SELECT COUNT(rev_id) FROM $revisionTableName WHERE rev_user_text=:username"; |
260
|
|
|
$stmt = $this->replicas->prepare($sql); |
|
|
|
|
261
|
|
|
$stmt->bindParam("username", $username); |
262
|
|
|
$stmt->execute(); |
263
|
|
|
$total = (int)$stmt->fetchColumn(); |
264
|
|
|
$topEditCounts[$project['dbName']] = array_merge($project, ['total' => $total]); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
uasort($topEditCounts, function ($a, $b) { |
268
|
|
|
return $b['total'] - $a['total']; |
269
|
|
|
}); |
270
|
|
|
$out = array_slice($topEditCounts, 0, $numProjects); |
271
|
|
|
|
272
|
|
|
// Cache for ten minutes. |
273
|
|
|
$this->cacheSave($cacheKey, $out, 'PT10M'); |
|
|
|
|
274
|
|
|
|
275
|
|
|
return $out; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* Get the given user's total edit counts per namespace. |
280
|
|
|
* @param string $username The username of the user. |
281
|
|
|
* @return integer[] Array keys are namespace IDs, values are the edit counts. |
282
|
|
|
*/ |
283
|
|
|
public function getNamespaceTotals($username) |
284
|
|
|
{ |
285
|
|
|
$userId = $this->getUserId($username); |
|
|
|
|
286
|
|
|
$sql = "SELECT page_namespace, count(rev_id) AS total |
287
|
|
|
FROM " . $this->labsHelper->getTable('revision') . " r |
288
|
|
|
JOIN " . $this->labsHelper->getTable('page') . " p on r.rev_page = p.page_id |
289
|
|
|
WHERE r.rev_user = :id GROUP BY page_namespace"; |
290
|
|
|
$resultQuery = $this->replicas->prepare($sql); |
291
|
|
|
$resultQuery->bindParam(":id", $userId); |
292
|
|
|
$resultQuery->execute(); |
293
|
|
|
$results = $resultQuery->fetchAll(); |
294
|
|
|
$namespaceTotals = array_combine(array_map(function ($e) { |
295
|
|
|
return $e['page_namespace']; |
296
|
|
|
}, $results), array_map(function ($e) { |
297
|
|
|
return $e['total']; |
298
|
|
|
}, $results)); |
299
|
|
|
|
300
|
|
|
return $namespaceTotals; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* Get this user's most recent 10 edits across all projects. |
305
|
|
|
* @param string $username The username. |
306
|
|
|
* @param integer $topN The number of items to return. |
307
|
|
|
* @param integer $days The number of days to search from each wiki. |
308
|
|
|
* @return string[] |
309
|
|
|
*/ |
310
|
|
|
public function getRecentGlobalContribs($username, $projects = [], $topN = 10, $days = 30) |
311
|
|
|
{ |
312
|
|
|
$allRevisions = []; |
313
|
|
|
foreach ($this->labsHelper->getProjectsInfo($projects) as $project) { |
314
|
|
|
$cacheKey = "globalcontribs.{$project['dbName']}.$username"; |
315
|
|
|
if ($this->cacheHas($cacheKey)) { |
|
|
|
|
316
|
|
|
$revisions = $this->cacheGet($cacheKey); |
|
|
|
|
317
|
|
|
} else { |
318
|
|
|
$sql = |
319
|
|
|
"SELECT rev_id, rev_timestamp, UNIX_TIMESTAMP(rev_timestamp) AS unix_timestamp, " . |
320
|
|
|
" rev_minor_edit, rev_deleted, rev_len, rev_parent_id, rev_comment, " . |
321
|
|
|
" page_title, page_namespace " . " FROM " . |
322
|
|
|
$this->labsHelper->getTable('revision', $project['dbName']) . " JOIN " . |
323
|
|
|
$this->labsHelper->getTable('page', $project['dbName']) . |
324
|
|
|
" ON (rev_page = page_id)" . |
325
|
|
|
" WHERE rev_timestamp > NOW() - INTERVAL $days DAY AND rev_user_text LIKE :username" . |
326
|
|
|
" ORDER BY rev_timestamp DESC" . " LIMIT 10"; |
327
|
|
|
$resultQuery = $this->replicas->prepare($sql); |
328
|
|
|
$resultQuery->bindParam(":username", $username); |
329
|
|
|
$resultQuery->execute(); |
330
|
|
|
$revisions = $resultQuery->fetchAll(); |
331
|
|
|
$this->cacheSave($cacheKey, $revisions, 'PT15M'); |
|
|
|
|
332
|
|
|
} |
333
|
|
|
if (count($revisions) === 0) { |
334
|
|
|
continue; |
335
|
|
|
} |
336
|
|
|
$revsWithProject = array_map(function (&$item) use ($project) { |
337
|
|
|
$item['project_name'] = $project['wikiName']; |
338
|
|
|
$item['project_url'] = $project['url']; |
339
|
|
|
$item['project_db_name'] = $project['dbName']; |
340
|
|
|
$item['rev_time_formatted'] = date('Y-m-d H:i', $item['unix_timestamp']); |
341
|
|
|
|
342
|
|
|
return $item; |
343
|
|
|
}, $revisions); |
344
|
|
|
$allRevisions = array_merge($allRevisions, $revsWithProject); |
345
|
|
|
} |
346
|
|
|
usort($allRevisions, function ($a, $b) { |
347
|
|
|
return $b['rev_timestamp'] - $a['rev_timestamp']; |
348
|
|
|
}); |
349
|
|
|
|
350
|
|
|
return array_slice($allRevisions, 0, $topN); |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Get data for a bar chart of monthly edit totals per namespace. |
355
|
|
|
* @param string $username The username. |
356
|
|
|
* @return string[] |
357
|
|
|
*/ |
358
|
|
|
public function getMonthCounts($username) |
359
|
|
|
{ |
360
|
|
|
$cacheKey = "monthcounts.$username"; |
361
|
|
|
if ($this->cacheHas($cacheKey)) { |
|
|
|
|
362
|
|
|
return $this->cacheGet($cacheKey); |
|
|
|
|
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
$sql = |
366
|
|
|
"SELECT " . " YEAR(rev_timestamp) AS `year`," . |
367
|
|
|
" MONTH(rev_timestamp) AS `month`," . " page_namespace," . |
368
|
|
|
" COUNT(rev_id) AS `count` " . " FROM " . $this->labsHelper->getTable('revision') . |
369
|
|
|
" JOIN " . $this->labsHelper->getTable('page') . " ON (rev_page = page_id)" . |
370
|
|
|
" WHERE rev_user_text = :username" . |
371
|
|
|
" GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), page_namespace " . |
372
|
|
|
" ORDER BY rev_timestamp DESC"; |
373
|
|
|
$resultQuery = $this->replicas->prepare($sql); |
374
|
|
|
$resultQuery->bindParam(":username", $username); |
375
|
|
|
$resultQuery->execute(); |
376
|
|
|
$totals = $resultQuery->fetchAll(); |
377
|
|
|
$out = [ |
378
|
|
|
'years' => [], |
379
|
|
|
'namespaces' => [], |
380
|
|
|
'totals' => [], |
381
|
|
|
]; |
382
|
|
|
$out['max_year'] = 0; |
383
|
|
|
$out['min_year'] = date('Y'); |
384
|
|
|
foreach ($totals as $total) { |
385
|
|
|
// Collect all applicable years and namespaces. |
386
|
|
|
$out['max_year'] = max($out['max_year'], $total['year']); |
387
|
|
|
$out['min_year'] = min($out['min_year'], $total['year']); |
388
|
|
|
// Collate the counts by namespace, and then year and month. |
389
|
|
|
$ns = $total['page_namespace']; |
390
|
|
|
if (!isset($out['totals'][$ns])) { |
391
|
|
|
$out['totals'][$ns] = []; |
392
|
|
|
} |
393
|
|
|
$out['totals'][$ns][$total['year'] . $total['month']] = $total['count']; |
394
|
|
|
} |
395
|
|
|
// Fill in the blanks (where no edits were made in a given month for a namespace). |
396
|
|
|
for ($y = $out['min_year']; $y <= $out['max_year']; $y++) { |
397
|
|
|
for ($m = 1; $m <= 12; $m++) { |
398
|
|
|
foreach ($out['totals'] as $nsId => &$total) { |
399
|
|
|
if (!isset($total[$y . $m])) { |
400
|
|
|
$total[$y . $m] = 0; |
401
|
|
|
} |
402
|
|
|
} |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
$this->cacheSave($cacheKey, $out, 'PT10M'); |
|
|
|
|
406
|
|
|
|
407
|
|
|
return $out; |
|
|
|
|
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Get yearly edit totals for this user, grouped by namespace. |
412
|
|
|
* @param string $username |
413
|
|
|
* @return string[] ['<namespace>' => ['<year>' => 'total', ... ], ... ] |
414
|
|
|
*/ |
415
|
|
|
public function getYearCounts($username) |
416
|
|
|
{ |
417
|
|
|
$cacheKey = "yearcounts.$username"; |
418
|
|
|
if ($this->cacheHas($cacheKey)) { |
|
|
|
|
419
|
|
|
return $this->cacheGet($cacheKey); |
|
|
|
|
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
$sql = |
423
|
|
|
"SELECT " . " SUBSTR(CAST(rev_timestamp AS CHAR(4)), 1, 4) AS `year`," . |
424
|
|
|
" page_namespace," . " COUNT(rev_id) AS `count` " . " FROM " . |
425
|
|
|
$this->labsHelper->getTable('revision') . " JOIN " . |
426
|
|
|
$this->labsHelper->getTable('page') . " ON (rev_page = page_id)" . |
427
|
|
|
" WHERE rev_user_text = :username" . |
428
|
|
|
" GROUP BY SUBSTR(CAST(rev_timestamp AS CHAR(4)), 1, 4), page_namespace " . |
429
|
|
|
" ORDER BY rev_timestamp DESC "; |
430
|
|
|
$resultQuery = $this->replicas->prepare($sql); |
431
|
|
|
$resultQuery->bindParam(":username", $username); |
432
|
|
|
$resultQuery->execute(); |
433
|
|
|
$totals = $resultQuery->fetchAll(); |
434
|
|
|
$out = [ |
435
|
|
|
'years' => [], |
436
|
|
|
'namespaces' => [], |
437
|
|
|
'totals' => [], |
438
|
|
|
]; |
439
|
|
|
foreach ($totals as $total) { |
440
|
|
|
$out['years'][$total['year']] = $total['year']; |
441
|
|
|
$out['namespaces'][$total['page_namespace']] = $total['page_namespace']; |
442
|
|
|
if (!isset($out['totals'][$total['page_namespace']])) { |
443
|
|
|
$out['totals'][$total['page_namespace']] = []; |
444
|
|
|
} |
445
|
|
|
$out['totals'][$total['page_namespace']][$total['year']] = $total['count']; |
446
|
|
|
} |
447
|
|
|
$this->cacheSave($cacheKey, $out, 'PT10M'); |
|
|
|
|
448
|
|
|
|
449
|
|
|
return $out; |
|
|
|
|
450
|
|
|
} |
451
|
|
|
|
452
|
|
|
/** |
453
|
|
|
* Get data for the timecard chart, with totals grouped by day and to the nearest two-hours. |
454
|
|
|
* @param string $username The user's username. |
455
|
|
|
* @return string[] |
456
|
|
|
*/ |
457
|
|
|
public function getTimeCard($username) |
458
|
|
|
{ |
459
|
|
|
$cacheKey = "timecard.$username"; |
460
|
|
|
if ($this->cacheHas($cacheKey)) { |
|
|
|
|
461
|
|
|
return $this->cacheGet($cacheKey); |
|
|
|
|
462
|
|
|
} |
463
|
|
|
|
464
|
|
|
$hourInterval = 2; |
465
|
|
|
$xCalc = "ROUND(HOUR(rev_timestamp)/$hourInterval)*$hourInterval"; |
466
|
|
|
$sql = |
467
|
|
|
"SELECT " . " DAYOFWEEK(rev_timestamp) AS `y`, " . " $xCalc AS `x`, " . |
468
|
|
|
" COUNT(rev_id) AS `r` " . " FROM " . $this->labsHelper->getTable('revision') . |
469
|
|
|
" WHERE rev_user_text = :username" . " GROUP BY DAYOFWEEK(rev_timestamp), $xCalc " . |
470
|
|
|
" "; |
471
|
|
|
$resultQuery = $this->replicas->prepare($sql); |
472
|
|
|
$resultQuery->bindParam(":username", $username); |
473
|
|
|
$resultQuery->execute(); |
474
|
|
|
$totals = $resultQuery->fetchAll(); |
475
|
|
|
// Scale the radii: get the max, then scale each radius. |
476
|
|
|
// This looks inefficient, but there's a max of 72 elements in this array. |
477
|
|
|
$max = 0; |
478
|
|
|
foreach ($totals as $total) { |
479
|
|
|
$max = max($max, $total['r']); |
480
|
|
|
} |
481
|
|
|
foreach ($totals as &$total) { |
482
|
|
|
$total['r'] = round($total['r'] / $max * 100); |
483
|
|
|
} |
484
|
|
|
$this->cacheSave($cacheKey, $totals, 'PT10M'); |
|
|
|
|
485
|
|
|
|
486
|
|
|
return $totals; |
487
|
|
|
} |
488
|
|
|
|
489
|
|
|
/** |
490
|
|
|
* Get a summary of automated edits made by the given user in their last 1000 edits. |
491
|
|
|
* Will cache the result for 10 minutes. |
492
|
|
|
* @param User $user The user. |
493
|
|
|
* @return integer[] Array of edit counts, keyed by all tool names from |
494
|
|
|
* app/config/semi_automated.yml |
495
|
|
|
* @TODO this is broke |
496
|
|
|
*/ |
497
|
|
|
public function countAutomatedRevisions(Project $project, User $user) |
498
|
|
|
{ |
499
|
|
|
$userId = $user->getId($project); |
500
|
|
|
$cacheKey = "automatedEdits.".$project->getDatabaseName().'.'.$userId; |
501
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
502
|
|
|
$this->log->debug("Using cache for $cacheKey"); |
503
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
504
|
|
|
} |
505
|
|
|
|
506
|
|
|
// Get the most recent 1000 edit summaries. |
507
|
|
|
$revisionTable = $this->getTableName($project->getDatabaseName(), 'revision'); |
508
|
|
|
$sql = "SELECT rev_comment FROM $revisionTable |
509
|
|
|
WHERE rev_user=:userId ORDER BY rev_timestamp DESC LIMIT 1000"; |
510
|
|
|
$resultQuery = $this->getProjectsConnection()->prepare($sql); |
511
|
|
|
$resultQuery->bindParam("userId", $userId); |
512
|
|
|
$resultQuery->execute(); |
513
|
|
|
$results = $resultQuery->fetchAll(); |
514
|
|
|
$out = []; |
515
|
|
|
foreach ($results as $result) { |
516
|
|
|
$toolName = $this->getTool($result['rev_comment']); |
|
|
|
|
517
|
|
|
if ($toolName) { |
518
|
|
|
if (!isset($out[$toolName])) { |
519
|
|
|
$out[$toolName] = 0; |
520
|
|
|
} |
521
|
|
|
$out[$toolName]++; |
522
|
|
|
} |
523
|
|
|
} |
524
|
|
|
arsort($out); |
525
|
|
|
|
526
|
|
|
// Cache for 10 minutes. |
527
|
|
|
$this->log->debug("Saving $cacheKey to cache", [$out]); |
528
|
|
|
$this->cacheSave($cacheKey, $out, 'PT10M'); |
|
|
|
|
529
|
|
|
|
530
|
|
|
return $out; |
531
|
|
|
} |
532
|
|
|
} |
533
|
|
|
|
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.