Completed
Push — master ( fd3d21...1cb40b )
by Sam
02:55
created

EditCounter::countPagesProtected()   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
     * 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 and page counts etc.
53
     * @return int[]
54
     */
55
    protected function getPairData()
56
    {
57
        if (! is_array($this->pairData)) {
58
            $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...
59
                ->getPairData($this->project, $this->user);
60
        }
61
        return $this->pairData;
62
    }
63
64
    /**
65
     * Get revision dates.
66
     * @return int[]
67
     */
68
    protected function getLogCounts()
69
    {
70
        if (! is_array($this->logCounts)) {
71
            $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...
72
                ->getLogCounts($this->project, $this->user);
73
        }
74
        return $this->logCounts;
75
    }
76
77
    /**
78
     * Get block data.
79
     * @param string $type Either 'set' or 'received'.
80
     * @return array
81
     */
82
    protected function getBlocks($type)
83
    {
84
        if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) {
85
            return $this->blocks[$type];
86
        }
87
        $method = "getBlocks".ucfirst($type);
88
        $blocks = $this->getRepository()->$method($this->project, $this->user);
89
        $this->blocks[$type] = $blocks;
90
        return $this->blocks[$type];
91
    }
92
93
    /**
94
     * Get the total number of currently-live revisions.
95
     * @return int
96
     */
97
    public function countLiveRevisions()
98
    {
99
        $revCounts = $this->getPairData();
100
        return isset($revCounts['live']) ? $revCounts['live'] : 0;
101
    }
102
103
    /**
104
     * Get the total number of the user's revisions that have been deleted.
105
     * @return int
106
     */
107
    public function countDeletedRevisions()
108
    {
109
        $revCounts = $this->getPairData();
110
        return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0;
111
    }
112
113
    /**
114
     * Get the total edit count (live + deleted).
115
     * @return int
116
     */
117
    public function countAllRevisions()
118
    {
119
        return $this->countLiveRevisions() + $this->countDeletedRevisions();
120
    }
121
122
    /**
123
     * Get the total number of revisions with comments.
124
     * @return int
125
     */
126
    public function countRevisionsWithComments()
127
    {
128
        $revCounts = $this->getPairData();
129
        return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0;
130
    }
131
132
    /**
133
     * Get the total number of revisions without comments.
134
     * @return int
135
     */
136
    public function countRevisionsWithoutComments()
137
    {
138
        return $this->countAllRevisions() - $this->countRevisionsWithComments();
139
    }
140
141
    /**
142
     * Get the total number of revisions marked as 'minor' by the user.
143
     * @return int
144
     */
145
    public function countMinorRevisions()
146
    {
147
        $revCounts = $this->getPairData();
148
        return isset($revCounts['minor']) ? $revCounts['minor'] : 0;
149
    }
150
151
    /**
152
     * Get the total number of revisions under 20 bytes.
153
     */
154
    public function countSmallRevisions()
155
    {
156
        $revCounts = $this->getPairData();
157
        return isset($revCounts['small']) ? $revCounts['small'] : 0;
158
    }
159
160
    /**
161
     * Get the total number of revisions over 1000 bytes.
162
     */
163
    public function countLargeRevisions()
164
    {
165
        $revCounts = $this->getPairData();
166
        return isset($revCounts['large']) ? $revCounts['large'] : 0;
167
    }
168
169
    /**
170
     * Get the average revision size for the user.
171
     * @return float Size in bytes.
172
     */
173
    public function averageRevisionSize()
174
    {
175
        $revisionCounts = $this->getPairData();
176
        if (!isset($revisionCounts['average_size'])) {
177
            return 0;
178
        }
179
        return round($revisionCounts['average_size'], 3);
180
    }
181
182
    /**
183
     * Get the total number of non-deleted pages edited by the user.
184
     * @return int
185
     */
186
    public function countLivePagesEdited()
187
    {
188
        $pageCounts = $this->getPairData();
189
        return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0;
190
    }
191
192
    /**
193
     * Get the total number of deleted pages ever edited by the user.
194
     * @return int
195
     */
196
    public function countDeletedPagesEdited()
197
    {
198
        $pageCounts = $this->getPairData();
199
        return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0;
200
    }
201
202
    /**
203
     * Get the total number of pages ever edited by this user (both live and deleted).
204
     * @return int
205
     */
206
    public function countAllPagesEdited()
207
    {
208
        return $this->countLivePagesEdited() + $this->countDeletedPagesEdited();
209
    }
210
211
    /**
212
     * Get the total number of pages (both still live and those that have been deleted) created
213
     * by the user.
214
     * @return int
215
     */
216
    public function countPagesCreated()
217
    {
218
        return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted();
219
    }
220
221
    /**
222
     * Get the total number of pages created by the user, that have not been deleted.
223
     * @return int
224
     */
225
    public function countCreatedPagesLive()
226
    {
227
        $pageCounts = $this->getPairData();
228
        return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0;
229
    }
230
231
    /**
232
     * Get the total number of pages created by the user, that have since been deleted.
233
     * @return int
234
     */
235
    public function countPagesCreatedDeleted()
236
    {
237
        $pageCounts = $this->getPairData();
238
        return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0;
239
    }
240
241
    /**
242
     * Get the total number of pages that have been deleted by the user.
243
     * @return int
244
     */
245
    public function countPagesDeleted()
246
    {
247
        $logCounts = $this->getLogCounts();
248
        return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0;
249
    }
250
251
    /**
252
     * Get the total number of pages moved by the user.
253
     * @return int
254
     */
255
    public function countPagesMoved()
256
    {
257
        $logCounts = $this->getLogCounts();
258
        return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0;
259
    }
260
261
    /**
262
     * Get the total number of times the user has blocked or re-blocked a user.
263
     * @return int
264
     */
265
    public function countBlocksSet()
266
    {
267
        $logCounts = $this->getLogCounts();
268
        $block = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0;
269
        $reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0;
270
        return $block + $reBlock;
271
    }
272
273
    /**
274
     * Get the total number of blocks that have been lifted (i.e. unblocks) by this user.
275
     * @return int
276
     */
277
    public function countBlocksLifted()
278
    {
279
        $logCounts = $this->getLogCounts();
280
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
281
    }
282
283
    /**
284
     * Get the total number of times the user has been blocked.
285
     * @return int
286
     */
287
    public function countBlocksReceived()
288
    {
289
        $blocks = $this->getBlocks('received');
290
        return count($blocks);
291
    }
292
293
    /**
294
     * Get the total number of users blocked by this user.
295
     * @return int
296
     */
297
    public function countUsersBlocked()
298
    {
299
        $blocks = $this->getBlocks('set');
300
        $usersBlocked = [];
301
        foreach ($blocks as $block) {
302
            $usersBlocked[$block['ipb_user']] = true;
303
        }
304
        return count($usersBlocked);
305
    }
306
307
    /**
308
     * Get the total number of users that this user has unblocked.
309
     * @return int
310
     */
311
    public function countUsersUnblocked()
312
    {
313
        $logCounts = $this->getLogCounts();
314
        return isset($logCounts['users-unblocked']) ? (int)$logCounts['users-unblocked'] : 0;
315
    }
316
317
    /**
318
     * Get the total number of pages protected by the user.
319
     * @return int
320
     */
321
    public function countPagesProtected()
322
    {
323
        $logCounts = $this->getLogCounts();
324
        return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0;
325
    }
326
    
327
    /**
328
     * Get the total number of pages unprotected by the user.
329
     * @return int
330
     */
331
    public function countPagesUnprotected()
332
    {
333
        $logCounts = $this->getLogCounts();
334
        return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0;
335
    }
336
337
    /**
338
     * Get the total number of edits deleted by the user.
339
     * @return int
340
     */
341
    public function countEditsDeleted()
342
    {
343
        $logCounts = $this->getLogCounts();
344
        return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0;
345
    }
346
347
    /**
348
     * Get the total number of pages restored by the user.
349
     * @return int
350
     */
351
    public function countPagesRestored()
352
    {
353
        $logCounts = $this->getLogCounts();
354
        return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0;
355
    }
356
357
    /**
358
     * Get the total number of pages imported by the user (through any import mechanism:
359
     * interwiki, or XML upload).
360
     * @return int
361
     */
362
    public function countPagesImported()
363
    {
364
        $logCounts = $this->getLogCounts();
365
        $import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0;
366
        $interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0;
367
        $upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0;
368
        return $import + $interwiki + $upload;
369
    }
370
371
    /**
372
     * Get the average number of edits per page (including deleted revisions and pages).
373
     * @return float
374
     */
375
    public function averageRevisionsPerPage()
376
    {
377
        if ($this->countAllPagesEdited() == 0) {
378
            return 0;
379
        }
380
        return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3);
381
    }
382
383
    /**
384
     * Average number of edits made per day.
385
     * @return float
386
     */
387
    public function averageRevisionsPerDay()
388
    {
389
        if ($this->getDays() == 0) {
390
            return 0;
391
        }
392
        return round($this->countAllRevisions() / $this->getDays(), 3);
393
    }
394
395
    /**
396
     * Get the total number of edits made by the user with semi-automating tools.
397
     */
398
    public function countAutomatedRevisions()
399
    {
400
        $autoSummary = $this->automatedRevisionsSummary();
401
        $count = 0;
402
        foreach ($autoSummary as $summary) {
403
            $count += $summary;
404
        }
405
        return $count;
406
    }
407
408
    /**
409
     * Get a summary of the numbers of edits made by the user with semi-automating tools.
410
     */
411
    public function automatedRevisionsSummary()
412
    {
413
        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...
414
    }
415
416
    /**
417
     * Get the count of (non-deleted) edits made in the given timeframe to now.
418
     * @param string $time One of 'day', 'week', 'month', or 'year'.
419
     * @return int The total number of live edits.
420
     */
421
    public function countRevisionsInLast($time)
422
    {
423
        $revCounts = $this->getPairData();
424
        return isset($revCounts[$time]) ? $revCounts[$time] : 0;
425
    }
426
427
    /**
428
     * Get the date and time of the user's first edit.
429
     * @return DateTime|bool The time of the first revision, or false.
430
     */
431
    public function datetimeFirstRevision()
432
    {
433
        $revDates = $this->getPairData();
434
        return isset($revDates['first']) ? new DateTime($revDates['first']) : false;
435
    }
436
437
    /**
438
     * Get the date and time of the user's first edit.
439
     * @return DateTime|bool The time of the last revision, or false.
440
     */
441
    public function datetimeLastRevision()
442
    {
443
        $revDates = $this->getPairData();
444
        return isset($revDates['last']) ? new DateTime($revDates['last']) : false;
445
    }
446
447
    /**
448
     * Get the number of days between the first and last edits.
449
     * If there's only one edit, this is counted as one day.
450
     * @return int
451
     */
452
    public function getDays()
453
    {
454
        $first = $this->datetimeFirstRevision();
455
        $last = $this->datetimeLastRevision();
456
        if ($first === false || $last === false) {
457
            return 0;
458
        }
459
        $days = $last->diff($first)->days;
460
        return $days > 0 ? $days : 1;
461
    }
462
463
    /**
464
     * Get the total number of files uploaded (including those now deleted).
465
     * @return int
466
     */
467
    public function countFilesUploaded()
468
    {
469
        $logCounts = $this->getLogCounts();
470
        return $logCounts['upload-upload'] ?: 0;
471
    }
472
473
    /**
474
     * Get the total number of files uploaded to Commons (including those now deleted).
475
     * This is only applicable for WMF labs installations.
476
     * @return int
477
     */
478
    public function countFilesUploadedCommons()
479
    {
480
        $logCounts = $this->getLogCounts();
481
        return $logCounts['files_uploaded_commons'] ?: 0;
482
    }
483
484
    /**
485
     * Get the total number of revisions the user has sent thanks for.
486
     * @return int
487
     */
488
    public function thanks()
489
    {
490
        $logCounts = $this->getLogCounts();
491
        return $logCounts['thanks-thank'] ?: 0;
492
    }
493
494
    /**
495
     * Get the total number of approvals
496
     * @return int
497
     */
498
    public function approvals()
499
    {
500
        $logCounts = $this->getLogCounts();
501
        $total = $logCounts['review-approve'] +
502
        (!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) +
503
        (!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) +
504
        (!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0);
505
        return $total;
506
    }
507
508
    /**
509
     * Get the total number of patrols performed by the user.
510
     * @return int
511
     */
512
    public function patrols()
513
    {
514
        $logCounts = $this->getLogCounts();
515
        return $logCounts['patrol-patrol'] ?: 0;
516
    }
517
518
    /**
519
     * Get the given user's total edit counts per namespace.
520
     * @return integer[] Array keys are namespace IDs, values are the edit counts.
521
     */
522
    public function namespaceTotals()
523
    {
524
        $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...
525
        arsort($counts);
526
        return $counts;
527
    }
528
529
    /**
530
     * Get a summary of the times of day and the days of the week that the user has edited.
531
     * @return string[]
532
     */
533
    public function timeCard()
534
    {
535
        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...
536
    }
537
538
    /**
539
     * Get the total numbers of edits per year.
540
     * @return int[]
541
     */
542
    public function yearCounts()
543
    {
544
        $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...
545
        $out = [
546
            'years' => [],
547
            'namespaces' => [],
548
            'totals' => [],
549
        ];
550
        foreach ($totals as $total) {
551
            $out['years'][$total['year']] = $total['year'];
552
            $out['namespaces'][$total['page_namespace']] = $total['page_namespace'];
553
            if (!isset($out['totals'][$total['page_namespace']])) {
554
                $out['totals'][$total['page_namespace']] = [];
555
            }
556
            $out['totals'][$total['page_namespace']][$total['year']] = $total['count'];
557
        }
558
559
        return $out;
560
    }
561
562
    /**
563
     * Get the total numbers of edits per month.
564
     * @return mixed[] With keys 'years', 'namespaces' and 'totals'.
565
     */
566
    public function monthCounts()
567
    {
568
        $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...
569
        $out = [
570
            'years' => [],
571
            'namespaces' => [],
572
            'totals' => [],
573
        ];
574
        $out['max_year'] = 0;
575
        $out['min_year'] = date('Y');
576
        foreach ($totals as $total) {
577
            // Collect all applicable years and namespaces.
578
            $out['max_year'] = max($out['max_year'], $total['year']);
579
            $out['min_year'] = min($out['min_year'], $total['year']);
580
            // Collate the counts by namespace, and then year and month.
581
            $ns = $total['page_namespace'];
582
            if (!isset($out['totals'][$ns])) {
583
                $out['totals'][$ns] = [];
584
            }
585
            $out['totals'][$ns][$total['year'] . $total['month']] = $total['count'];
586
        }
587
        // Fill in the blanks (where no edits were made in a given month for a namespace).
588
        for ($y = $out['min_year']; $y <= $out['max_year']; $y++) {
589
            for ($m = 1; $m <= 12; $m++) {
590
                foreach ($out['totals'] as $nsId => &$total) {
591
                    if (!isset($total[$y . $m])) {
592
                        $total[$y . $m] = 0;
593
                    }
594
                }
595
            }
596
        }
597
        return $out;
598
    }
599
600
    /**
601
     * Get the total edit counts for the top n projects of this user.
602
     * @param int $numProjects
603
     * @return mixed[] Each element has 'total' and 'project' keys.
604
     */
605
    public function globalEditCountsTopN($numProjects = 10)
606
    {
607
        // Get counts.
608
        $editCounts = $this->globalEditCounts(true);
609
        // 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...
610
        return array_slice($editCounts, 0, $numProjects);
611
    }
612
613
    /**
614
     * Get the total number of edits excluding the top n.
615
     * @param int $numProjects
616
     * @return int
617
     */
618
    public function globalEditCountWithoutTopN($numProjects = 10)
619
    {
620
        $editCounts = $this->globalEditCounts(true);
621
        $bottomM = array_slice($editCounts, $numProjects);
622
        $total = 0;
623
        foreach ($bottomM as $editCount) {
624
            $total += $editCount['total'];
625
        }
626
        return $total;
627
    }
628
629
    /**
630
     * Get the grand total of all edits on all projects.
631
     * @return int
632
     */
633
    public function globalEditCount()
634
    {
635
        $total = 0;
636
        foreach ($this->globalEditCounts() as $editCount) {
637
            $total += $editCount['total'];
638
        }
639
        return $total;
640
    }
641
642
    /**
643
     * Get the total revision counts for all projects for this user.
644
     * @param bool $sorted Whether to sort the list by total, or not.
645
     * @return mixed[] Each element has 'total' and 'project' keys.
646
     */
647
    public function globalEditCounts($sorted = false)
648
    {
649
        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...
650
            $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...
651
                ->globalEditCounts($this->user, $this->project);
652
            if ($sorted) {
653
                // Sort.
654
                uasort($this->globalEditCounts, function ($a, $b) {
655
                    return $b['total'] - $a['total'];
656
                });
657
            }
658
        }
659
        return $this->globalEditCounts;
660
    }
661
662
    /**
663
     * Get the most recent n revisions across all projects.
664
     * @param int $max The maximum number of revisions to return.
665
     * @return Edit[]
666
     */
667
    public function globalEdits($max)
668
    {
669
        // Only look for revisions newer than this.
670
        $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...
671
        // Collect all projects with any edits.
672
        $projects = [];
673
        foreach ($this->globalEditCounts() as $editCount) {
674
            // Don't query revisions if there aren't any.
675
            if ($editCount['total'] == 0) {
676
                continue;
677
            }
678
            $projects[$editCount['project']->getDatabaseName()] = $editCount['project'];
679
        }
680
681
        // Get all revisions for those projects.
682
        $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...
683
            ->getRevisions($projects, $this->user, $max);
684
        $globalEdits = [];
685
        foreach ($globalRevisionsData as $revision) {
686
            /** @var Project $project */
687
            $project = $projects[$revision['project_name']];
688
            $nsName = '';
689
            if ($revision['page_namespace']) {
690
                $nsName = $project->getNamespaces()[$revision['page_namespace']];
691
            }
692
            $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...
693
                ->getPage($project, $nsName . ':' . $revision['page_title']);
694
            $edit = new Edit($page, $revision);
695
            $globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit;
696
        }
697
698
        // Sort and prune, before adding more.
699
        krsort($globalEdits);
700
        $globalEdits = array_slice($globalEdits, 0, $max);
701
        return $globalEdits;
702
    }
703
}
704