Passed
Push — master ( 8ec441...50f1c7 )
by MusikAnimal
06:08
created

AutoEditsRepository::getNonAutomatedEdits()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 55
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 26
nc 5
nop 6
dl 0
loc 55
rs 9.078
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file contains only the AutoEditsRepository class.
4
 */
5
6
namespace Xtools;
7
8
use AppBundle\Helper\AutomatedEditsHelper;
9
use DateInterval;
10
use Symfony\Component\Config\Definition\Exception\Exception;
11
12
/**
13
 * AutoEditsRepository is responsible for retrieving data from the database
14
 * about the automated edits made by a user.
15
 * @codeCoverageIgnore
16
 */
17
class AutoEditsRepository extends UserRepository
18
{
19
    /** @var AutomatedEditsHelper Used for fetching the tool list and filtering it. */
20
    private $aeh;
21
22
    /**
23
     * Method to give the repository access to the AutomatedEditsHelper.
24
     */
25
    public function getHelper()
26
    {
27
        if (!isset($this->aeh)) {
28
            $this->aeh = $this->container->get('app.automated_edits_helper');
29
        }
30
        return $this->aeh;
31
    }
32
33
    /**
34
     * Get the number of edits this user made using semi-automated tools.
35
     * @param Project $project
36
     * @param User $user
37
     * @param string|int $namespace Namespace ID or 'all'
38
     * @param string $start Start date in a format accepted by strtotime()
39
     * @param string $end End date in a format accepted by strtotime()
40
     * @return int Result of query, see below.
41
     */
42
    public function countAutomatedEdits(Project $project, User $user, $namespace = 'all', $start = '', $end = '')
43
    {
44
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoeditcount');
45
        if ($this->cache->hasItem($cacheKey)) {
46
            return $this->cache->getItem($cacheKey)->get();
47
        }
48
        $this->stopwatch->start($cacheKey, 'XTools');
49
50
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
51
52
        // Get the combined regex and tags for the tools
53
        list($regex, $tags) = $this->getToolRegexAndTags($project);
54
55
        list($pageJoin, $condNamespace) = $this->getPageAndNamespaceSql($project, $namespace);
56
57
        $revisionTable = $project->getTableName('revision');
58
        $tagTable = $project->getTableName('change_tag');
59
        $tagJoin = '';
60
61
        // Build SQL for detecting autoedits via regex and/or tags
62
        $condTools = [];
63
        if ($regex != '') {
64
            $condTools[] = "rev_comment REGEXP $regex";
65
        }
66
        if ($tags != '') {
67
            $tagJoin = $tags != '' ? "LEFT OUTER JOIN $tagTable ON ct_rev_id = rev_id" : '';
68
            $condTools[] = "ct_tag IN ($tags)";
69
        }
70
        $condTool = 'AND (' . implode(' OR ', $condTools) . ')';
71
72
        $sql = "SELECT COUNT(DISTINCT(rev_id))
73
                FROM $revisionTable
74
                $pageJoin
75
                $tagJoin
76
                WHERE rev_user_text = :username
77
                $condTool
78
                $condNamespace
79
                $condBegin
80
                $condEnd";
81
82
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
83
        $result = (int) $resultQuery->fetchColumn();
84
85
        // Cache and return.
86
        $this->stopwatch->stop($cacheKey);
87
        return $this->setCache($cacheKey, $result);
88
    }
89
90
    /**
91
     * Get non-automated contributions for the given user.
92
     * @param Project $project
93
     * @param User $user
94
     * @param string|int $namespace Namespace ID or 'all'
95
     * @param string $start Start date in a format accepted by strtotime()
96
     * @param string $end End date in a format accepted by strtotime()
97
     * @param int $offset Used for pagination, offset results by N edits
98
     * @return string[] Result of query, with columns 'page_title',
99
     *   'page_namespace', 'rev_id', 'timestamp', 'minor',
100
     *   'length', 'length_change', 'comment'
101
     */
102
    public function getNonAutomatedEdits(
103
        Project $project,
104
        User $user,
105
        $namespace = 'all',
106
        $start = '',
107
        $end = '',
108
        $offset = 0
109
    ) {
110
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_nonautoedits');
111
        if ($this->cache->hasItem($cacheKey)) {
112
            return $this->cache->getItem($cacheKey)->get();
113
        }
114
        $this->stopwatch->start($cacheKey, 'XTools');
115
116
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end, 'revs.');
117
118
        // Get the combined regex and tags for the tools
119
        list($regex, $tags) = $this->getToolRegexAndTags($project);
120
121
        $pageTable = $project->getTableName('page');
122
        $revisionTable = $project->getTableName('revision');
123
        $tagTable = $project->getTableName('change_tag');
124
        $condNamespace = $namespace === 'all' ? '' : 'AND page_namespace = :namespace';
125
        $condTag = $tags != '' ? "AND NOT EXISTS (SELECT 1 FROM $tagTable
126
            WHERE ct_rev_id = revs.rev_id AND ct_tag IN ($tags))" : '';
127
        $sql = "SELECT
128
                    page_title,
129
                    page_namespace,
130
                    revs.rev_id AS rev_id,
131
                    revs.rev_timestamp AS timestamp,
132
                    revs.rev_minor_edit AS minor,
133
                    revs.rev_len AS length,
134
                    (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change,
135
                    revs.rev_comment AS comment
136
                FROM $pageTable
137
                JOIN $revisionTable AS revs ON (page_id = revs.rev_page)
138
                LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
139
                WHERE revs.rev_user_text = :username
140
                AND revs.rev_timestamp > 0
141
                AND revs.rev_comment NOT RLIKE $regex
142
                $condTag
143
                $condBegin
144
                $condEnd
145
                $condNamespace
146
                GROUP BY revs.rev_id
147
                ORDER BY revs.rev_timestamp DESC
148
                LIMIT 50
149
                OFFSET $offset";
150
151
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
152
        $result = $resultQuery->fetchAll();
153
154
        // Cache and return.
155
        $this->stopwatch->stop($cacheKey);
156
        return $this->setCache($cacheKey, $result);
157
    }
158
159
    /**
160
     * Get (semi-)automated contributions for the given user, and optionally for a given tool
161
     * @param Project $project
162
     * @param User $user
163
     * @param string|int $namespace Namespace ID or 'all'
164
     * @param string $start Start date in a format accepted by strtotime()
165
     * @param string $end End date in a format accepted by strtotime()
166
     * @param string|null $tool Only get edits made with this tool. Must match the keys in semi_automated.yml
167
     * @param int $offset Used for pagination, offset results by N edits
168
     * @return string[] Result of query, with columns 'page_title',
169
     *   'page_namespace', 'rev_id', 'timestamp', 'minor',
170
     *   'length', 'length_change', 'comment'
171
     */
172
    public function getAutomatedEdits(
173
        Project $project,
174
        User $user,
175
        $namespace = 'all',
176
        $start = '',
177
        $end = '',
178
        $tool = null,
179
        $offset = 0
180
    ) {
181
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_autoedits');
182
        if ($this->cache->hasItem($cacheKey)) {
183
            return $this->cache->getItem($cacheKey)->get();
184
        }
185
        $this->stopwatch->start($cacheKey, 'XTools');
186
187
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end, 'revs.');
188
189
        // Get the combined regex and tags for the tools
190
        list($regex, $tags) = $this->getToolRegexAndTags($project, $tool);
191
192
        $pageTable = $project->getTableName('page');
193
        $revisionTable = $project->getTableName('revision');
194
        $tagTable = $project->getTableName('change_tag');
195
        $condNamespace = $namespace === 'all' ? '' : 'AND page_namespace = :namespace';
196
        $tagJoin = $tags != '' ? "LEFT OUTER JOIN $tagTable ON (ct_rev_id = revs.rev_id)" : '';
197
        $condTag = $tags != '' ? "OR ct_tag IN ($tags)" : '';
198
        $sql = "SELECT
199
                    page_title,
200
                    page_namespace,
201
                    revs.rev_id AS rev_id,
202
                    revs.rev_timestamp AS timestamp,
203
                    revs.rev_minor_edit AS minor,
204
                    revs.rev_len AS length,
205
                    (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS length_change,
206
                    revs.rev_comment AS comment
207
                FROM $pageTable
208
                JOIN $revisionTable AS revs ON (page_id = revs.rev_page)
209
                LEFT JOIN $revisionTable AS parentrevs ON (revs.rev_parent_id = parentrevs.rev_id)
210
                $tagJoin
211
                WHERE revs.rev_user_text = :username
212
                AND revs.rev_timestamp > 0
213
                $condBegin
214
                $condEnd
215
                $condNamespace
216
                AND (
217
                    revs.rev_comment RLIKE $regex
218
                    $condTag
219
                )
220
                GROUP BY revs.rev_id
221
                ORDER BY revs.rev_timestamp DESC
222
                LIMIT 50
223
                OFFSET $offset";
224
225
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
226
        $result = $resultQuery->fetchAll();
227
228
        // Cache and return.
229
        $this->stopwatch->stop($cacheKey);
230
        return $this->setCache($cacheKey, $result);
231
    }
232
233
    /**
234
     * Get counts of known automated tools used by the given user.
235
     * @param Project $project
236
     * @param User $user
237
     * @param string|int $namespace Namespace ID or 'all'.
238
     * @param string $start Start date in a format accepted by strtotime()
239
     * @param string $end End date in a format accepted by strtotime()
240
     * @return string[] Each tool that they used along with the count and link:
241
     *                  [
242
     *                      'Twinkle' => [
243
     *                          'count' => 50,
244
     *                          'link' => 'Wikipedia:Twinkle',
245
     *                      ],
246
     *                  ]
247
     */
248
    public function getToolCounts(
249
        Project $project,
250
        User $user,
251
        $namespace = 'all',
252
        $start = '',
253
        $end = ''
254
    ) {
255
        $cacheKey = $this->getCacheKey(func_get_args(), 'user_autotoolcounts');
256
        if ($this->cache->hasItem($cacheKey)) {
257
            return $this->cache->getItem($cacheKey)->get();
258
        }
259
        $this->stopwatch->start($cacheKey, 'XTools');
260
261
        $sql = $this->getAutomatedCountsSql($project, $namespace, $start, $end);
262
        $resultQuery = $this->executeQuery($sql, $user, $namespace, $start, $end);
263
264
        $tools = $this->getHelper()->getTools($project);
265
266
        // handling results
267
        $results = [];
268
269
        while ($row = $resultQuery->fetch()) {
270
            // Only track tools that they've used at least once
271
            $tool = $row['toolname'];
272
            if ($row['count'] > 0) {
273
                $results[$tool] = [
274
                    'link' => $tools[$tool]['link'],
275
                    'label' => isset($tools[$tool]['label'])
276
                        ? $tools[$tool]['label']
277
                        : $tool,
278
                    'count' => $row['count'],
279
                ];
280
            }
281
        }
282
283
        // Sort the array by count
284
        uasort($results, function ($a, $b) {
285
            return $b['count'] - $a['count'];
286
        });
287
288
        // Cache and return.
289
        $this->stopwatch->stop($cacheKey);
290
        return $this->setCache($cacheKey, $results);
291
    }
292
293
    /**
294
     * Get SQL for getting counts of known automated tools used by the given user.
295
     * @see self::getAutomatedCounts()
296
     * @param Project $project
297
     * @param string|int $namespace Namespace ID or 'all'.
298
     * @param string $start Start date in a format accepted by strtotime()
299
     * @param string $end End date in a format accepted by strtotime()
300
     * @return string The SQL.
301
     */
302
    private function getAutomatedCountsSql(Project $project, $namespace, $start, $end)
303
    {
304
        list($condBegin, $condEnd) = $this->getRevTimestampConditions($start, $end);
305
306
        // Load the semi-automated edit types.
307
        $tools = $this->getHelper()->getTools($project);
308
309
        // Create a collection of queries that we're going to run.
310
        $queries = [];
311
312
        $revisionTable = $project->getTableName('revision');
313
        $tagTable = $project->getTableName('change_tag');
314
315
        list($pageJoin, $condNamespace) = $this->getPageAndNamespaceSql($project, $namespace);
316
317
        $conn = $this->getProjectsConnection();
318
319
        foreach ($tools as $toolname => $values) {
320
            list($condTool, $tagJoin) = $this->getInnerAutomatedCountsSql($tagTable, $values);
0 ignored issues
show
Bug introduced by
$values of type string is incompatible with the type string[] expected by parameter $values of Xtools\AutoEditsReposito...nerAutomatedCountsSql(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

320
            list($condTool, $tagJoin) = $this->getInnerAutomatedCountsSql($tagTable, /** @scrutinizer ignore-type */ $values);
Loading history...
321
322
            $toolname = $conn->quote($toolname, \PDO::PARAM_STR);
323
324
            // Developer error, no regex or tag provided for this tool.
325
            if ($condTool === '') {
326
                throw new Exception("No regex or tag found for the tool $toolname. " .
327
                    "Please verify this entry in semi_automated.yml");
328
            }
329
330
            $queries[] .= "
331
                SELECT $toolname AS toolname, COUNT(rev_id) AS count
332
                FROM $revisionTable
333
                $pageJoin
334
                $tagJoin
335
                WHERE rev_user_text = :username
336
                AND $condTool
337
                $condNamespace
338
                $condBegin
339
                $condEnd";
340
        }
341
342
        // Combine to one big query.
343
        return implode(' UNION ', $queries);
344
    }
345
346
    /**
347
     * Get some of the inner SQL for self::getAutomatedCountsSql().
348
     * @param  string $tagTable Name of the `change_tag` table.
349
     * @param  string[] $values Values as defined in semi_automated.yml
350
     * @return string[] [Equality clause, JOIN clause]
351
     */
352
    private function getInnerAutomatedCountsSql($tagTable, $values)
353
    {
354
        $conn = $this->getProjectsConnection();
355
        $tagJoin = '';
356
        $condTool = '';
357
358
        if (isset($values['regex'])) {
359
            $regex = $conn->quote($values['regex'], \PDO::PARAM_STR);
360
            $condTool = "rev_comment REGEXP $regex";
361
        }
362
        if (isset($values['tag'])) {
363
            $tagJoin = "LEFT OUTER JOIN $tagTable ON ct_rev_id = rev_id";
364
            $tag = $conn->quote($values['tag'], \PDO::PARAM_STR);
365
366
            // Append to regex clause if already present.
367
            // Tags are more reliable but may not be present for edits made with
368
            //   older versions of the tool, before it started adding tags.
369
            if ($condTool === '') {
370
                $condTool = "ct_tag = $tag";
371
            } else {
372
                $condTool = '(' . $condTool . " OR ct_tag = $tag)";
373
            }
374
        }
375
376
        return [$condTool, $tagJoin];
377
    }
378
379
    /**
380
     * Get the combined regex and tags for all semi-automated tools,
381
     * or the given tool, ready to be used in a query.
382
     * @param Project $project
383
     * @param string|null $tool
384
     * @return string[] In the format:
385
     *    ['combined|regex', 'combined,tags']
386
     */
387
    private function getToolRegexAndTags(Project $project, $tool = null)
388
    {
389
        $conn = $this->getProjectsConnection();
390
        $tools = $this->getHelper()->getTools($project);
391
        $regexes = [];
392
        $tags = [];
393
394
        if ($tool != '') {
395
            $tools = [$tools[$tool]];
396
        }
397
398
        foreach ($tools as $tool => $values) {
399
            if (isset($values['regex'])) {
400
                $regexes[] = $values['regex'];
401
            }
402
            if (isset($values['tag'])) {
403
                $tags[] = $conn->quote($values['tag'], \PDO::PARAM_STR);
404
            }
405
        }
406
407
        return [
408
            $conn->quote(implode('|', $regexes), \PDO::PARAM_STR),
409
            implode(',', $tags),
410
        ];
411
    }
412
}
413