Completed
Push — master ( 65ce64...a47571 )
by Sam
02:52
created

EditCounter::globalEdits()   B

Complexity

Conditions 5
Paths 9

Size

Total Lines 36
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 36
rs 8.439
cc 5
eloc 22
nc 9
nop 1
1
<?php
2
/**
3
 * This file contains only the EditCounter class.
4
 */
5
6
namespace Xtools;
7
8
use \DateTime;
9
10
/**
11
 * An EditCounter provides statistics about a user's edits on a project.
12
 */
13
class EditCounter extends Model
14
{
15
    
16
    /** @var Project The project. */
17
    protected $project;
18
    
19
    /** @var User The user. */
20
    protected $user;
21
22
    /** @var int[] The total revision counts. */
23
    protected $revisionCounts;
24
25
    /** @var string[] The start and end dates of revisions. */
26
    protected $revisionDates;
27
28
    /** @var int[] The total page counts. */
29
    protected $pageCounts;
30
    
31
    /** @var int[] The lot totals. */
32
    protected $logCounts;
33
34
    /** @var int[] Keys are project DB names. */
35
    protected $globalEditCounts;
36
37
    /** @var array Block data, with keys 'set' and 'received'. */
38
    protected $blocks;
39
40
    /**
41
     * EditCounter constructor.
42
     * @param Project $project The base project to count edits
43
     * @param User $user
44
     */
45
    public function __construct(Project $project, User $user)
46
    {
47
        $this->project = $project;
48
        $this->user = $user;
49
    }
50
51
    /**
52
     * Get revision count data.
53
     * @return int[]
54
     */
55
    protected function getRevisionCounts()
56
    {
57
        if (! is_array($this->revisionCounts)) {
58
            $this->revisionCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisionCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
59
                ->getRevisionCounts($this->project, $this->user);
60
        }
61
        return $this->revisionCounts;
62
    }
63
64
    /**
65
     * Get revision dates.
66
     * @return int[]
67
     */
68
    protected function getRevisionDates()
69
    {
70
        if (! is_array($this->revisionDates)) {
71
            $this->revisionDates = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisionDates() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
72
                ->getRevisionDates($this->project, $this->user);
73
        }
74
        return $this->revisionDates;
75
    }
76
77
    /**
78
     * Get page count data.
79
     * @return int[]
80
     */
81
    protected function getPageCounts()
82
    {
83
        if (! is_array($this->pageCounts)) {
84
            $this->pageCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getPageCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
85
                ->getPageCounts($this->project, $this->user);
86
        }
87
        return $this->pageCounts;
88
    }
89
90
    /**
91
     * Get revision dates.
92
     * @return int[]
93
     */
94
    protected function getLogCounts()
95
    {
96
        if (! is_array($this->logCounts)) {
97
            $this->logCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getLogCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
98
                ->getLogCounts($this->project, $this->user);
99
        }
100
        return $this->logCounts;
101
    }
102
103
    /**
104
     * Get block data.
105
     * @param string $type Either 'set' or 'received'.
106
     * @return array
107
     */
108
    protected function getBlocks($type)
109
    {
110
        if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) {
111
            return $this->blocks[$type];
112
        }
113
        $method = "getBlocks".ucfirst($type);
114
        $blocks = $this->getRepository()->$method($this->project, $this->user);
115
        $this->blocks[$type] = $blocks;
116
        return $this->blocks[$type];
117
    }
118
119
    /**
120
     * Get the total number of currently-live revisions.
121
     * @return int
122
     */
123
    public function countLiveRevisions()
124
    {
125
        $revCounts = $this->getRevisionCounts();
126
        return isset($revCounts['live']) ? $revCounts['live'] : 0;
127
    }
128
129
    /**
130
     * Get the total number of the user's revisions that have been deleted.
131
     * @return int
132
     */
133
    public function countDeletedRevisions()
134
    {
135
        $revCounts = $this->getRevisionCounts();
136
        return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0;
137
    }
138
139
    /**
140
     * Get the total edit count (live + deleted).
141
     * @return int
142
     */
143
    public function countAllRevisions()
144
    {
145
        return $this->countLiveRevisions() + $this->countDeletedRevisions();
146
    }
147
148
    /**
149
     * Get the total number of revisions with comments.
150
     * @return int
151
     */
152
    public function countRevisionsWithComments()
153
    {
154
        $revCounts = $this->getRevisionCounts();
155
        return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0;
156
    }
157
158
    /**
159
     * Get the total number of revisions without comments.
160
     * @return int
161
     */
162
    public function countRevisionsWithoutComments()
163
    {
164
        return $this->countAllRevisions() - $this->countRevisionsWithComments();
165
    }
166
167
    /**
168
     * Get the total number of revisions marked as 'minor' by the user.
169
     * @return int
170
     */
171
    public function countMinorRevisions()
172
    {
173
        $revCounts = $this->getRevisionCounts();
174
        return isset($revCounts['minor']) ? $revCounts['minor'] : 0;
175
    }
176
177
    /**
178
     * Get the total number of revisions under 20 bytes.
179
     */
180
    public function countSmallRevisions()
181
    {
182
        $revCounts = $this->getRevisionCounts();
183
        return isset($revCounts['small']) ? $revCounts['small'] : 0;
184
    }
185
186
    /**
187
     * Get the total number of revisions over 1000 bytes.
188
     */
189
    public function countLargeRevisions()
190
    {
191
        $revCounts = $this->getRevisionCounts();
192
        return isset($revCounts['large']) ? $revCounts['large'] : 0;
193
    }
194
195
    /**
196
     * Get the average revision size for the user.
197
     * @return float Size in bytes.
198
     */
199
    public function averageRevisionSize()
200
    {
201
        $revisionCounts = $this->getRevisionCounts();
202
        return round($revisionCounts['average_size'], 3);
203
    }
204
205
    /**
206
     * Get the total number of non-deleted pages edited by the user.
207
     * @return int
208
     */
209
    public function countLivePagesEdited()
210
    {
211
        $pageCounts = $this->getPageCounts();
212
        return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0;
213
    }
214
215
    /**
216
     * Get the total number of deleted pages ever edited by the user.
217
     * @return int
218
     */
219
    public function countDeletedPagesEdited()
220
    {
221
        $pageCounts = $this->getPageCounts();
222
        return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0;
223
    }
224
225
    /**
226
     * Get the total number of pages ever edited by this user (both live and deleted).
227
     * @return int
228
     */
229
    public function countAllPagesEdited()
230
    {
231
        return $this->countLivePagesEdited() + $this->countDeletedPagesEdited();
232
    }
233
234
    /**
235
     * Get the total number of pages (both still live and those that have been deleted) created
236
     * by the user.
237
     * @return int
238
     */
239
    public function countPagesCreated()
240
    {
241
        return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted();
242
    }
243
244
    /**
245
     * Get the total number of pages created by the user, that have not been deleted.
246
     * @return int
247
     */
248
    public function countCreatedPagesLive()
249
    {
250
        $pageCounts = $this->getPageCounts();
251
        return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0;
252
    }
253
254
    /**
255
     * Get the total number of pages created by the user, that have since been deleted.
256
     * @return int
257
     */
258
    public function countPagesCreatedDeleted()
259
    {
260
        $pageCounts = $this->getPageCounts();
261
        return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0;
262
    }
263
264
    /**
265
     * Get the total number of pages that have been deleted by the user.
266
     * @return int
267
     */
268
    public function countPagesDeleted()
269
    {
270
        $logCounts = $this->getLogCounts();
271
        return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0;
272
    }
273
274
    /**
275
     * Get the total number of pages moved by the user.
276
     * @return int
277
     */
278
    public function countPagesMoved()
279
    {
280
        $logCounts = $this->getLogCounts();
281
        return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0;
282
    }
283
284
    /**
285
     * Get the total number of times the user has blocked or re-blocked a user.
286
     * @return int
287
     */
288
    public function countBlocksSet()
289
    {
290
        $logCounts = $this->getLogCounts();
291
        $block = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0;
292
        $reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0;
293
        return $block + $reBlock;
294
    }
295
296
    /**
297
     * Get the total number of blocks that have been lifted (i.e. unblocks) by this user.
298
     * @return int
299
     */
300
    public function countBlocksLifted()
301
    {
302
        $logCounts = $this->getLogCounts();
303
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
304
    }
305
306
    /**
307
     * Get the total number of times the user has been blocked.
308
     * @return int
309
     */
310
    public function countBlocksReceived()
311
    {
312
        $blocks = $this->getBlocks('received');
313
        return count($blocks);
314
    }
315
316
    /**
317
     * Get the total number of users blocked by this user.
318
     * @return int
319
     */
320
    public function countUsersBlocked()
321
    {
322
        $blocks = $this->getBlocks('set');
323
        $usersBlocked = [];
324
        foreach ($blocks as $block) {
325
            $usersBlocked[$block['ipb_user']] = true;
326
        }
327
        return count($usersBlocked);
328
    }
329
330
    /**
331
     * Get the total number of users that this user has unblocked.
332
     * @return int
333
     */
334
    public function countUsersUnblocked()
335
    {
336
        $logCounts = $this->getLogCounts();
337
        return isset($logCounts['users-unblocked']) ? (int)$logCounts['users-unblocked'] : 0;
338
    }
339
340
    /**
341
     * Get the total number of pages protected by the user.
342
     * @return int
343
     */
344
    public function countPagesProtected()
345
    {
346
        $logCounts = $this->getLogCounts();
347
        return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0;
348
    }
349
    
350
    /**
351
     * Get the total number of pages unprotected by the user.
352
     * @return int
353
     */
354
    public function countPagesUnprotected()
355
    {
356
        $logCounts = $this->getLogCounts();
357
        return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0;
358
    }
359
360
    /**
361
     * Get the total number of edits deleted by the user.
362
     * @return int
363
     */
364
    public function countEditsDeleted()
365
    {
366
        $logCounts = $this->getLogCounts();
367
        return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0;
368
    }
369
370
    /**
371
     * Get the total number of pages restored by the user.
372
     * @return int
373
     */
374
    public function countPagesRestored()
375
    {
376
        $logCounts = $this->getLogCounts();
377
        return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0;
378
    }
379
380
    /**
381
     * Get the total number of pages imported by the user (through any import mechanism:
382
     * interwiki, or XML upload).
383
     * @return int
384
     */
385
    public function countPagesImported()
386
    {
387
        $logCounts = $this->getLogCounts();
388
        $import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0;
389
        $interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0;
390
        $upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0;
391
        return $import + $interwiki + $upload;
392
    }
393
394
    /**
395
     * Get the average number of edits per page (including deleted revisions and pages).
396
     * @return float
397
     */
398
    public function averageRevisionsPerPage()
399
    {
400
        if ($this->countAllPagesEdited() == 0) {
401
            return 0;
402
        }
403
        return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3);
404
    }
405
406
    /**
407
     * Average number of edits made per day.
408
     * @return float
409
     */
410
    public function averageRevisionsPerDay()
411
    {
412
        if ($this->getDays() == 0) {
413
            return 0;
414
        }
415
        return round($this->countAllRevisions() / $this->getDays(), 3);
416
    }
417
418
    /**
419
     * Get the total number of edits made by the user with semi-automating tools.
420
     */
421
    public function countAutomatedRevisions()
422
    {
423
        $autoSummary = $this->automatedRevisionsSummary();
424
        $count = 0;
425
        foreach ($autoSummary as $summary) {
426
            $count += $summary;
427
        }
428
        return $count;
429
    }
430
431
    /**
432
     * Get a summary of the numbers of edits made by the user with semi-automating tools.
433
     */
434
    public function automatedRevisionsSummary()
435
    {
436
        return $this->getRepository()->countAutomatedRevisions($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method countAutomatedRevisions() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
437
    }
438
439
    /**
440
     * Get the count of (non-deleted) edits made in the given timeframe to now.
441
     * @param string $time One of 'day', 'week', 'month', or 'year'.
442
     * @return int The total number of live edits.
443
     */
444
    public function countRevisionsInLast($time)
445
    {
446
        $revCounts = $this->getRevisionCounts();
447
        return isset($revCounts[$time]) ? $revCounts[$time] : 0;
448
    }
449
450
    /**
451
     * Get the date and time of the user's first edit.
452
     * @return DateTime|bool The time of the first revision, or false.
453
     */
454
    public function datetimeFirstRevision()
455
    {
456
        $revDates = $this->getRevisionDates();
457
        return isset($revDates['first']) ? new DateTime($revDates['first']) : false;
458
    }
459
460
    /**
461
     * Get the date and time of the user's first edit.
462
     * @return DateTime|bool The time of the last revision, or false.
463
     */
464
    public function datetimeLastRevision()
465
    {
466
        $revDates = $this->getRevisionDates();
467
        return isset($revDates['last']) ? new DateTime($revDates['last']) : false;
468
    }
469
470
    /**
471
     * Get the number of days between the first and last edits.
472
     * If there's only one edit, this is counted as one day.
473
     * @return int
474
     */
475
    public function getDays()
476
    {
477
        $first = $this->datetimeFirstRevision();
478
        $last = $this->datetimeLastRevision();
479
        if ($first === false || $last === false) {
480
            return 0;
481
        }
482
        $days = $last->diff($first)->days;
483
        return $days > 0 ? $days : 1;
484
    }
485
486
    /**
487
     * Get the total number of files uploaded (including those now deleted).
488
     * @return int
489
     */
490
    public function countFilesUploaded()
491
    {
492
        $logCounts = $this->getLogCounts();
493
        return $logCounts['upload-upload'] ?: 0;
494
    }
495
496
    /**
497
     * Get the total number of files uploaded to Commons (including those now deleted).
498
     * This is only applicable for WMF labs installations.
499
     * @return int
500
     */
501
    public function countFilesUploadedCommons()
502
    {
503
        $logCounts = $this->getLogCounts();
504
        return $logCounts['files_uploaded_commons'] ?: 0;
505
    }
506
507
    /**
508
     * Get the total number of revisions the user has sent thanks for.
509
     * @return int
510
     */
511
    public function thanks()
512
    {
513
        $logCounts = $this->getLogCounts();
514
        return $logCounts['thanks-thank'] ?: 0;
515
    }
516
517
    /**
518
     * Get the total number of approvals
519
     * @return int
520
     */
521
    public function approvals()
522
    {
523
        $logCounts = $this->getLogCounts();
524
        $total = $logCounts['review-approve'] +
525
        (!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) +
526
        (!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) +
527
        (!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0);
528
        return $total;
529
    }
530
531
    /**
532
     * Get the total number of patrols performed by the user.
533
     * @return int
534
     */
535
    public function patrols()
536
    {
537
        $logCounts = $this->getLogCounts();
538
        return $logCounts['patrol-patrol'] ?: 0;
539
    }
540
541
    /**
542
     * Get the given user's total edit counts per namespace.
543
     * @return integer[] Array keys are namespace IDs, values are the edit counts.
544
     */
545
    public function namespaceTotals()
546
    {
547
        $counts = $this->getRepository()->getNamespaceTotals($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getNamespaceTotals() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
548
        arsort($counts);
549
        return $counts;
550
    }
551
552
    /**
553
     * Get a summary of the times of day and the days of the week that the user has edited.
554
     * @return string[]
555
     */
556
    public function timeCard()
557
    {
558
        return $this->getRepository()->getTimeCard($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getTimeCard() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
559
    }
560
561
    /**
562
     * Get the total numbers of edits per year.
563
     * @return int[]
564
     */
565
    public function yearCounts()
566
    {
567
        $totals = $this->getRepository()->getYearCounts($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getYearCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
568
        $out = [
569
            'years' => [],
570
            'namespaces' => [],
571
            'totals' => [],
572
        ];
573
        foreach ($totals as $total) {
574
            $out['years'][$total['year']] = $total['year'];
575
            $out['namespaces'][$total['page_namespace']] = $total['page_namespace'];
576
            if (!isset($out['totals'][$total['page_namespace']])) {
577
                $out['totals'][$total['page_namespace']] = [];
578
            }
579
            $out['totals'][$total['page_namespace']][$total['year']] = $total['count'];
580
        }
581
582
        return $out;
583
    }
584
585
    /**
586
     * Get the total numbers of edits per month.
587
     * @return mixed[] With keys 'years', 'namespaces' and 'totals'.
588
     */
589
    public function monthCounts()
590
    {
591
        $totals = $this->getRepository()->getMonthCounts($this->project, $this->user);
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getMonthCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
592
        $out = [
593
            'years' => [],
594
            'namespaces' => [],
595
            'totals' => [],
596
        ];
597
        $out['max_year'] = 0;
598
        $out['min_year'] = date('Y');
599
        foreach ($totals as $total) {
600
            // Collect all applicable years and namespaces.
601
            $out['max_year'] = max($out['max_year'], $total['year']);
602
            $out['min_year'] = min($out['min_year'], $total['year']);
603
            // Collate the counts by namespace, and then year and month.
604
            $ns = $total['page_namespace'];
605
            if (!isset($out['totals'][$ns])) {
606
                $out['totals'][$ns] = [];
607
            }
608
            $out['totals'][$ns][$total['year'] . $total['month']] = $total['count'];
609
        }
610
        // Fill in the blanks (where no edits were made in a given month for a namespace).
611
        for ($y = $out['min_year']; $y <= $out['max_year']; $y++) {
612
            for ($m = 1; $m <= 12; $m++) {
613
                foreach ($out['totals'] as $nsId => &$total) {
614
                    if (!isset($total[$y . $m])) {
615
                        $total[$y . $m] = 0;
616
                    }
617
                }
618
            }
619
        }
620
        return $out;
621
    }
622
623
    /**
624
     * Get the total edit counts for the top n projects of this user.
625
     * @param int $numProjects
626
     * @return mixed[] Each element has 'total' and 'project' keys.
627
     */
628
    public function globalEditCountsTopN($numProjects = 10)
629
    {
630
        // Get counts.
631
        $editCounts = $this->globalEditCounts(true);
632
        // Truncate, and return.
1 ignored issue
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
633
        return array_slice($editCounts, 0, $numProjects);
634
    }
635
636
    /**
637
     * Get the total number of edits excluding the top n.
638
     * @param int $numProjects
639
     * @return int
640
     */
641
    public function globalEditCountWithoutTopN($numProjects = 10)
642
    {
643
        $editCounts = $this->globalEditCounts(true);
644
        $bottomM = array_slice($editCounts, $numProjects);
645
        $total = 0;
646
        foreach ($bottomM as $editCount) {
647
            $total += $editCount['total'];
648
        }
649
        return $total;
650
    }
651
652
    /**
653
     * Get the grand total of all edits on all projects.
654
     * @return int
655
     */
656
    public function globalEditCount()
657
    {
658
        $total = 0;
659
        foreach ($this->globalEditCounts() as $editCount) {
660
            $total += $editCount['total'];
661
        }
662
        return $total;
663
    }
664
665
    /**
666
     * Get the total revision counts for all projects for this user.
667
     * @param bool $sorted Whether to sort the list by total, or not.
668
     * @return mixed[] Each element has 'total' and 'project' keys.
669
     */
670
    public function globalEditCounts($sorted = false)
671
    {
672
        if (!$this->globalEditCounts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->globalEditCounts of type integer[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
673
            $this->globalEditCounts = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method globalEditCounts() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
674
                ->globalEditCounts($this->user, $this->project);
675
            if ($sorted) {
676
                // Sort.
677
                uasort($this->globalEditCounts, function ($a, $b) {
678
                    return $b['total'] - $a['total'];
679
                });
680
            }
681
        }
682
        return $this->globalEditCounts;
683
    }
684
685
    /**
686
     * Get the most recent n revisions across all projects.
687
     * @param int $max The maximum number of revisions to return.
688
     * @return Edit[]
689
     */
690
    public function globalEdits($max)
691
    {
692
        // Only look for revisions newer than this.
693
        $oldest = null;
0 ignored issues
show
Unused Code introduced by
$oldest is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
694
        // Collect all projects with any edits.
695
        $projects = [];
696
        foreach ($this->globalEditCounts() as $editCount) {
697
            // Don't query revisions if there aren't any.
698
            if ($editCount['total'] == 0) {
699
                continue;
700
            }
701
            $projects[$editCount['project']->getDatabaseName()] = $editCount['project'];
702
        }
703
704
        // Get all revisions for those projects.
705
        $globalRevisionsData = $this->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getRevisions() does only exist in the following sub-classes of Xtools\Repository: Xtools\EditCounterRepository, Xtools\PagesRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
706
            ->getRevisions($projects, $this->user, $max);
707
        $globalEdits = [];
708
        foreach ($globalRevisionsData as $revision) {
709
            /** @var Project $project */
710
            $project = $projects[$revision['project_name']];
711
            $nsName = '';
712
            if ($revision['page_namespace']) {
713
                $nsName = $project->getNamespaces()[$revision['page_namespace']];
714
            }
715
            $page = $project->getRepository()
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getPage() does only exist in the following sub-classes of Xtools\Repository: Xtools\ProjectRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
716
                ->getPage($project, $nsName . ':' . $revision['page_title']);
717
            $edit = new Edit($page, $revision);
718
            $globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit;
719
        }
720
721
        // Sort and prune, before adding more.
722
        krsort($globalEdits);
723
        $globalEdits = array_slice($globalEdits, 0, $max);
724
        return $globalEdits;
725
    }
726
}
727