|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
declare(strict_types = 1); |
|
4
|
|
|
|
|
5
|
|
|
namespace App\Repository; |
|
6
|
|
|
|
|
7
|
|
|
use App\Model\Project; |
|
8
|
|
|
use App\Model\User; |
|
9
|
|
|
use Doctrine\Persistence\ManagerRegistry; |
|
10
|
|
|
use GuzzleHttp\Client; |
|
11
|
|
|
use Psr\Cache\CacheItemPoolInterface; |
|
12
|
|
|
use Psr\Log\LoggerInterface; |
|
13
|
|
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; |
|
14
|
|
|
use Wikimedia\IPUtils; |
|
15
|
|
|
|
|
16
|
|
|
/** |
|
17
|
|
|
* An EditCounterRepository is responsible for retrieving edit count information from the |
|
18
|
|
|
* databases and API. It doesn't do any post-processing of that information. |
|
19
|
|
|
* @codeCoverageIgnore |
|
20
|
|
|
*/ |
|
21
|
|
|
class EditCounterRepository extends Repository |
|
22
|
|
|
{ |
|
23
|
|
|
protected AutoEditsRepository $autoEditsRepo; |
|
24
|
|
|
protected ProjectRepository $projectRepo; |
|
25
|
|
|
|
|
26
|
|
|
public function __construct( |
|
27
|
|
|
ManagerRegistry $managerRegistry, |
|
28
|
|
|
CacheItemPoolInterface $cache, |
|
29
|
|
|
Client $guzzle, |
|
30
|
|
|
LoggerInterface $logger, |
|
31
|
|
|
ParameterBagInterface $parameterBag, |
|
32
|
|
|
bool $isWMF, |
|
33
|
|
|
int $queryTimeout, |
|
34
|
|
|
ProjectRepository $projectRepo, |
|
35
|
|
|
AutoEditsRepository $autoEditsRepo |
|
36
|
|
|
) { |
|
37
|
|
|
$this->projectRepo = $projectRepo; |
|
38
|
|
|
$this->autoEditsRepo = $autoEditsRepo; |
|
39
|
|
|
parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); |
|
40
|
|
|
} |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Get data about revisions, pages, etc. |
|
44
|
|
|
* @param Project $project The project. |
|
45
|
|
|
* @param User $user The user. |
|
46
|
|
|
* @return string[] With keys: 'deleted', 'live', 'total', '24h', '7d', '30d', |
|
47
|
|
|
* '365d', 'small', 'large', 'with_comments', and 'minor_edits', ... |
|
48
|
|
|
*/ |
|
49
|
|
|
public function getPairData(Project $project, User $user): array |
|
50
|
|
|
{ |
|
51
|
|
|
// Set up cache. |
|
52
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_pairdata'); |
|
53
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
54
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
// Prepare the queries and execute them. |
|
58
|
|
|
$archiveTable = $project->getTableName('archive'); |
|
59
|
|
|
$revisionTable = $project->getTableName('revision'); |
|
60
|
|
|
$pageTable = $project->getTableName('page'); |
|
61
|
|
|
|
|
62
|
|
|
// Always JOIN on page, see T355027 |
|
63
|
|
|
$pageJoin = "JOIN $pageTable ON rev_page = page_id"; |
|
64
|
|
|
|
|
65
|
|
|
if ($user->isIpRange()) { |
|
66
|
|
|
$ipcTable = $project->getTableName('ip_changes'); |
|
67
|
|
|
$ipcJoin = "JOIN $ipcTable ON ipc_rev_id = rev_id"; |
|
68
|
|
|
$whereClause = "ipc_hex BETWEEN :startIp AND :endIp"; |
|
69
|
|
|
$archiveQueries = ''; |
|
70
|
|
|
$params = []; |
|
71
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
72
|
|
|
} else { |
|
73
|
|
|
$ipcJoin = ''; |
|
74
|
|
|
$whereClause = 'rev_actor = :actorId'; |
|
75
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
76
|
|
|
$archiveQueries = " |
|
77
|
|
|
SELECT 'deleted' AS `key`, COUNT(ar_id) AS val FROM $archiveTable |
|
78
|
|
|
WHERE ar_actor = :actorId |
|
79
|
|
|
) UNION ( |
|
80
|
|
|
SELECT 'edited-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val` FROM $archiveTable |
|
81
|
|
|
WHERE ar_actor = :actorId |
|
82
|
|
|
) UNION ( |
|
83
|
|
|
SELECT 'created-deleted' AS `key`, COUNT(DISTINCT ar_page_id) AS `val` FROM $archiveTable |
|
84
|
|
|
WHERE ar_actor = :actorId AND ar_parent_id = 0 |
|
85
|
|
|
) UNION ("; |
|
86
|
|
|
} |
|
87
|
|
|
|
|
88
|
|
|
$sql = " |
|
89
|
|
|
($archiveQueries |
|
90
|
|
|
|
|
91
|
|
|
-- Revision counts. |
|
92
|
|
|
SELECT 'live' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
93
|
|
|
$pageJoin |
|
94
|
|
|
$ipcJoin |
|
95
|
|
|
WHERE $whereClause |
|
96
|
|
|
) UNION ( |
|
97
|
|
|
SELECT 'day' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
98
|
|
|
$pageJoin |
|
99
|
|
|
$ipcJoin |
|
100
|
|
|
WHERE $whereClause AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 DAY) |
|
101
|
|
|
) UNION ( |
|
102
|
|
|
SELECT 'week' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
103
|
|
|
$pageJoin |
|
104
|
|
|
$ipcJoin |
|
105
|
|
|
WHERE $whereClause AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 WEEK) |
|
106
|
|
|
) UNION ( |
|
107
|
|
|
SELECT 'month' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
108
|
|
|
$pageJoin |
|
109
|
|
|
$ipcJoin |
|
110
|
|
|
WHERE $whereClause AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 MONTH) |
|
111
|
|
|
) UNION ( |
|
112
|
|
|
SELECT 'year' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
113
|
|
|
$pageJoin |
|
114
|
|
|
$ipcJoin |
|
115
|
|
|
WHERE $whereClause AND rev_timestamp >= DATE_SUB(NOW(), INTERVAL 1 YEAR) |
|
116
|
|
|
) UNION ( |
|
117
|
|
|
SELECT 'minor' AS `key`, COUNT(rev_id) AS val FROM $revisionTable |
|
118
|
|
|
$pageJoin |
|
119
|
|
|
$ipcJoin |
|
120
|
|
|
WHERE $whereClause AND rev_minor_edit = 1 |
|
121
|
|
|
|
|
122
|
|
|
-- Page counts. |
|
123
|
|
|
) UNION ( |
|
124
|
|
|
SELECT 'edited-live' AS `key`, COUNT(DISTINCT rev_page) AS `val` |
|
125
|
|
|
FROM $revisionTable |
|
126
|
|
|
$pageJoin |
|
127
|
|
|
$ipcJoin |
|
128
|
|
|
WHERE $whereClause |
|
129
|
|
|
) UNION ( |
|
130
|
|
|
SELECT 'created-live' AS `key`, COUNT(DISTINCT rev_page) AS `val` |
|
131
|
|
|
FROM $revisionTable |
|
132
|
|
|
$pageJoin |
|
133
|
|
|
$ipcJoin |
|
134
|
|
|
WHERE $whereClause AND rev_parent_id = 0 |
|
135
|
|
|
)"; |
|
136
|
|
|
|
|
137
|
|
|
$resultQuery = $this->executeProjectsQuery($project, $sql, $params); |
|
138
|
|
|
|
|
139
|
|
|
$revisionCounts = []; |
|
140
|
|
|
while ($result = $resultQuery->fetchAssociative()) { |
|
141
|
|
|
$revisionCounts[$result['key']] = (int)$result['val']; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
// Cache and return. |
|
145
|
|
|
return $this->setCache($cacheKey, $revisionCounts); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Get log totals for a user. |
|
150
|
|
|
* @param Project $project The project. |
|
151
|
|
|
* @param User $user The user. |
|
152
|
|
|
* @return int[] Keys are "<log>-<action>" strings, values are counts. |
|
153
|
|
|
*/ |
|
154
|
|
|
public function getLogCounts(Project $project, User $user): array |
|
155
|
|
|
{ |
|
156
|
|
|
// Set up cache. |
|
157
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_logcounts'); |
|
158
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
159
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
160
|
|
|
} |
|
161
|
|
|
|
|
162
|
|
|
// Query. |
|
163
|
|
|
$loggingTable = $this->getTableName($project->getDatabaseName(), 'logging'); |
|
164
|
|
|
$sql = " |
|
165
|
|
|
(SELECT CONCAT(log_type, '-', log_action) AS source, COUNT(log_id) AS value |
|
166
|
|
|
FROM $loggingTable |
|
167
|
|
|
WHERE log_actor = :actorId |
|
168
|
|
|
GROUP BY log_type, log_action |
|
169
|
|
|
)"; |
|
170
|
|
|
|
|
171
|
|
|
$results = $this->executeProjectsQuery($project, $sql, [ |
|
172
|
|
|
'actorId' => $user->getActorId($project), |
|
173
|
|
|
])->fetchAllAssociative(); |
|
174
|
|
|
|
|
175
|
|
|
$logCounts = array_combine( |
|
176
|
|
|
array_map(function ($e) { |
|
177
|
|
|
return $e['source']; |
|
178
|
|
|
}, $results), |
|
179
|
|
|
array_map(function ($e) { |
|
180
|
|
|
return (int)$e['value']; |
|
181
|
|
|
}, $results) |
|
182
|
|
|
); |
|
183
|
|
|
|
|
184
|
|
|
// Make sure there is some value for each of the wanted counts. |
|
185
|
|
|
$requiredCounts = [ |
|
186
|
|
|
'thanks-thank', |
|
187
|
|
|
'review-approve', |
|
188
|
|
|
'newusers-create2', |
|
189
|
|
|
'newusers-byemail', |
|
190
|
|
|
'patrol-patrol', |
|
191
|
|
|
'block-block', |
|
192
|
|
|
'block-reblock', |
|
193
|
|
|
'block-unblock', |
|
194
|
|
|
'protect-protect', |
|
195
|
|
|
'protect-modify', |
|
196
|
|
|
'protect-unprotect', |
|
197
|
|
|
'rights-rights', |
|
198
|
|
|
'move-move', |
|
199
|
|
|
'delete-delete', |
|
200
|
|
|
'delete-revision', |
|
201
|
|
|
'delete-restore', |
|
202
|
|
|
'import-import', |
|
203
|
|
|
'import-interwiki', |
|
204
|
|
|
'import-upload', |
|
205
|
|
|
'upload-upload', |
|
206
|
|
|
'upload-overwrite', |
|
207
|
|
|
'abusefilter-modify', |
|
208
|
|
|
'merge-merge', |
|
209
|
|
|
'contentmodel-change', |
|
210
|
|
|
'contentmodel-new', |
|
211
|
|
|
]; |
|
212
|
|
|
foreach ($requiredCounts as $req) { |
|
213
|
|
|
if (!isset($logCounts[$req])) { |
|
214
|
|
|
$logCounts[$req] = 0; |
|
215
|
|
|
} |
|
216
|
|
|
} |
|
217
|
|
|
|
|
218
|
|
|
// Cache and return. |
|
219
|
|
|
return $this->setCache($cacheKey, $logCounts); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
/** |
|
223
|
|
|
* Get counts of files moved, and files moved/uploaded on Commons. |
|
224
|
|
|
* Local file uploads are counted in getLogCounts() since we're querying the same rows anyway. |
|
225
|
|
|
* @param Project $project |
|
226
|
|
|
* @param User $user |
|
227
|
|
|
* @return array |
|
228
|
|
|
*/ |
|
229
|
|
|
public function getFileCounts(Project $project, User $user): array |
|
230
|
|
|
{ |
|
231
|
|
|
// Anons can't upload or move files. |
|
232
|
|
|
if ($user->isAnon()) { |
|
233
|
|
|
return []; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
// Set up cache. |
|
237
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_filecounts'); |
|
238
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
239
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
240
|
|
|
} |
|
241
|
|
|
|
|
242
|
|
|
$loggingTable = $project->getTableName('logging'); |
|
243
|
|
|
|
|
244
|
|
|
$sql = "SELECT 'files_moved' AS `key`, COUNT(log_id) AS `val` |
|
245
|
|
|
FROM $loggingTable |
|
246
|
|
|
WHERE log_actor = :actorId |
|
247
|
|
|
AND log_type = 'move' |
|
248
|
|
|
AND log_action = 'move' |
|
249
|
|
|
AND log_namespace = 6"; |
|
250
|
|
|
$results = $this->executeProjectsQuery($project, $sql, [ |
|
251
|
|
|
'actorId' => $user->getActorId($project), |
|
252
|
|
|
])->fetchAllAssociative(); |
|
253
|
|
|
|
|
254
|
|
|
if ($this->isWMF && 'commons.wikimedia.org' !== $project->getDomain()) { |
|
255
|
|
|
$results = array_merge($results, $this->getFileCountsCommons($user)); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
$counts = array_combine( |
|
259
|
|
|
array_map(function ($e) { |
|
260
|
|
|
return $e['key']; |
|
261
|
|
|
}, $results), |
|
262
|
|
|
array_map(function ($e) { |
|
263
|
|
|
return (int)$e['val']; |
|
264
|
|
|
}, $results) |
|
265
|
|
|
); |
|
266
|
|
|
|
|
267
|
|
|
// Cache and return. |
|
268
|
|
|
return $this->setCache($cacheKey, $counts); |
|
269
|
|
|
} |
|
270
|
|
|
|
|
271
|
|
|
/** |
|
272
|
|
|
* Get count of files moved and uploaded on Commons. |
|
273
|
|
|
* @param User $user |
|
274
|
|
|
* @return array |
|
275
|
|
|
*/ |
|
276
|
|
|
protected function getFileCountsCommons(User $user): array |
|
277
|
|
|
{ |
|
278
|
|
|
$commonsProject = $this->projectRepo->getProject('commonswiki'); |
|
279
|
|
|
$loggingTableCommons = $commonsProject->getTableName('logging'); |
|
280
|
|
|
$sql = "(SELECT 'files_moved_commons' AS `key`, COUNT(log_id) AS `val` |
|
281
|
|
|
FROM $loggingTableCommons |
|
282
|
|
|
WHERE log_actor = :actorId AND log_type = 'move' |
|
283
|
|
|
AND log_action = 'move' AND log_namespace = 6 |
|
284
|
|
|
) UNION ( |
|
285
|
|
|
SELECT 'files_uploaded_commons' AS `key`, COUNT(log_id) AS `val` |
|
286
|
|
|
FROM $loggingTableCommons |
|
287
|
|
|
WHERE log_actor = :actorId AND log_type = 'upload' AND log_action = 'upload')"; |
|
288
|
|
|
return $this->executeProjectsQuery($commonsProject, $sql, [ |
|
289
|
|
|
'actorId' => $user->getActorId($commonsProject), |
|
290
|
|
|
])->fetchAllAssociative(); |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Get the IDs and timestamps of the latest edit and logged action by the given user. |
|
295
|
|
|
* @param Project $project |
|
296
|
|
|
* @param User $user |
|
297
|
|
|
* @return string[] With keys 'rev_first', 'rev_latest', 'log_latest'. |
|
298
|
|
|
*/ |
|
299
|
|
|
public function getFirstAndLatestActions(Project $project, User $user): array |
|
300
|
|
|
{ |
|
301
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_first_latest_actions'); |
|
302
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
303
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
304
|
|
|
} |
|
305
|
|
|
|
|
306
|
|
|
$loggingTable = $project->getTableName('logging', 'userindex'); |
|
307
|
|
|
if ($user->isIpRange()) { |
|
308
|
|
|
$fromTable = $project->getTableName('ip_changes'); |
|
309
|
|
|
$idColumn = 'ipc_rev_id'; |
|
310
|
|
|
$timestampColumn = 'ipc_rev_timestamp'; |
|
311
|
|
|
$whereClause = "ipc_hex BETWEEN :startIp AND :endIp"; |
|
312
|
|
|
$params = []; |
|
313
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
314
|
|
|
$logQuery = ''; |
|
315
|
|
|
} else { |
|
316
|
|
|
$fromTable = $project->getTableName('revision'); |
|
317
|
|
|
$idColumn = 'rev_id'; |
|
318
|
|
|
$timestampColumn = 'rev_timestamp'; |
|
319
|
|
|
$whereClause = 'rev_actor = :actorId'; |
|
320
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
321
|
|
|
$logQuery = " |
|
322
|
|
|
SELECT 'log_latest' AS `key`, log_id AS `id`, |
|
323
|
|
|
log_timestamp AS `timestamp`, log_type AS `type` |
|
324
|
|
|
FROM $loggingTable |
|
325
|
|
|
WHERE log_actor = :actorId |
|
326
|
|
|
ORDER BY log_timestamp DESC LIMIT 1 |
|
327
|
|
|
) UNION ("; |
|
328
|
|
|
} |
|
329
|
|
|
|
|
330
|
|
|
$sql = "( |
|
331
|
|
|
$logQuery |
|
332
|
|
|
SELECT 'rev_first' AS `key`, $idColumn AS `id`, |
|
333
|
|
|
$timestampColumn AS `timestamp`, NULL as `type` |
|
334
|
|
|
FROM $fromTable |
|
335
|
|
|
WHERE $whereClause |
|
336
|
|
|
ORDER BY $timestampColumn ASC LIMIT 1 |
|
337
|
|
|
) UNION ( |
|
338
|
|
|
SELECT 'rev_latest' AS `key`, $idColumn AS `id`, |
|
339
|
|
|
$timestampColumn AS `timestamp`, NULL as `type` |
|
340
|
|
|
FROM $fromTable |
|
341
|
|
|
WHERE $whereClause |
|
342
|
|
|
ORDER BY $timestampColumn DESC LIMIT 1 |
|
343
|
|
|
)"; |
|
344
|
|
|
|
|
345
|
|
|
$resultQuery = $this->executeProjectsQuery($project, $sql, $params); |
|
346
|
|
|
|
|
347
|
|
|
$actions = []; |
|
348
|
|
|
while ($result = $resultQuery->fetchAssociative()) { |
|
349
|
|
|
$actions[$result['key']] = [ |
|
350
|
|
|
'id' => $result['id'], |
|
351
|
|
|
'timestamp' => $result['timestamp'], |
|
352
|
|
|
'type' => $result['type'], |
|
353
|
|
|
]; |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
return $this->setCache($cacheKey, $actions); |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
/** |
|
360
|
|
|
* Get data for all blocks set on the given user. |
|
361
|
|
|
* @param Project $project |
|
362
|
|
|
* @param User $user |
|
363
|
|
|
* @return array |
|
364
|
|
|
*/ |
|
365
|
|
|
public function getBlocksReceived(Project $project, User $user): array |
|
366
|
|
|
{ |
|
367
|
|
|
$loggingTable = $this->getTableName($project->getDatabaseName(), 'logging', 'logindex'); |
|
368
|
|
|
$sql = "SELECT log_action, log_timestamp, log_params FROM $loggingTable |
|
369
|
|
|
WHERE log_type = 'block' |
|
370
|
|
|
AND log_action IN ('block', 'reblock', 'unblock') |
|
371
|
|
|
AND log_timestamp > 0 |
|
372
|
|
|
AND log_title = :username |
|
373
|
|
|
AND log_namespace = 2 |
|
374
|
|
|
ORDER BY log_timestamp ASC"; |
|
375
|
|
|
$username = str_replace(' ', '_', $user->getUsername()); |
|
376
|
|
|
|
|
377
|
|
|
return $this->executeProjectsQuery($project, $sql, [ |
|
378
|
|
|
'username' => $username, |
|
379
|
|
|
])->fetchAllAssociative(); |
|
380
|
|
|
} |
|
381
|
|
|
|
|
382
|
|
|
/** |
|
383
|
|
|
* Get the number of times the user was thanked. |
|
384
|
|
|
* @param Project $project |
|
385
|
|
|
* @param User $user |
|
386
|
|
|
* @return int |
|
387
|
|
|
*/ |
|
388
|
|
|
public function getThanksReceived(Project $project, User $user): int |
|
389
|
|
|
{ |
|
390
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_thanksreceived'); |
|
391
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
392
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
393
|
|
|
} |
|
394
|
|
|
|
|
395
|
|
|
$loggingTable = $project->getTableName('logging', 'logindex'); |
|
396
|
|
|
$sql = "SELECT COUNT(log_id) |
|
397
|
|
|
FROM $loggingTable |
|
398
|
|
|
WHERE log_type = 'thanks' |
|
399
|
|
|
AND log_title = :username |
|
400
|
|
|
AND log_namespace = 2"; |
|
401
|
|
|
$username = str_replace(' ', '_', $user->getUsername()); |
|
402
|
|
|
|
|
403
|
|
|
return $this->setCache($cacheKey, (int)$this->executeProjectsQuery($project, $sql, [ |
|
|
|
|
|
|
404
|
|
|
'username' => $username, |
|
405
|
|
|
])->fetchColumn()); |
|
406
|
|
|
} |
|
407
|
|
|
|
|
408
|
|
|
/** |
|
409
|
|
|
* Get the given user's total edit counts per namespace on the given project. |
|
410
|
|
|
* @param Project $project The project. |
|
411
|
|
|
* @param User $user The user. |
|
412
|
|
|
* @return array Array keys are namespace IDs, values are the edit counts. |
|
413
|
|
|
*/ |
|
414
|
|
|
public function getNamespaceTotals(Project $project, User $user): array |
|
415
|
|
|
{ |
|
416
|
|
|
// Cache? |
|
417
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_namespacetotals'); |
|
418
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
419
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
420
|
|
|
} |
|
421
|
|
|
|
|
422
|
|
|
// Query. |
|
423
|
|
|
$revisionTable = $project->getTableName('revision'); |
|
424
|
|
|
$pageTable = $project->getTableName('page'); |
|
425
|
|
|
$ipcJoin = ''; |
|
426
|
|
|
$whereClause = 'r.rev_actor = :actorId'; |
|
427
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
428
|
|
|
|
|
429
|
|
|
if ($user->isIpRange()) { |
|
430
|
|
|
$ipcTable = $project->getTableName('ip_changes'); |
|
431
|
|
|
$ipcJoin = "JOIN $ipcTable ON ipc_rev_id = rev_id"; |
|
432
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
433
|
|
|
$whereClause = 'ipc_hex BETWEEN :startIp AND :endIp'; |
|
434
|
|
|
} |
|
435
|
|
|
|
|
436
|
|
|
$sql = "SELECT page_namespace AS `namespace`, COUNT(rev_id) AS `total` |
|
437
|
|
|
FROM $pageTable p JOIN $revisionTable r ON (r.rev_page = p.page_id) |
|
438
|
|
|
$ipcJoin |
|
439
|
|
|
WHERE $whereClause |
|
440
|
|
|
GROUP BY `namespace`"; |
|
441
|
|
|
|
|
442
|
|
|
$results = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); |
|
|
|
|
|
|
443
|
|
|
|
|
444
|
|
|
$namespaceTotals = array_combine(array_map(function ($e) { |
|
445
|
|
|
return $e['namespace']; |
|
446
|
|
|
}, $results), array_map(function ($e) { |
|
447
|
|
|
return (int)$e['total']; |
|
448
|
|
|
}, $results)); |
|
449
|
|
|
|
|
450
|
|
|
// Cache and return. |
|
451
|
|
|
return $this->setCache($cacheKey, $namespaceTotals); |
|
452
|
|
|
} |
|
453
|
|
|
|
|
454
|
|
|
/** |
|
455
|
|
|
* Get data for a bar chart of monthly edit totals per namespace. |
|
456
|
|
|
* @param Project $project The project. |
|
457
|
|
|
* @param User $user The user. |
|
458
|
|
|
* @return string[] [ |
|
459
|
|
|
* [ |
|
460
|
|
|
* 'year' => <year>, |
|
461
|
|
|
* 'month' => <month>, |
|
462
|
|
|
* 'namespace' => <namespace>, |
|
463
|
|
|
* 'count' => <count>, |
|
464
|
|
|
* ], |
|
465
|
|
|
* ... |
|
466
|
|
|
* ] |
|
467
|
|
|
*/ |
|
468
|
|
|
public function getMonthCounts(Project $project, User $user): array |
|
469
|
|
|
{ |
|
470
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_monthcounts'); |
|
471
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
472
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
473
|
|
|
} |
|
474
|
|
|
|
|
475
|
|
|
$revisionTable = $project->getTableName('revision'); |
|
476
|
|
|
$pageTable = $project->getTableName('page'); |
|
477
|
|
|
$ipcJoin = ''; |
|
478
|
|
|
$whereClause = 'rev_actor = :actorId'; |
|
479
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
480
|
|
|
|
|
481
|
|
|
if ($user->isIpRange()) { |
|
482
|
|
|
$ipcTable = $project->getTableName('ip_changes'); |
|
483
|
|
|
$ipcJoin = "JOIN $ipcTable ON ipc_rev_id = rev_id"; |
|
484
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
485
|
|
|
$whereClause = 'ipc_hex BETWEEN :startIp AND :endIp'; |
|
486
|
|
|
} |
|
487
|
|
|
|
|
488
|
|
|
$sql = " |
|
489
|
|
|
SELECT YEAR(rev_timestamp) AS `year`, |
|
490
|
|
|
MONTH(rev_timestamp) AS `month`, |
|
491
|
|
|
page_namespace AS `namespace`, |
|
492
|
|
|
COUNT(rev_id) AS `count` |
|
493
|
|
|
FROM $revisionTable JOIN $pageTable ON (rev_page = page_id) |
|
494
|
|
|
$ipcJoin |
|
495
|
|
|
WHERE $whereClause |
|
496
|
|
|
GROUP BY YEAR(rev_timestamp), MONTH(rev_timestamp), `namespace`"; |
|
497
|
|
|
|
|
498
|
|
|
$totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); |
|
|
|
|
|
|
499
|
|
|
|
|
500
|
|
|
// Cache and return. |
|
501
|
|
|
return $this->setCache($cacheKey, $totals); |
|
502
|
|
|
} |
|
503
|
|
|
|
|
504
|
|
|
/** |
|
505
|
|
|
* Get data for the timecard chart, with totals grouped by day and to the nearest two-hours. |
|
506
|
|
|
* @param Project $project |
|
507
|
|
|
* @param User $user |
|
508
|
|
|
* @return string[][] |
|
509
|
|
|
*/ |
|
510
|
|
|
public function getTimeCard(Project $project, User $user): array |
|
511
|
|
|
{ |
|
512
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_timecard'); |
|
513
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
514
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
515
|
|
|
} |
|
516
|
|
|
|
|
517
|
|
|
$hourInterval = 1; |
|
518
|
|
|
$revisionTable = $project->getTableName('revision'); |
|
519
|
|
|
// Always JOIN on page, see T325492 |
|
520
|
|
|
$pageTable = $project->getTableName('page'); |
|
521
|
|
|
|
|
522
|
|
|
if ($user->isIpRange()) { |
|
523
|
|
|
$column = 'ipc_rev_timestamp'; |
|
524
|
|
|
$table = $project->getTableName('ip_changes'); |
|
525
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
526
|
|
|
$whereClause = 'ipc_hex BETWEEN :startIp AND :endIp'; |
|
527
|
|
|
$joinClause = "JOIN $revisionTable ON rev_id = ipc_rev_id |
|
528
|
|
|
JOIN $pageTable ON rev_page = page_id"; |
|
529
|
|
|
} else { |
|
530
|
|
|
$column = 'rev_timestamp'; |
|
531
|
|
|
$table = $revisionTable; |
|
532
|
|
|
$whereClause = 'rev_actor = :actorId'; |
|
533
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
534
|
|
|
$joinClause = "JOIN $pageTable ON rev_page = page_id"; |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
$xCalc = "ROUND(HOUR($column)/$hourInterval) * $hourInterval"; |
|
538
|
|
|
|
|
539
|
|
|
$sql = " |
|
540
|
|
|
SELECT DAYOFWEEK($column) AS `day_of_week`, |
|
541
|
|
|
$xCalc AS `hour`, |
|
542
|
|
|
COUNT($column) AS `value` |
|
543
|
|
|
FROM $table |
|
544
|
|
|
$joinClause |
|
545
|
|
|
WHERE $whereClause |
|
546
|
|
|
GROUP BY DAYOFWEEK($column), $xCalc"; |
|
547
|
|
|
|
|
548
|
|
|
$totals = $this->executeProjectsQuery($project, $sql, $params)->fetchAll(); |
|
|
|
|
|
|
549
|
|
|
|
|
550
|
|
|
// Cache and return. |
|
551
|
|
|
return $this->setCache($cacheKey, $totals); |
|
552
|
|
|
} |
|
553
|
|
|
|
|
554
|
|
|
/** |
|
555
|
|
|
* Get various data about edit sizes of the past 5,000 edits. |
|
556
|
|
|
* Will cache the result for 10 minutes. |
|
557
|
|
|
* @param Project $project The project. |
|
558
|
|
|
* @param User $user The user. |
|
559
|
|
|
* @return string[] With keys 'average_size', 'small_edits' and 'large_edits' |
|
560
|
|
|
*/ |
|
561
|
|
|
public function getEditSizeData(Project $project, User $user): array |
|
562
|
|
|
{ |
|
563
|
|
|
// Set up cache. |
|
564
|
|
|
$cacheKey = $this->getCacheKey(func_get_args(), 'ec_editsizes'); |
|
565
|
|
|
if ($this->cache->hasItem($cacheKey)) { |
|
566
|
|
|
return $this->cache->getItem($cacheKey)->get(); |
|
567
|
|
|
} |
|
568
|
|
|
|
|
569
|
|
|
// Prepare the queries and execute them. |
|
570
|
|
|
$revisionTable = $project->getTableName('revision'); |
|
571
|
|
|
$pageTable = $project->getTableName('page'); |
|
572
|
|
|
$ipcJoin = ''; |
|
573
|
|
|
$whereClause = 'revs.rev_actor = :actorId'; |
|
574
|
|
|
$params = ['actorId' => $user->getActorId($project)]; |
|
575
|
|
|
|
|
576
|
|
|
if ($user->isIpRange()) { |
|
577
|
|
|
$ipcTable = $project->getTableName('ip_changes'); |
|
578
|
|
|
$ipcJoin = "JOIN $ipcTable ON ipc_rev_id = revs.rev_id"; |
|
579
|
|
|
[$params['startIp'], $params['endIp']] = IPUtils::parseRange($user->getUsername()); |
|
580
|
|
|
$whereClause = 'ipc_hex BETWEEN :startIp AND :endIp'; |
|
581
|
|
|
} |
|
582
|
|
|
|
|
583
|
|
|
$sql = "SELECT AVG(sizes.size) AS average_size, |
|
584
|
|
|
COUNT(CASE WHEN sizes.size < 20 THEN 1 END) AS small_edits, |
|
585
|
|
|
COUNT(CASE WHEN sizes.size > 1000 THEN 1 END) AS large_edits |
|
586
|
|
|
FROM ( |
|
587
|
|
|
SELECT (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS size |
|
588
|
|
|
FROM $revisionTable AS revs |
|
589
|
|
|
JOIN $pageTable ON revs.rev_page = page_id |
|
590
|
|
|
$ipcJoin |
|
591
|
|
|
LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id) |
|
592
|
|
|
WHERE $whereClause |
|
593
|
|
|
ORDER BY revs.rev_timestamp DESC |
|
594
|
|
|
LIMIT 5000 |
|
595
|
|
|
) sizes"; |
|
596
|
|
|
$results = $this->executeProjectsQuery($project, $sql, $params)->fetchAssociative(); |
|
597
|
|
|
|
|
598
|
|
|
// Cache and return. |
|
599
|
|
|
return $this->setCache($cacheKey, $results); |
|
600
|
|
|
} |
|
601
|
|
|
|
|
602
|
|
|
/** |
|
603
|
|
|
* Get the number of edits this user made using semi-automated tools. |
|
604
|
|
|
* @param Project $project |
|
605
|
|
|
* @param User $user |
|
606
|
|
|
* @return int Result of query, see below. |
|
607
|
|
|
* @deprecated Inject AutoEditsRepository and call the countAutomatedEdits directly. |
|
608
|
|
|
*/ |
|
609
|
|
|
public function countAutomatedEdits(Project $project, User $user): int |
|
610
|
|
|
{ |
|
611
|
|
|
return $this->autoEditsRepo->countAutomatedEdits($project, $user); |
|
612
|
|
|
} |
|
613
|
|
|
} |
|
614
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.