Completed
Push — master ( d2bafa...7405dd )
by
unknown
02:55
created

EditCounter::countRightsModified()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
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[] Revision and page counts etc. */
23
    protected $pairData;
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
     * Duration of the longest block in days; -1 if indefinite,
42
     *   or false if could not be parsed from log params
43
     * @var int|bool
44
     */
45
    protected $longestBlockDays;
46
47
    /**
48
     * EditCounter constructor.
49
     * @param Project $project The base project to count edits
50
     * @param User $user
51
     */
52
    public function __construct(Project $project, User $user)
53
    {
54
        $this->project = $project;
55
        $this->user = $user;
56
    }
57
58
    /**
59
     * Get revision and page counts etc.
60
     * @return int[]
61
     */
62
    protected function getPairData()
63
    {
64
        if (! is_array($this->pairData)) {
65
            $this->pairData = $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 getPairData() 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...
66
                ->getPairData($this->project, $this->user);
67
        }
68
        return $this->pairData;
69
    }
70
71
    /**
72
     * Get revision dates.
73
     * @return int[]
74
     */
75
    protected function getLogCounts()
76
    {
77
        if (! is_array($this->logCounts)) {
78
            $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...
79
                ->getLogCounts($this->project, $this->user);
80
        }
81
        return $this->logCounts;
82
    }
83
84
    /**
85
     * Get block data.
86
     * @param string $type Either 'set' or 'received'.
87
     * @return array
88
     */
89
    protected function getBlocks($type)
90
    {
91
        if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) {
92
            return $this->blocks[$type];
93
        }
94
        $method = "getBlocks".ucfirst($type);
95
        $blocks = $this->getRepository()->$method($this->project, $this->user);
96
        $this->blocks[$type] = $blocks;
97
        return $this->blocks[$type];
98
    }
99
100
    /**
101
     * Get the total number of currently-live revisions.
102
     * @return int
103
     */
104
    public function countLiveRevisions()
105
    {
106
        $revCounts = $this->getPairData();
107
        return isset($revCounts['live']) ? $revCounts['live'] : 0;
108
    }
109
110
    /**
111
     * Get the total number of the user's revisions that have been deleted.
112
     * @return int
113
     */
114
    public function countDeletedRevisions()
115
    {
116
        $revCounts = $this->getPairData();
117
        return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0;
118
    }
119
120
    /**
121
     * Get the total edit count (live + deleted).
122
     * @return int
123
     */
124
    public function countAllRevisions()
125
    {
126
        return $this->countLiveRevisions() + $this->countDeletedRevisions();
127
    }
128
129
    /**
130
     * Get the total number of revisions with comments.
131
     * @return int
132
     */
133
    public function countRevisionsWithComments()
134
    {
135
        $revCounts = $this->getPairData();
136
        return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0;
137
    }
138
139
    /**
140
     * Get the total number of revisions without comments.
141
     * @return int
142
     */
143
    public function countRevisionsWithoutComments()
144
    {
145
        return $this->countAllRevisions() - $this->countRevisionsWithComments();
146
    }
147
148
    /**
149
     * Get the total number of revisions marked as 'minor' by the user.
150
     * @return int
151
     */
152
    public function countMinorRevisions()
153
    {
154
        $revCounts = $this->getPairData();
155
        return isset($revCounts['minor']) ? $revCounts['minor'] : 0;
156
    }
157
158
    /**
159
     * Get the total number of revisions under 20 bytes.
160
     */
161
    public function countSmallRevisions()
162
    {
163
        $revCounts = $this->getPairData();
164
        return isset($revCounts['small']) ? $revCounts['small'] : 0;
165
    }
166
167
    /**
168
     * Get the total number of revisions over 1000 bytes.
169
     */
170
    public function countLargeRevisions()
171
    {
172
        $revCounts = $this->getPairData();
173
        return isset($revCounts['large']) ? $revCounts['large'] : 0;
174
    }
175
176
    /**
177
     * Get the average revision size for the user.
178
     * @return float Size in bytes.
179
     */
180
    public function averageRevisionSize()
181
    {
182
        $revisionCounts = $this->getPairData();
183
        if (!isset($revisionCounts['average_size'])) {
184
            return 0;
185
        }
186
        return round($revisionCounts['average_size'], 3);
187
    }
188
189
    /**
190
     * Get the total number of non-deleted pages edited by the user.
191
     * @return int
192
     */
193
    public function countLivePagesEdited()
194
    {
195
        $pageCounts = $this->getPairData();
196
        return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0;
197
    }
198
199
    /**
200
     * Get the total number of deleted pages ever edited by the user.
201
     * @return int
202
     */
203
    public function countDeletedPagesEdited()
204
    {
205
        $pageCounts = $this->getPairData();
206
        return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0;
207
    }
208
209
    /**
210
     * Get the total number of pages ever edited by this user (both live and deleted).
211
     * @return int
212
     */
213
    public function countAllPagesEdited()
214
    {
215
        return $this->countLivePagesEdited() + $this->countDeletedPagesEdited();
216
    }
217
218
    /**
219
     * Get the total number of pages (both still live and those that have been deleted) created
220
     * by the user.
221
     * @return int
222
     */
223
    public function countPagesCreated()
224
    {
225
        return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted();
226
    }
227
228
    /**
229
     * Get the total number of pages created by the user, that have not been deleted.
230
     * @return int
231
     */
232
    public function countCreatedPagesLive()
233
    {
234
        $pageCounts = $this->getPairData();
235
        return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0;
236
    }
237
238
    /**
239
     * Get the total number of pages created by the user, that have since been deleted.
240
     * @return int
241
     */
242
    public function countPagesCreatedDeleted()
243
    {
244
        $pageCounts = $this->getPairData();
245
        return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0;
246
    }
247
248
    /**
249
     * Get the total number of pages that have been deleted by the user.
250
     * @return int
251
     */
252
    public function countPagesDeleted()
253
    {
254
        $logCounts = $this->getLogCounts();
255
        return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0;
256
    }
257
258
    /**
259
     * Get the total number of pages moved by the user.
260
     * @return int
261
     */
262
    public function countPagesMoved()
263
    {
264
        $logCounts = $this->getLogCounts();
265
        return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0;
266
    }
267
268
    /**
269
     * Get the total number of times the user has blocked a user.
270
     * @return int
271
     */
272
    public function countBlocksSet()
273
    {
274
        $logCounts = $this->getLogCounts();
275
        $reBlock = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0;
276
        return $reBlock;
277
    }
278
279
    /**
280
     * Get the total number of times the user has re-blocked a user.
281
     * @return int
282
     */
283
    public function countReblocksSet()
284
    {
285
        $logCounts = $this->getLogCounts();
286
        $reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0;
287
        return $reBlock;
288
    }
289
290
    /**
291
     * Get the total number of times the user has unblocked a user.
292
     * @return int
293
     */
294
    public function countUnblocksSet()
295
    {
296
        $logCounts = $this->getLogCounts();
297
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
298
    }
299
300
    /**
301
     * Get the total number of blocks that have been lifted (i.e. unblocks) by this user.
302
     * @return int
303
     */
304
    public function countBlocksLifted()
305
    {
306
        $logCounts = $this->getLogCounts();
307
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
308
    }
309
310
    /**
311
     * Get the total number of times the user has been blocked.
312
     * @return int
313
     */
314
    public function countBlocksReceived()
315
    {
316
        $blocks = $this->getBlocks('received');
317
        return count($blocks);
318
    }
319
320
    /**
321
     * Get the length of the longest block the user received.
322
     * @return int|bool Number of days or false if it could not be determined.
323
     *                  If the longest duration is indefinite, -1 is returned.
324
     */
325
    public function getLongestBlockDays()
326
    {
327
        if (isset($this->longestBlockDays)) {
328
            return $this->longestBlockDays;
329
        }
330
331
        $blocks = $this->getBlocks('received'); // FIXME: make sure this is only called once
332
        $this->longestBlockDays = false;
333
334
        foreach ($blocks as $block) {
335
            $timestamp = strtotime($block['log_timestamp']);
336
337
            // First check if the string is serialized, and if so parse it to get the block duration
338
            if (@unserialize($block['log_params']) !== false) {
339
                $parsedParams = unserialize($block['log_params']);
340
                $durationStr = $parsedParams['5::duration'];
341
            } else {
342
                // Old format, the duration in English + block options separated by new lines
343
                $durationStr = explode("\n", $block['log_params'])[0];
344
            }
345
346
            if (in_array($durationStr, ['indefinite', 'infinity', 'infinite'])) {
347
                return -1;
348
            }
349
350
            // Try block just in case there are older, unpredictable formats
351
            try {
352
                $expiry = strtotime($durationStr, $timestamp);
353
                $duration = ($expiry - $timestamp) / (60 * 60 * 24);
354
355
                if (!$duration || $duration > $this->longestBlockDays) {
356
                    $this->longestBlockDays = $duration;
357
                }
358
            } catch (Exception $error) {
0 ignored issues
show
Bug introduced by
The class Xtools\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
359
                // do nothing, leaving the longest block at false
360
            }
361
        }
362
363
        return $this->longestBlockDays;
364
    }
365
366
    /**
367
     * Get the total number of pages protected by the user.
368
     * @return int
369
     */
370
    public function countPagesProtected()
371
    {
372
        $logCounts = $this->getLogCounts();
373
        return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0;
374
    }
375
376
    /**
377
     * Get the total number of pages reprotected by the user.
378
     * @return int
379
     */
380
    public function countPagesReprotected()
381
    {
382
        $logCounts = $this->getLogCounts();
383
        return isset($logCounts['protect-modify']) ? (int)$logCounts['protect-modify'] : 0;
384
    }
385
386
    /**
387
     * Get the total number of pages unprotected by the user.
388
     * @return int
389
     */
390
    public function countPagesUnprotected()
391
    {
392
        $logCounts = $this->getLogCounts();
393
        return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0;
394
    }
395
396
    /**
397
     * Get the total number of edits deleted by the user.
398
     * @return int
399
     */
400
    public function countEditsDeleted()
401
    {
402
        $logCounts = $this->getLogCounts();
403
        return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0;
404
    }
405
406
    /**
407
     * Get the total number of pages restored by the user.
408
     * @return int
409
     */
410
    public function countPagesRestored()
411
    {
412
        $logCounts = $this->getLogCounts();
413
        return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0;
414
    }
415
416
    /**
417
     * Get the total number of times the user has modified the rights of a user.
418
     * @return int
419
     */
420
    public function countRightsModified()
421
    {
422
        $logCounts = $this->getLogCounts();
423
        return isset($logCounts['rights-rights']) ? (int)$logCounts['rights-rights'] : 0;
424
    }
425
426
    /**
427
     * Get the total number of pages imported by the user (through any import mechanism:
428
     * interwiki, or XML upload).
429
     * @return int
430
     */
431
    public function countPagesImported()
432
    {
433
        $logCounts = $this->getLogCounts();
434
        $import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0;
435
        $interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0;
436
        $upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0;
437
        return $import + $interwiki + $upload;
438
    }
439
440
    /**
441
     * Get the average number of edits per page (including deleted revisions and pages).
442
     * @return float
443
     */
444
    public function averageRevisionsPerPage()
445
    {
446
        if ($this->countAllPagesEdited() == 0) {
447
            return 0;
448
        }
449
        return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3);
450
    }
451
452
    /**
453
     * Average number of edits made per day.
454
     * @return float
455
     */
456
    public function averageRevisionsPerDay()
457
    {
458
        if ($this->getDays() == 0) {
459
            return 0;
460
        }
461
        return round($this->countAllRevisions() / $this->getDays(), 3);
462
    }
463
464
    /**
465
     * Get the total number of edits made by the user with semi-automating tools.
466
     */
467
    public function countAutomatedRevisions()
468
    {
469
        $autoSummary = $this->automatedRevisionsSummary();
470
        $count = 0;
471
        foreach ($autoSummary as $summary) {
472
            $count += $summary;
473
        }
474
        return $count;
475
    }
476
477
    /**
478
     * Get a summary of the numbers of edits made by the user with semi-automating tools.
479
     */
480
    public function automatedRevisionsSummary()
481
    {
482
        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...
483
    }
484
485
    /**
486
     * Get the count of (non-deleted) edits made in the given timeframe to now.
487
     * @param string $time One of 'day', 'week', 'month', or 'year'.
488
     * @return int The total number of live edits.
489
     */
490
    public function countRevisionsInLast($time)
491
    {
492
        $revCounts = $this->getPairData();
493
        return isset($revCounts[$time]) ? $revCounts[$time] : 0;
494
    }
495
496
    /**
497
     * Get the date and time of the user's first edit.
498
     * @return DateTime|bool The time of the first revision, or false.
499
     */
500
    public function datetimeFirstRevision()
501
    {
502
        $revDates = $this->getPairData();
503
        return isset($revDates['first']) ? new DateTime($revDates['first']) : false;
504
    }
505
506
    /**
507
     * Get the date and time of the user's first edit.
508
     * @return DateTime|bool The time of the last revision, or false.
509
     */
510
    public function datetimeLastRevision()
511
    {
512
        $revDates = $this->getPairData();
513
        return isset($revDates['last']) ? new DateTime($revDates['last']) : false;
514
    }
515
516
    /**
517
     * Get the number of days between the first and last edits.
518
     * If there's only one edit, this is counted as one day.
519
     * @return int
520
     */
521
    public function getDays()
522
    {
523
        $first = $this->datetimeFirstRevision();
524
        $last = $this->datetimeLastRevision();
525
        if ($first === false || $last === false) {
526
            return 0;
527
        }
528
        $days = $last->diff($first)->days;
529
        return $days > 0 ? $days : 1;
530
    }
531
532
    /**
533
     * Get the total number of files uploaded (including those now deleted).
534
     * @return int
535
     */
536
    public function countFilesUploaded()
537
    {
538
        $logCounts = $this->getLogCounts();
539
        return $logCounts['upload-upload'] ?: 0;
540
    }
541
542
    /**
543
     * Get the total number of files uploaded to Commons (including those now deleted).
544
     * This is only applicable for WMF labs installations.
545
     * @return int
546
     */
547
    public function countFilesUploadedCommons()
548
    {
549
        $logCounts = $this->getLogCounts();
550
        return $logCounts['files_uploaded_commons'] ?: 0;
551
    }
552
553
    /**
554
     * Get the total number of revisions the user has sent thanks for.
555
     * @return int
556
     */
557
    public function thanks()
558
    {
559
        $logCounts = $this->getLogCounts();
560
        return $logCounts['thanks-thank'] ?: 0;
561
    }
562
563
    /**
564
     * Get the total number of approvals
565
     * @return int
566
     */
567
    public function approvals()
568
    {
569
        $logCounts = $this->getLogCounts();
570
        $total = $logCounts['review-approve'] +
571
        (!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) +
572
        (!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) +
573
        (!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0);
574
        return $total;
575
    }
576
577
    /**
578
     * Get the total number of patrols performed by the user.
579
     * @return int
580
     */
581
    public function patrols()
582
    {
583
        $logCounts = $this->getLogCounts();
584
        return $logCounts['patrol-patrol'] ?: 0;
585
    }
586
587
    /**
588
     * Get the total number of accounts created by the user.
589
     * @return int
590
     */
591
    public function accountsCreated()
592
    {
593
        $logCounts = $this->getLogCounts();
594
        $create2 = $logCounts['newusers-create2'] ?: 0;
595
        $byemail = $logCounts['newusers-byemail'] ?: 0;
596
        return $create2 + $byemail;
597
    }
598
599
    /**
600
     * Get the given user's total edit counts per namespace.
601
     * @return integer[] Array keys are namespace IDs, values are the edit counts.
602
     */
603
    public function namespaceTotals()
604
    {
605
        $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...
606
        arsort($counts);
607
        return $counts;
608
    }
609
610
    /**
611
     * Get a summary of the times of day and the days of the week that the user has edited.
612
     * @return string[]
613
     */
614
    public function timeCard()
615
    {
616
        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...
617
    }
618
619
    /**
620
     * Get the total numbers of edits per year.
621
     * @return int[]
622
     */
623
    public function yearCounts()
624
    {
625
        $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...
626
        $out = [
627
            'years' => [],
628
            'namespaces' => [],
629
            'totals' => [],
630
        ];
631
        foreach ($totals as $total) {
632
            $out['years'][$total['year']] = $total['year'];
633
            $out['namespaces'][$total['page_namespace']] = $total['page_namespace'];
634
            if (!isset($out['totals'][$total['page_namespace']])) {
635
                $out['totals'][$total['page_namespace']] = [];
636
            }
637
            $out['totals'][$total['page_namespace']][$total['year']] = $total['count'];
638
        }
639
640
        return $out;
641
    }
642
643
    /**
644
     * Get the total numbers of edits per month.
645
     * @return mixed[] With keys 'years', 'namespaces' and 'totals'.
646
     */
647
    public function monthCounts()
648
    {
649
        $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...
650
        $out = [
651
            'years' => [],
652
            'namespaces' => [],
653
            'totals' => [],
654
        ];
655
        $out['max_year'] = 0;
656
        $out['min_year'] = date('Y');
657
        foreach ($totals as $total) {
658
            // Collect all applicable years and namespaces.
659
            $out['max_year'] = max($out['max_year'], $total['year']);
660
            $out['min_year'] = min($out['min_year'], $total['year']);
661
            // Collate the counts by namespace, and then year and month.
662
            $ns = $total['page_namespace'];
663
            if (!isset($out['totals'][$ns])) {
664
                $out['totals'][$ns] = [];
665
            }
666
            $out['totals'][$ns][$total['year'] . $total['month']] = $total['count'];
667
        }
668
        // Fill in the blanks (where no edits were made in a given month for a namespace).
669
        for ($y = $out['min_year']; $y <= $out['max_year']; $y++) {
670
            for ($m = 1; $m <= 12; $m++) {
671
                foreach ($out['totals'] as $nsId => &$total) {
672
                    if (!isset($total[$y . $m])) {
673
                        $total[$y . $m] = 0;
674
                    }
675
                }
676
            }
677
        }
678
        return $out;
679
    }
680
681
    /**
682
     * Get the total edit counts for the top n projects of this user.
683
     * @param int $numProjects
684
     * @return mixed[] Each element has 'total' and 'project' keys.
685
     */
686
    public function globalEditCountsTopN($numProjects = 10)
687
    {
688
        // Get counts.
689
        $editCounts = $this->globalEditCounts(true);
690
        // 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...
691
        return array_slice($editCounts, 0, $numProjects);
692
    }
693
694
    /**
695
     * Get the total number of edits excluding the top n.
696
     * @param int $numProjects
697
     * @return int
698
     */
699
    public function globalEditCountWithoutTopN($numProjects = 10)
700
    {
701
        $editCounts = $this->globalEditCounts(true);
702
        $bottomM = array_slice($editCounts, $numProjects);
703
        $total = 0;
704
        foreach ($bottomM as $editCount) {
705
            $total += $editCount['total'];
706
        }
707
        return $total;
708
    }
709
710
    /**
711
     * Get the grand total of all edits on all projects.
712
     * @return int
713
     */
714
    public function globalEditCount()
715
    {
716
        $total = 0;
717
        foreach ($this->globalEditCounts() as $editCount) {
718
            $total += $editCount['total'];
719
        }
720
        return $total;
721
    }
722
723
    /**
724
     * Get the total revision counts for all projects for this user.
725
     * @param bool $sorted Whether to sort the list by total, or not.
726
     * @return mixed[] Each element has 'total' and 'project' keys.
727
     */
728
    public function globalEditCounts($sorted = false)
729
    {
730
        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...
731
            $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...
732
                ->globalEditCounts($this->user, $this->project);
733
            if ($sorted) {
734
                // Sort.
735
                uasort($this->globalEditCounts, function ($a, $b) {
736
                    return $b['total'] - $a['total'];
737
                });
738
            }
739
        }
740
        return $this->globalEditCounts;
741
    }
742
743
    /**
744
     * Get the most recent n revisions across all projects.
745
     * @param int $max The maximum number of revisions to return.
746
     * @return Edit[]
747
     */
748
    public function globalEdits($max)
749
    {
750
        // Only look for revisions newer than this.
751
        $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...
752
        // Collect all projects with any edits.
753
        $projects = [];
754
        foreach ($this->globalEditCounts() as $editCount) {
755
            // Don't query revisions if there aren't any.
756
            if ($editCount['total'] == 0) {
757
                continue;
758
            }
759
            $projects[$editCount['project']->getDatabaseName()] = $editCount['project'];
760
        }
761
762
        // Get all revisions for those projects.
763
        $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...
764
            ->getRevisions($projects, $this->user, $max);
765
        $globalEdits = [];
766
        foreach ($globalRevisionsData as $revision) {
767
            /** @var Project $project */
768
            $project = $projects[$revision['project_name']];
769
            $nsName = '';
770
            if ($revision['page_namespace']) {
771
                $nsName = $project->getNamespaces()[$revision['page_namespace']];
772
            }
773
            $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...
774
                ->getPage($project, $nsName . ':' . $revision['page_title']);
775
            $edit = new Edit($page, $revision);
776
            $globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit;
777
        }
778
779
        // Sort and prune, before adding more.
780
        krsort($globalEdits);
781
        $globalEdits = array_slice($globalEdits, 0, $max);
782
        return $globalEdits;
783
    }
784
}
785