Completed
Push — master ( c51827...601203 )
by Sam
02:51
created

EditCounter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

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

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
633
        return array_slice($editCounts, 0, $numProjects);
634
    }
635
636
    /**
637
     * Get the grand total of all edits on all projects.
638
     * @return int
639
     */
640
    public function globalEditCount()
641
    {
642
        $total = 0;
643
        foreach ($this->globalEditCounts() as $editCount) {
644
            $total += $editCount['total'];
645
        }
646
        return $total;
647
    }
648
649
    /**
650
     * Get the total revision counts for all projects for this user.
651
     * @return mixed[] Each element has 'total' and 'project' keys.
652
     */
653
    public function globalEditCounts()
654
    {
655
        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...
656
            $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...
657
                ->globalEditCounts($this->user, $this->project);
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
        // Store the top n Edits.
670
        $globalRevisions = [];
671
        // Only look for revisions newer than this.
672
        $oldest = null;
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
679
            /** @var Project $otherProject */
680
            $otherProject = $editCount['project'];
681
            $revisions = $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...
682
                ->getRevisions($otherProject, $this->user, $oldest, $max);
683
            foreach ($revisions as &$revision) {
684
                $nsName = $otherProject->getNamespaces()[$revision['page_namespace']];
685
                $page = $otherProject->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...
686
                    ->getPage($otherProject, $nsName . ':' . $revision['page_title']);
687
                $edit = new Edit($page, $revision);
688
689
                // If we've already got enough, only check for those newer than the current oldest.
690
                $haveEnough = (count($globalRevisions) >= $max);
691
                $thisIsOlder = ($oldest === null
692
                    || ($oldest !== null && $edit->getTimestamp() < $oldest));
693
                if ($haveEnough && $thisIsOlder) {
694
                    // Use this as the new oldest time.
695
                    $oldest = $edit->getTimestamp(); //$revision['unix_timestamp'];
696
                }
697
                $globalRevisions[$edit->getTimestamp()->getTimestamp().'-'.$edit->getId()] = $edit;
698
            }
699
            // Sort and prune, before adding more.
700
            krsort($globalRevisions);
701
            $globalRevisions = array_slice($globalRevisions, 0, $max);
702
        }
703
        return $globalRevisions;
704
    }
705
}
706