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