Completed
Push — master ( cf9a22...944927 )
by Sam
06:52
created

EditCounter::averageEditSize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 2
eloc 6
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
     * Revision size data, with keys 'average_size', 'large_edits' and 'small_edits'.
42
     * @var string[] As returned by the DB, unconverted to int or float
43
     */
44
    protected $editSizeData;
45
46
    /**
47
     * Duration of the longest block in days; -1 if indefinite,
48
     *   or false if could not be parsed from log params
49
     * @var int|bool
50
     */
51
    protected $longestBlockDays;
52
53
    /**
54
     * EditCounter constructor.
55
     * @param Project $project The base project to count edits
56
     * @param User $user
57
     */
58
    public function __construct(Project $project, User $user)
59
    {
60
        $this->project = $project;
61
        $this->user = $user;
62
    }
63
64
    /**
65
     * Get revision and page counts etc.
66
     * @return int[]
67
     */
68
    protected function getPairData()
69
    {
70
        if (! is_array($this->pairData)) {
71
            $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...
72
                ->getPairData($this->project, $this->user);
73
        }
74
        return $this->pairData;
75
    }
76
77
    /**
78
     * Get revision dates.
79
     * @return int[]
80
     */
81
    protected function getLogCounts()
82
    {
83
        if (! is_array($this->logCounts)) {
84
            $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...
85
                ->getLogCounts($this->project, $this->user);
86
        }
87
        return $this->logCounts;
88
    }
89
90
    /**
91
     * Get block data.
92
     * @param string $type Either 'set' or 'received'.
93
     * @return array
94
     */
95
    protected function getBlocks($type)
96
    {
97
        if (isset($this->blocks[$type]) && is_array($this->blocks[$type])) {
98
            return $this->blocks[$type];
99
        }
100
        $method = "getBlocks".ucfirst($type);
101
        $blocks = $this->getRepository()->$method($this->project, $this->user);
102
        $this->blocks[$type] = $blocks;
103
        return $this->blocks[$type];
104
    }
105
106
    /**
107
     * Get the total number of currently-live revisions.
108
     * @return int
109
     */
110
    public function countLiveRevisions()
111
    {
112
        $revCounts = $this->getPairData();
113
        return isset($revCounts['live']) ? $revCounts['live'] : 0;
114
    }
115
116
    /**
117
     * Get the total number of the user's revisions that have been deleted.
118
     * @return int
119
     */
120
    public function countDeletedRevisions()
121
    {
122
        $revCounts = $this->getPairData();
123
        return isset($revCounts['deleted']) ? $revCounts['deleted'] : 0;
124
    }
125
126
    /**
127
     * Get the total edit count (live + deleted).
128
     * @return int
129
     */
130
    public function countAllRevisions()
131
    {
132
        return $this->countLiveRevisions() + $this->countDeletedRevisions();
133
    }
134
135
    /**
136
     * Get the total number of revisions with comments.
137
     * @return int
138
     */
139
    public function countRevisionsWithComments()
140
    {
141
        $revCounts = $this->getPairData();
142
        return isset($revCounts['with_comments']) ? $revCounts['with_comments'] : 0;
143
    }
144
145
    /**
146
     * Get the total number of revisions without comments.
147
     * @return int
148
     */
149
    public function countRevisionsWithoutComments()
150
    {
151
        return $this->countAllRevisions() - $this->countRevisionsWithComments();
152
    }
153
154
    /**
155
     * Get the total number of revisions marked as 'minor' by the user.
156
     * @return int
157
     */
158
    public function countMinorRevisions()
159
    {
160
        $revCounts = $this->getPairData();
161
        return isset($revCounts['minor']) ? $revCounts['minor'] : 0;
162
    }
163
164
    /**
165
     * Get the total number of non-deleted pages edited by the user.
166
     * @return int
167
     */
168
    public function countLivePagesEdited()
169
    {
170
        $pageCounts = $this->getPairData();
171
        return isset($pageCounts['edited-live']) ? $pageCounts['edited-live'] : 0;
172
    }
173
174
    /**
175
     * Get the total number of deleted pages ever edited by the user.
176
     * @return int
177
     */
178
    public function countDeletedPagesEdited()
179
    {
180
        $pageCounts = $this->getPairData();
181
        return isset($pageCounts['edited-deleted']) ? $pageCounts['edited-deleted'] : 0;
182
    }
183
184
    /**
185
     * Get the total number of pages ever edited by this user (both live and deleted).
186
     * @return int
187
     */
188
    public function countAllPagesEdited()
189
    {
190
        return $this->countLivePagesEdited() + $this->countDeletedPagesEdited();
191
    }
192
193
    /**
194
     * Get the total number of pages (both still live and those that have been deleted) created
195
     * by the user.
196
     * @return int
197
     */
198
    public function countPagesCreated()
199
    {
200
        return $this->countCreatedPagesLive() + $this->countPagesCreatedDeleted();
201
    }
202
203
    /**
204
     * Get the total number of pages created by the user, that have not been deleted.
205
     * @return int
206
     */
207
    public function countCreatedPagesLive()
208
    {
209
        $pageCounts = $this->getPairData();
210
        return isset($pageCounts['created-live']) ? (int)$pageCounts['created-live'] : 0;
211
    }
212
213
    /**
214
     * Get the total number of pages created by the user, that have since been deleted.
215
     * @return int
216
     */
217
    public function countPagesCreatedDeleted()
218
    {
219
        $pageCounts = $this->getPairData();
220
        return isset($pageCounts['created-deleted']) ? (int)$pageCounts['created-deleted'] : 0;
221
    }
222
223
    /**
224
     * Get the total number of pages that have been deleted by the user.
225
     * @return int
226
     */
227
    public function countPagesDeleted()
228
    {
229
        $logCounts = $this->getLogCounts();
230
        return isset($logCounts['delete-delete']) ? (int)$logCounts['delete-delete'] : 0;
231
    }
232
233
    /**
234
     * Get the total number of pages moved by the user.
235
     * @return int
236
     */
237
    public function countPagesMoved()
238
    {
239
        $logCounts = $this->getLogCounts();
240
        return isset($logCounts['move-move']) ? (int)$logCounts['move-move'] : 0;
241
    }
242
243
    /**
244
     * Get the total number of times the user has blocked a user.
245
     * @return int
246
     */
247
    public function countBlocksSet()
248
    {
249
        $logCounts = $this->getLogCounts();
250
        $reBlock = isset($logCounts['block-block']) ? (int)$logCounts['block-block'] : 0;
251
        return $reBlock;
252
    }
253
254
    /**
255
     * Get the total number of times the user has re-blocked a user.
256
     * @return int
257
     */
258
    public function countReblocksSet()
259
    {
260
        $logCounts = $this->getLogCounts();
261
        $reBlock = isset($logCounts['block-reblock']) ? (int)$logCounts['block-reblock'] : 0;
262
        return $reBlock;
263
    }
264
265
    /**
266
     * Get the total number of times the user has unblocked a user.
267
     * @return int
268
     */
269
    public function countUnblocksSet()
270
    {
271
        $logCounts = $this->getLogCounts();
272
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
273
    }
274
275
    /**
276
     * Get the total number of blocks that have been lifted (i.e. unblocks) by this user.
277
     * @return int
278
     */
279
    public function countBlocksLifted()
280
    {
281
        $logCounts = $this->getLogCounts();
282
        return isset($logCounts['block-unblock']) ? (int)$logCounts['block-unblock'] : 0;
283
    }
284
285
    /**
286
     * Get the total number of times the user has been blocked.
287
     * @return int
288
     */
289
    public function countBlocksReceived()
290
    {
291
        $blocks = $this->getBlocks('received');
292
        return count($blocks);
293
    }
294
295
    /**
296
     * Get the length of the longest block the user received.
297
     * @return int|bool Number of days or false if it could not be determined.
298
     *                  If the longest duration is indefinite, -1 is returned.
299
     */
300
    public function getLongestBlockDays()
301
    {
302
        if (isset($this->longestBlockDays)) {
303
            return $this->longestBlockDays;
304
        }
305
306
        $blocks = $this->getBlocks('received'); // FIXME: make sure this is only called once
307
        $this->longestBlockDays = false;
308
309
        foreach ($blocks as $block) {
310
            $timestamp = strtotime($block['log_timestamp']);
311
312
            // First check if the string is serialized, and if so parse it to get the block duration
313
            if (@unserialize($block['log_params']) !== false) {
314
                $parsedParams = unserialize($block['log_params']);
315
                $durationStr = $parsedParams['5::duration'];
316
            } else {
317
                // Old format, the duration in English + block options separated by new lines
318
                $durationStr = explode("\n", $block['log_params'])[0];
319
            }
320
321
            if (in_array($durationStr, ['indefinite', 'infinity', 'infinite'])) {
322
                return -1;
323
            }
324
325
            // Try block just in case there are older, unpredictable formats
326
            try {
327
                $expiry = strtotime($durationStr, $timestamp);
328
                $duration = ($expiry - $timestamp) / (60 * 60 * 24);
329
330
                if (!$duration || $duration > $this->longestBlockDays) {
331
                    $this->longestBlockDays = $duration;
332
                }
333
            } 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...
334
                // do nothing, leaving the longest block at false
335
            }
336
        }
337
338
        return $this->longestBlockDays;
339
    }
340
341
    /**
342
     * Get the total number of pages protected by the user.
343
     * @return int
344
     */
345
    public function countPagesProtected()
346
    {
347
        $logCounts = $this->getLogCounts();
348
        return isset($logCounts['protect-protect']) ? (int)$logCounts['protect-protect'] : 0;
349
    }
350
351
    /**
352
     * Get the total number of pages reprotected by the user.
353
     * @return int
354
     */
355
    public function countPagesReprotected()
356
    {
357
        $logCounts = $this->getLogCounts();
358
        return isset($logCounts['protect-modify']) ? (int)$logCounts['protect-modify'] : 0;
359
    }
360
361
    /**
362
     * Get the total number of pages unprotected by the user.
363
     * @return int
364
     */
365
    public function countPagesUnprotected()
366
    {
367
        $logCounts = $this->getLogCounts();
368
        return isset($logCounts['protect-unprotect']) ? (int)$logCounts['protect-unprotect'] : 0;
369
    }
370
371
    /**
372
     * Get the total number of edits deleted by the user.
373
     * @return int
374
     */
375
    public function countEditsDeleted()
376
    {
377
        $logCounts = $this->getLogCounts();
378
        return isset($logCounts['delete-revision']) ? (int)$logCounts['delete-revision'] : 0;
379
    }
380
381
    /**
382
     * Get the total number of pages restored by the user.
383
     * @return int
384
     */
385
    public function countPagesRestored()
386
    {
387
        $logCounts = $this->getLogCounts();
388
        return isset($logCounts['delete-restore']) ? (int)$logCounts['delete-restore'] : 0;
389
    }
390
391
    /**
392
     * Get the total number of times the user has modified the rights of a user.
393
     * @return int
394
     */
395
    public function countRightsModified()
396
    {
397
        $logCounts = $this->getLogCounts();
398
        return isset($logCounts['rights-rights']) ? (int)$logCounts['rights-rights'] : 0;
399
    }
400
401
    /**
402
     * Get the total number of pages imported by the user (through any import mechanism:
403
     * interwiki, or XML upload).
404
     * @return int
405
     */
406
    public function countPagesImported()
407
    {
408
        $logCounts = $this->getLogCounts();
409
        $import = isset($logCounts['import-import']) ? (int)$logCounts['import-import'] : 0;
410
        $interwiki = isset($logCounts['import-interwiki']) ? (int)$logCounts['import-interwiki'] : 0;
411
        $upload = isset($logCounts['import-upload']) ? (int)$logCounts['import-upload'] : 0;
412
        return $import + $interwiki + $upload;
413
    }
414
415
    /**
416
     * Get the average number of edits per page (including deleted revisions and pages).
417
     * @return float
418
     */
419
    public function averageRevisionsPerPage()
420
    {
421
        if ($this->countAllPagesEdited() == 0) {
422
            return 0;
423
        }
424
        return round($this->countAllRevisions() / $this->countAllPagesEdited(), 3);
425
    }
426
427
    /**
428
     * Average number of edits made per day.
429
     * @return float
430
     */
431
    public function averageRevisionsPerDay()
432
    {
433
        if ($this->getDays() == 0) {
434
            return 0;
435
        }
436
        return round($this->countAllRevisions() / $this->getDays(), 3);
437
    }
438
439
    /**
440
     * Get the total number of edits made by the user with semi-automating tools.
441
     */
442
    public function countAutomatedRevisions()
443
    {
444
        $autoSummary = $this->automatedRevisionsSummary();
445
        $count = 0;
446
        foreach ($autoSummary as $summary) {
447
            $count += $summary;
448
        }
449
        return $count;
450
    }
451
452
    /**
453
     * Get a summary of the numbers of edits made by the user with semi-automating tools.
454
     */
455
    public function automatedRevisionsSummary()
456
    {
457
        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...
458
    }
459
460
    /**
461
     * Get the count of (non-deleted) edits made in the given timeframe to now.
462
     * @param string $time One of 'day', 'week', 'month', or 'year'.
463
     * @return int The total number of live edits.
464
     */
465
    public function countRevisionsInLast($time)
466
    {
467
        $revCounts = $this->getPairData();
468
        return isset($revCounts[$time]) ? $revCounts[$time] : 0;
469
    }
470
471
    /**
472
     * Get the date and time of the user's first edit.
473
     * @return DateTime|bool The time of the first revision, or false.
474
     */
475
    public function datetimeFirstRevision()
476
    {
477
        $revDates = $this->getPairData();
478
        return isset($revDates['first']) ? new DateTime($revDates['first']) : false;
479
    }
480
481
    /**
482
     * Get the date and time of the user's first edit.
483
     * @return DateTime|bool The time of the last revision, or false.
484
     */
485
    public function datetimeLastRevision()
486
    {
487
        $revDates = $this->getPairData();
488
        return isset($revDates['last']) ? new DateTime($revDates['last']) : false;
489
    }
490
491
    /**
492
     * Get the number of days between the first and last edits.
493
     * If there's only one edit, this is counted as one day.
494
     * @return int
495
     */
496
    public function getDays()
497
    {
498
        $first = $this->datetimeFirstRevision();
499
        $last = $this->datetimeLastRevision();
500
        if ($first === false || $last === false) {
501
            return 0;
502
        }
503
        $days = $last->diff($first)->days;
504
        return $days > 0 ? $days : 1;
505
    }
506
507
    /**
508
     * Get the total number of files uploaded (including those now deleted).
509
     * @return int
510
     */
511
    public function countFilesUploaded()
512
    {
513
        $logCounts = $this->getLogCounts();
514
        return $logCounts['upload-upload'] ?: 0;
515
    }
516
517
    /**
518
     * Get the total number of files uploaded to Commons (including those now deleted).
519
     * This is only applicable for WMF labs installations.
520
     * @return int
521
     */
522
    public function countFilesUploadedCommons()
523
    {
524
        $logCounts = $this->getLogCounts();
525
        return $logCounts['files_uploaded_commons'] ?: 0;
526
    }
527
528
    /**
529
     * Get the total number of revisions the user has sent thanks for.
530
     * @return int
531
     */
532
    public function thanks()
533
    {
534
        $logCounts = $this->getLogCounts();
535
        return $logCounts['thanks-thank'] ?: 0;
536
    }
537
538
    /**
539
     * Get the total number of approvals
540
     * @return int
541
     */
542
    public function approvals()
543
    {
544
        $logCounts = $this->getLogCounts();
545
        $total = $logCounts['review-approve'] +
546
        (!empty($logCounts['review-approve-a']) ? $logCounts['review-approve-a'] : 0) +
547
        (!empty($logCounts['review-approve-i']) ? $logCounts['review-approve-i'] : 0) +
548
        (!empty($logCounts['review-approve-ia']) ? $logCounts['review-approve-ia'] : 0);
549
        return $total;
550
    }
551
552
    /**
553
     * Get the total number of patrols performed by the user.
554
     * @return int
555
     */
556
    public function patrols()
557
    {
558
        $logCounts = $this->getLogCounts();
559
        return $logCounts['patrol-patrol'] ?: 0;
560
    }
561
562
    /**
563
     * Get the total number of accounts created by the user.
564
     * @return int
565
     */
566
    public function accountsCreated()
567
    {
568
        $logCounts = $this->getLogCounts();
569
        $create2 = $logCounts['newusers-create2'] ?: 0;
570
        $byemail = $logCounts['newusers-byemail'] ?: 0;
571
        return $create2 + $byemail;
572
    }
573
574
    /**
575
     * Get the given user's total edit counts per namespace.
576
     * @return integer[] Array keys are namespace IDs, values are the edit counts.
577
     */
578
    public function namespaceTotals()
579
    {
580
        $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...
581
        arsort($counts);
582
        return $counts;
583
    }
584
585
    /**
586
     * Get a summary of the times of day and the days of the week that the user has edited.
587
     * @return string[]
588
     */
589
    public function timeCard()
590
    {
591
        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...
592
    }
593
594
    /**
595
     * Get the total numbers of edits per year.
596
     * @return int[]
597
     */
598
    public function yearCounts()
599
    {
600
        $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...
601
        $out = [
602
            'years' => [],
603
            'namespaces' => [],
604
            'totals' => [],
605
        ];
606
        foreach ($totals as $total) {
607
            $out['years'][$total['year']] = $total['year'];
608
            $out['namespaces'][$total['page_namespace']] = $total['page_namespace'];
609
            if (!isset($out['totals'][$total['page_namespace']])) {
610
                $out['totals'][$total['page_namespace']] = [];
611
            }
612
            $out['totals'][$total['page_namespace']][$total['year']] = $total['count'];
613
        }
614
615
        // Make sure data is sorted by namespace
616
        ksort($out['namespaces']);
617
        ksort($out['totals']);
618
619
        return $out;
620
    }
621
622
    /**
623
     * Get the total numbers of edits per month.
624
     * @return mixed[] With keys 'years', 'namespaces' and 'totals'.
625
     */
626
    public function monthCounts()
627
    {
628
        $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...
629
        $out = [
630
            'years' => [],
631
            'totals' => [],
632
        ];
633
        $out['max_year'] = 0;
634
        $out['min_year'] = date('Y');
635
        foreach ($totals as $total) {
636
            // Collect all applicable years and namespaces.
637
            $out['max_year'] = max($out['max_year'], $total['year']);
638
            $out['min_year'] = min($out['min_year'], $total['year']);
639
            // Collate the counts by namespace, and then year and month.
640
            $ns = $total['page_namespace'];
641
            if (!isset($out['totals'][$ns])) {
642
                $out['totals'][$ns] = [];
643
            }
644
            $out['totals'][$ns][$total['year'] . $total['month']] = $total['count'];
645
        }
646
        // Fill in the blanks (where no edits were made in a given month for a namespace).
647
        for ($y = $out['min_year']; $y <= $out['max_year']; $y++) {
648
            for ($m = 1; $m <= 12; $m++) {
649
                foreach ($out['totals'] as $nsId => &$total) {
650
                    if (!isset($total[$y . $m])) {
651
                        $total[$y . $m] = 0;
652
                    }
653
                }
654
            }
655
        }
656
657
        // Sort by namespace
658
        ksort($out['totals']);
659
660
        return $out;
661
    }
662
663
    /**
664
     * Get the total edit counts for the top n projects of this user.
665
     * @param int $numProjects
666
     * @return mixed[] Each element has 'total' and 'project' keys.
667
     */
668
    public function globalEditCountsTopN($numProjects = 10)
669
    {
670
        // Get counts.
671
        $editCounts = $this->globalEditCounts(true);
672
        // 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...
673
        return array_slice($editCounts, 0, $numProjects);
674
    }
675
676
    /**
677
     * Get the total number of edits excluding the top n.
678
     * @param int $numProjects
679
     * @return int
680
     */
681
    public function globalEditCountWithoutTopN($numProjects = 10)
682
    {
683
        $editCounts = $this->globalEditCounts(true);
684
        $bottomM = array_slice($editCounts, $numProjects);
685
        $total = 0;
686
        foreach ($bottomM as $editCount) {
687
            $total += $editCount['total'];
688
        }
689
        return $total;
690
    }
691
692
    /**
693
     * Get the grand total of all edits on all projects.
694
     * @return int
695
     */
696
    public function globalEditCount()
697
    {
698
        $total = 0;
699
        foreach ($this->globalEditCounts() as $editCount) {
700
            $total += $editCount['total'];
701
        }
702
        return $total;
703
    }
704
705
    /**
706
     * Get the total revision counts for all projects for this user.
707
     * @param bool $sorted Whether to sort the list by total, or not.
708
     * @return mixed[] Each element has 'total' and 'project' keys.
709
     */
710
    public function globalEditCounts($sorted = false)
711
    {
712
        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...
713
            $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...
714
                ->globalEditCounts($this->user, $this->project);
715
            if ($sorted) {
716
                // Sort.
717
                uasort($this->globalEditCounts, function ($a, $b) {
718
                    return $b['total'] - $a['total'];
719
                });
720
            }
721
        }
722
        return $this->globalEditCounts;
723
    }
724
725
    /**
726
     * Get the most recent n revisions across all projects.
727
     * @param int $max The maximum number of revisions to return.
728
     * @return Edit[]
729
     */
730
    public function globalEdits($max)
731
    {
732
        // Only look for revisions newer than this.
733
        $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...
734
        // Collect all projects with any edits.
735
        $projects = [];
736
        foreach ($this->globalEditCounts() as $editCount) {
737
            // Don't query revisions if there aren't any.
738
            if ($editCount['total'] == 0) {
739
                continue;
740
            }
741
            $projects[$editCount['project']->getDatabaseName()] = $editCount['project'];
742
        }
743
744
        // Get all revisions for those projects.
745
        $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...
746
            ->getRevisions($projects, $this->user, $max);
747
        $globalEdits = [];
748
        foreach ($globalRevisionsData as $revision) {
749
            /** @var Project $project */
750
            $project = $projects[$revision['project_name']];
751
            $nsName = '';
752
            if ($revision['page_namespace']) {
753
                $nsName = $project->getNamespaces()[$revision['page_namespace']];
754
            }
755
            $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...
756
                ->getPage($project, $nsName . ':' . $revision['page_title']);
757
            $edit = new Edit($page, $revision);
758
            $globalEdits[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit;
759
        }
760
761
        // Sort and prune, before adding more.
762
        krsort($globalEdits);
763
        $globalEdits = array_slice($globalEdits, 0, $max);
764
        return $globalEdits;
765
    }
766
767
    /**
768
     * Get average edit size, and number of large and small edits.
769
     * @return int[]
770
     */
771
    protected function getEditSizeData()
772
    {
773
        if (! is_array($this->editSizeData)) {
774
            $this->editSizeData = $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 getEditSizeData() 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...
775
                ->getEditSizeData($this->project, $this->user);
776
        }
777
        return $this->editSizeData;
778
    }
779
780
    /**
781
     * Get the total edit count of this user or 5,000 if they've made more than 5,000 edits.
782
     * This is used to ensure percentages of small and large edits are computed properly.
783
     * @return int
784
     */
785
    public function countLast5000()
786
    {
787
        return $this->countLiveRevisions() > 5000 ? 5000 : $this->countLiveRevisions();
788
    }
789
790
    /**
791
     * Get the number of edits under 20 bytes of the user's past 5000 edits.
792
     * @return int
793
     */
794
    public function countSmallEdits()
795
    {
796
        $editSizeData = $this->getEditSizeData();
797
        return isset($editSizeData['small_edits']) ? (int) $editSizeData['small_edits'] : 0;
798
    }
799
800
    /**
801
     * Get the total number of edits over 1000 bytes of the user's past 5000 edits.
802
     * @return int
803
     */
804
    public function countLargeEdits()
805
    {
806
        $editSizeData = $this->getEditSizeData();
807
        return isset($editSizeData['large_edits']) ? (int) $editSizeData['large_edits'] : 0;
808
    }
809
810
    /**
811
     * Get the average size of the user's past 5000 edits.
812
     * @return float Size in bytes.
813
     */
814
    public function averageEditSize()
815
    {
816
        $editSizeData = $this->getEditSizeData();
817
        if (isset($editSizeData['average_size'])) {
818
            return round($editSizeData['average_size'], 3);
819
        } else {
820
            return 0;
821
        }
822
    }
823
}
824