Completed
Push — fm-matches ( f867e2...7fc64f )
by Vladimir
14:12
created

Player   F

Complexity

Total Complexity 90

Size/Duplication

Total Lines 1053
Duplicated Lines 1.14 %

Coupling/Cohesion

Components 2
Dependencies 12

Test Coverage

Coverage 53.74%

Importance

Changes 7
Bugs 1 Features 1
Metric Value
wmc 90
c 7
b 1
f 1
lcom 2
cbo 12
dl 12
loc 1053
ccs 151
cts 281
cp 0.5374
rs 3.4592

68 Methods

Rating   Name   Duplication   Size   Complexity  
A setConfirmCode() 0 6 1
A setReceives() 0 6 1
A setOutdated() 0 6 1
A assignResult() 0 10 1
A assignLazyResult() 0 16 1
A addRole() 0 20 4
A getAdminNotes() 0 6 1
A getBZID() 0 4 1
A getCountry() 0 4 1
A getEmailAddress() 0 6 1
A isVerified() 0 6 1
A getConfirmCode() 0 6 1
A getReceives() 0 6 1
A canReceive() 0 15 4
A isCorrectConfirmCode() 0 10 2
A getDescription() 0 6 1
A getJoinedDate() 0 6 1
A getKnownIPs() 0 4 1
A getLastLogin() 0 6 1
A getPastCallsigns() 0 4 1
A getTeam() 0 4 1
A getTimezone() 0 6 2
A updateUserPermissions() 0 9 2
A hasPermission() 0 10 2
A isOutdated() 0 6 1
A isDisabled() 0 4 1
A isTestUser() 0 4 1
A isTeamless() 0 4 1
A markAsBanned() 0 8 2
A markAsUnbanned() 0 8 2
A isBanned() 0 4 1
A getBan() 0 6 1
A removeRole() 0 7 1
A setEmailAddress() 0 15 2
A setVerified() 0 10 2
A generateNewConfirmCode() 0 7 1
A setDescription() 0 5 1
A setTimezone() 0 5 1
A setTeam() 0 5 1
A setStatus() 0 4 1
A setAdminNotes() 0 4 1
A setCountry() 0 4 1
A updateLastLogin() 0 4 1
A getUsername() 0 4 1
A getEscapedUsername() 0 4 1
A setName() 0 4 1
A markMessagesAsRead() 0 7 1
A setRoles() 0 23 3
A modifyRole() 0 18 4
A getFromBZID() 0 4 1
A getFromUsername() 0 6 1
A getPlayers() 0 6 1
A countUnreadNotifications() 0 4 1
A countUnreadMessages() 0 6 1
A getTeamMembers() 0 6 1
A getActiveStatuses() 0 4 1
A getEagerColumns() 0 4 1
A getLazyColumns() 0 4 1
A getQueryBuilder() 12 12 1
B newPlayer() 0 26 2
A playerBZIDExists() 0 4 1
B setUsername() 0 31 2
A getAlphabeticalSort() 0 6 1
A wipe() 0 6 1
A canSee() 0 4 1
A canDelete() 0 8 2
A canCreate() 0 4 1
A canEdit() 0 4 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Player often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Player, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file contains functionality relating to a league player
4
 *
5
 * @package    BZiON\Models
6
 * @license    https://github.com/allejo/bzion/blob/master/LICENSE.md GNU General Public License Version 3
7
 */
8
9
use Symfony\Component\Security\Core\Util\SecureRandom;
10
use Symfony\Component\Security\Core\Util\StringUtils;
11
12
/**
13
 * A league player
14
 * @package    BZiON\Models
15
 */
16
class Player extends AvatarModel implements NamedModel
17
{
18
    /**
19
     * These are built-in roles that cannot be deleted via the web interface so we will be storing these values as
20
     * constant variables. Hopefully, a user won't be silly enough to delete them manually from the database.
21
     *
22
     * @TODO Deprecate these and use the Role constants
23
     */
24
    const DEVELOPER    = Role::DEVELOPER;
25
    const ADMIN        = Role::ADMINISTRATOR;
26
    const COP          = Role::COP;
27
    const REFEREE      = Role::REFEREE;
28
    const S_ADMIN      = Role::SYSADMIN;
29
    const PLAYER       = Role::PLAYER;
30
    const PLAYER_NO_PM = Role::PLAYER_NO_PM;
31
32
    /**
33
     * The bzid of the player
34
     * @var int
35
     */
36
    protected $bzid;
37
38
    /**
39
     * The id of the player's team
40
     * @var int
41
     */
42
    protected $team;
43
44
    /**
45
     * The player's status
46
     * @var string
47
     */
48
    protected $status;
49
50
    /**
51
     * The player's e-mail address
52
     * @var string
53
     */
54
    protected $email;
55
56
    /**
57
     * Whether the player has verified their e-mail address
58
     * @var bool
59
     */
60
    protected $verified;
61
62
    /**
63
     * What kind of events the player should be e-mailed about
64
     * @var string
65
     */
66
    protected $receives;
67
68
    /**
69
     * A confirmation code for the player's e-mail address verification
70
     * @var string
71
     */
72
    protected $confirmCode;
73
74
    /**
75
     * Whether the callsign of the player is outdated
76
     * @var bool
77
     */
78
    protected $outdated;
79
80
    /**
81
     * The player's profile description
82
     * @var string
83
     */
84
    protected $description;
85
86
    /**
87
     * The id of the player's country
88
     * @var int
89
     */
90
    protected $country;
91
92
    /**
93
     * The player's timezone PHP identifier, e.g. "Europe/Paris"
94
     * @var string
95
     */
96
    protected $timezone;
97
98
    /**
99
     * The date the player joined the site
100
     * @var TimeDate
101
     */
102
    protected $joined;
103
104
    /**
105
     * The date of the player's last login
106
     * @var TimeDate
107
     */
108
    protected $last_login;
109
110
    /**
111
     * The roles a player belongs to
112
     * @var Role[]
113
     */
114
    protected $roles;
115
116
    /**
117
     * The permissions a player has
118
     * @var Permission[]
119
     */
120
    protected $permissions;
121
122
    /**
123
     * A section for admins to write notes about players
124
     * @var string
125
     */
126
    protected $admin_notes;
127
128
    /**
129
     * The ban of the player, or null if the player is not banned
130
     * @var Ban|null
131
     */
132
    protected $player;
133
134
    /**
135
     * The name of the database table used for queries
136
     */
137
    const TABLE = "players";
138
139
    /**
140
     * The location where avatars will be stored
141
     */
142
    const AVATAR_LOCATION = "/web/assets/imgs/avatars/players/";
143
144
    const EDIT_PERMISSION = Permission::EDIT_USER;
145
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_USER;
146
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_USER;
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 39
    protected function assignResult($player)
152
    {
153 39
        $this->bzid = $player['bzid'];
154 39
        $this->name = $player['username'];
155 39
        $this->alias = $player['alias'];
156 39
        $this->team = $player['team'];
157 39
        $this->status = $player['status'];
158 39
        $this->avatar = $player['avatar'];
159 39
        $this->country = $player['country'];
160 39
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 39
    protected function assignLazyResult($player)
166
    {
167 39
        $this->email = $player['email'];
168 39
        $this->verified = $player['verified'];
169 39
        $this->receives = $player['receives'];
170 39
        $this->confirmCode = $player['confirm_code'];
171 39
        $this->outdated = $player['outdated'];
172 39
        $this->description = $player['description'];
173 39
        $this->timezone = $player['timezone'];
174 39
        $this->joined = TimeDate::fromMysql($player['joined']);
175 39
        $this->last_login = TimeDate::fromMysql($player['last_login']);
176 39
        $this->admin_notes = $player['admin_notes'];
177 39
        $this->ban = Ban::getBan($this->id);
0 ignored issues
show
Bug introduced by
The property ban does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
178
179 39
        $this->updateUserPermissions();
180 39
    }
181
182
    /**
183
     * Add a player a new role
184
     *
185
     * @param Role|int $role_id The role ID to add a player to
186
     *
187
     * @return bool Whether the operation was successful or not
188
     */
189 39
    public function addRole($role_id)
190
    {
191 39
        if ($role_id instanceof Role) {
192 1
            $role_id = $role_id->getId();
193
        }
194
195 39
        $this->lazyLoad();
196
197
        // Make sure the player doesn't already have the role
198 39
        foreach ($this->roles as $playerRole) {
199 14
            if ($playerRole->getId() == $role_id) {
200 14
                return false;
201
            }
202
        }
203
204 39
        $status = $this->modifyRole($role_id, "add");
205 39
        $this->refresh();
206
207 39
        return $status;
208
    }
209
210
    /**
211
     * Get the notes admins have left about a player
212
     * @return string The notes
213
     */
214
    public function getAdminNotes()
215
    {
216
        $this->lazyLoad();
217
218
        return $this->admin_notes;
219
    }
220
221
    /**
222
     * Get the player's BZID
223
     * @return int The BZID
224
     */
225
    public function getBZID()
226
    {
227
        return $this->bzid;
228
    }
229
230
    /**
231
     * Get the country a player belongs to
232
     *
233
     * @return Country The country belongs to
234
     */
235 1
    public function getCountry()
236
    {
237 1
        return Country::get($this->country);
238
    }
239
240
    /**
241
     * Get the e-mail address of the player
242
     *
243
     * @return string The address
244
     */
245
    public function getEmailAddress()
246
    {
247
        $this->lazyLoad();
248
249
        return $this->email;
250
    }
251
252
    /**
253
     * Returns whether the player has verified their e-mail address
254
     *
255
     * @return bool `true` for verified players
256
     */
257
    public function isVerified()
258
    {
259
        $this->lazyLoad();
260
261
        return $this->verified;
262
    }
263
264
    /**
265
     * Returns the confirmation code for the player's e-mail address verification
266
     *
267
     * @return string The player's confirmation code
268
     */
269
    public function getConfirmCode()
270
    {
271
        $this->lazyLoad();
272
273
        return $this->confirmCode;
274
    }
275
276
    /**
277
     * Returns what kind of events the player should be e-mailed about
278
     *
279
     * @return string The type of notifications
280
     */
281
    public function getReceives()
282
    {
283
        $this->lazyLoad();
284
285
        return $this->receives;
286
    }
287
288
    /**
289
     * Finds out whether the specified player wants and can receive an e-mail
290
     * message
291
     *
292
     * @param  string  $type
293
     * @return bool `true` if the player should be sent an e-mail
294
     */
295 1
    public function canReceive($type)
296
    {
297 1
        $this->lazyLoad();
298
299 1
        if (!$this->email || !$this->isVerified()) {
300
            // Unverified e-mail means the user will receive nothing
301 1
            return false;
302
        }
303
304
        if ($this->receives == 'everything') {
305
            return true;
306
        }
307
308
        return $this->receives == $type;
309
    }
310
311
    /**
312
     * Find out whether the specified confirmation code is correct
313
     *
314
     * This method protects against timing attacks
315
     *
316
     * @return bool `true` for a correct e-mail verification code
317
     */
318
    public function isCorrectConfirmCode($code)
319
    {
320
        $this->lazyLoad();
321
322
        if ($this->confirmCode === null) {
323
            return false;
324
        }
325
326
        return StringUtils::equals($code, $this->confirmCode);
327
    }
328
329
    /**
330
     * Get the player's sanitized description
331
     * @return string The description
332
     */
333
    public function getDescription()
334
    {
335
        $this->lazyLoad();
336
337
        return $this->description;
338
    }
339
340
    /**
341
     * Get the joined date of the player
342
     * @return TimeDate The joined date of the player
343
     */
344
    public function getJoinedDate()
345
    {
346
        $this->lazyLoad();
347
348
        return $this->joined->copy();
349
    }
350
351
    /**
352
     * Get all of the known IPs used by the player
353
     *
354
     * @return string[][] An array containing IPs and hosts
355
     */
356
    public function getKnownIPs()
357
    {
358
        return $this->db->query("SELECT DISTINCT ip, host FROM visits WHERE player = ? LIMIT 10", "i", array($this->getId()));
359
    }
360
361
    /**
362
     * Get the last login for a player
363
     * @return TimeDate The date of the last login
364
     */
365
    public function getLastLogin()
366
    {
367
        $this->lazyLoad();
368
369
        return $this->last_login->copy();
370
    }
371
372
    /**
373
     * Get all of the callsigns a player has used to log in to the website
374
     * @return string[] An array containing all of the past callsigns recorded for a player
375
     */
376
    public function getPastCallsigns()
377
    {
378
        return parent::fetchIds("WHERE player = ?", "i", array($this->id), "past_callsigns", "username");
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (fetchIds() instead of getPastCallsigns()). Are you sure this is correct? If so, you might want to change this to $this->fetchIds().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
379
    }
380
381
    /**
382
     * Get the player's team
383
     * @return Team The object representing the team
384
     */
385 2
    public function getTeam()
386
    {
387 2
        return Team::get($this->team);
388
    }
389
390
    /**
391
     * Get the player's timezone PHP identifier (example: "Europe/Paris")
392
     * @return string The timezone
393
     */
394 1
    public function getTimezone()
395
    {
396 1
        $this->lazyLoad();
397
398 1
        return ($this->timezone) ?: date_default_timezone_get();
399
    }
400
401
    /**
402
     * Rebuild the list of permissions a user has been granted
403
     */
404 39
    private function updateUserPermissions()
405
    {
406 39
        $this->roles = Role::getRoles($this->id);
407 39
        $this->permissions = array();
408
409 39
        foreach ($this->roles as $role) {
410 39
            $this->permissions = array_merge($this->permissions, $role->getPerms());
0 ignored issues
show
Documentation Bug introduced by
It seems like array_merge($this->permi...ons, $role->getPerms()) of type array is incompatible with the declared type array<integer,object<Permission>> of property $permissions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
411
        }
412 39
    }
413
414
    /**
415
     * Check if a player has a specific permission
416
     *
417
     * @param string|null $permission The permission to check for
418
     *
419
     * @return bool Whether or not the player has the permission
420
     */
421 2
    public function hasPermission($permission)
422
    {
423 2
        if ($permission === null) {
424 1
            return false;
425
        }
426
427 2
        $this->lazyLoad();
428
429 2
        return isset($this->permissions[$permission]);
430
    }
431
432
    /**
433
     * Check whether the callsign of the player is outdated
434
     *
435
     * Returns true if this player has probably changed their callsign, making
436
     * the current username stored in the database obsolete
437
     *
438
     * @return bool Whether or not the player is disabled
439
     */
440
    public function isOutdated()
441
    {
442
        $this->lazyLoad();
443
444
        return $this->outdated;
445
    }
446
447
    /**
448
     * Check if a player's account has been disabled
449
     *
450
     * @return bool Whether or not the player is disabled
451
     */
452
    public function isDisabled()
453
    {
454
        return $this->status == "disabled";
455
    }
456
457
    /**
458
     * Check if everyone can log in as this user on a test environment
459
     *
460
     * @return bool
461
     */
462 1
    public function isTestUser()
463
    {
464 1
        return $this->status == "test";
465
    }
466
467
    /**
468
     * Check if a player is teamless
469
     *
470
     * @return bool True if the player is teamless
471
     */
472 18
    public function isTeamless()
473
    {
474 18
        return empty($this->team);
475
    }
476
477
    /**
478
     * Mark a player's account as banned
479
     */
480 1
    public function markAsBanned()
481
    {
482 1
        if ($this->status != 'active') {
483
            return $this;
484
        }
485
486 1
        return $this->updateProperty($this->status, "status", "banned", 's');
487
    }
488
489
    /**
490
     * Mark a player's account as unbanned
491
     */
492
    public function markAsUnbanned()
493
    {
494
        if ($this->status != 'banned') {
495
            return $this;
496
        }
497
498
        return $this->updateProperty($this->status, "status", "active", 's');
499
    }
500
501
    /**
502
     * Find out if a player is banned
503
     *
504
     * @return bool
505
     */
506 2
    public function isBanned()
507
    {
508 2
        return Ban::getBan($this->id) !== null;
509
    }
510
511
    /**
512
     * Get the ban of the player
513
     *
514
     * This method performs a load of all the lazy parameters of the Player
515
     *
516
     * @return Ban|null The current ban of the player, or null if the player is
517
     *                  is not banned
518
     */
519
    public function getBan()
520
    {
521
        $this->lazyLoad();
522
523
        return $this->ban;
524
    }
525
526
    /**
527
     * Remove a player from a role
528
     *
529
     * @param int $role_id The role ID to add or remove
530
     *
531
     * @return bool Whether the operation was successful or not
532
     */
533
    public function removeRole($role_id)
534
    {
535
        $status = $this->modifyRole($role_id, "remove");
536
        $this->refresh();
537
538
        return $status;
539
    }
540
541
    /**
542
     * Set the player's email address and reset their verification status
543
     * @param string $email The address
544
     */
545
    public function setEmailAddress($email)
546
    {
547
        $this->lazyLoad();
548
549
        if ($this->email == $email) {
550
            // The e-mail hasn't changed, don't do anything
551
            return;
552
        }
553
554
        $this->setVerified(false);
555
        $this->generateNewConfirmCode();
556
557
        $this->email = $email;
558
        $this->update("email", $email, 's');
559
    }
560
561
    /**
562
     * Set whether the player has verified their e-mail address
563
     *
564
     * @param  bool $verified Whether the player is verified or not
565
     * @return self
566
     */
567
    public function setVerified($verified)
568
    {
569
        $this->lazyLoad();
570
571
        if ($verified) {
572
            $this->setConfirmCode(null);
573
        }
574
575
        return $this->updateProperty($this->verified, 'verified', $verified, 'i');
576
    }
577
578
    /**
579
     * Generate a new random confirmation token for e-mail address verification
580
     *
581
     * @return self
582
     */
583
    public function generateNewConfirmCode()
584
    {
585
        $generator = new SecureRandom();
0 ignored issues
show
Deprecated Code introduced by
The class Symfony\Component\Security\Core\Util\SecureRandom has been deprecated with message: since version 2.8, to be removed in 3.0. Use the random_bytes function instead

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
586
        $random = $generator->nextBytes(16);
587
588
        return $this->setConfirmCode(bin2hex($random));
589
    }
590
591
    /**
592
     * Set the confirmation token for e-mail address verification
593
     *
594
     * @param  string $code The confirmation code
595
     * @return self
596
     */
597
    private function setConfirmCode($code)
598
    {
599
        $this->lazyLoad();
600
601
        return $this->updateProperty($this->confirmCode, 'confirm_code', $code, 's');
602
    }
603
604
    /**
605
     * Set what kind of events the player should be e-mailed about
606
     *
607
     * @param  string $receives The type of notification
608
     * @return self
609
     */
610
    public function setReceives($receives)
611
    {
612
        $this->lazyLoad();
613
614
        return $this->updateProperty($this->receives, 'receives', $receives, 's');
615
    }
616
617
    /**
618
     * Set whether the callsign of the player is outdated
619
     *
620
     * @param  bool $outdated Whether the callsign is outdated
621
     * @return self
622
     */
623 39
    public function setOutdated($outdated)
624
    {
625 39
        $this->lazyLoad();
626
627 39
        return $this->updateProperty($this->outdated, 'outdated', $outdated, 'i');
628
    }
629
630
    /**
631
     * Set the player's description
632
     * @param string $description The description
633
     */
634
    public function setDescription($description)
635
    {
636
        $this->description = $description;
637
        $this->update("description", $description, 's');
638
    }
639
640
    /**
641
     * Set the player's timezone
642
     * @param string $timezone The timezone
643
     */
644
    public function setTimezone($timezone)
645
    {
646
        $this->timezone = $timezone;
647
        $this->update("timezone", $timezone, 's');
648
    }
649
650
    /**
651
     * Set the player's team
652
     * @param int $team The team's ID
653
     */
654 18
    public function setTeam($team)
655
    {
656 18
        $this->team = $team;
657 18
        $this->update("team", $team, 'i');
658 18
    }
659
660
    /**
661
     * Set the player's status
662
     * @param string $status The new status
663
     */
664
    public function setStatus($status)
665
    {
666
        $this->updateProperty($this->status, 'status', $status, 's');
667
    }
668
669
    /**
670
     * Set the player's admin notes
671
     * @param  string $admin_notes The new admin notes
672
     * @return self
673
     */
674
    public function setAdminNotes($admin_notes)
675
    {
676
        return $this->updateProperty($this->admin_notes, 'admin_notes', $admin_notes, 's');
677
    }
678
679
    /**
680
     * Set the player's country
681
     * @param  int   $country The ID of the new country
682
     * @return self
683
     */
684
    public function setCountry($country)
685
    {
686
        return $this->updateProperty($this->country, 'country', $country, 'i');
687
    }
688
689
    /**
690
     * Updates this player's last login
691
     */
692
    public function updateLastLogin()
693
    {
694
        $this->update("last_login", TimeDate::now()->toMysql(), 's');
695
    }
696
697
    /**
698
     * Get the player's username
699
     * @return string The username
700
     */
701 1
    public function getUsername()
702
    {
703 1
        return $this->name;
704
    }
705
706
    /**
707
     * Get the player's username, safe for use in your HTML
708
     * @return string The username
709
     */
710 1
    public function getEscapedUsername()
711
    {
712 1
        return $this->getEscapedName();
713
    }
714
715
    /**
716
     * Alias for Player::setUsername()
717
     *
718
     * @param  string $username The new username
719
     * @return self
720
     */
721
    public function setName($username)
722
    {
723
        return $this->setUsername($username);
724
    }
725
726
    /**
727
     * Mark all the unread messages of a player as read
728
     *
729
     * @return void
730
     */
731
    public function markMessagesAsRead()
732
    {
733
        $this->db->query(
734
            "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `read` = 0",
735
            'i', array($this->id)
736
        );
737
    }
738
739
    /**
740
     * Set the roles of a user
741
     *
742
     * @todo   Is it worth making this faster?
743
     * @param  Role[] $roles The new roles of the user
744
     * @return self
745
     */
746
    public function setRoles($roles)
747
    {
748
        $this->lazyLoad();
749
750
        $oldRoles = Role::mapToIds($this->roles);
751
        $this->roles = $roles;
752
        $roleIds = Role::mapToIds($roles);
753
754
        $newRoles     = array_diff($roleIds, $oldRoles);
755
        $removedRoles = array_diff($oldRoles, $roleIds);
756
757
        foreach ($newRoles as $role) {
758
            $this->modifyRole($role, 'add');
759
        }
760
761
        foreach ($removedRoles as $role) {
762
            $this->modifyRole($role, 'remove');
763
        }
764
765
        $this->refresh();
766
767
        return $this;
768
    }
769
770
    /**
771
     * Give or remove a role to/form a player
772
     *
773
     * @param int    $role_id The role ID to add or remove
774
     * @param string $action  Whether to "add" or "remove" a role for a player
775
     *
776
     * @return bool Whether the operation was successful or not
777
     */
778 39
    private function modifyRole($role_id, $action)
779
    {
780 39
        $role = Role::get($role_id);
781
782 39
        if ($role->isValid()) {
783 39
            if ($action == "add") {
784 39
                $this->db->query("INSERT INTO player_roles (user_id, role_id) VALUES (?, ?)", "ii", array($this->getId(), $role_id));
785
            } elseif ($action == "remove") {
786
                $this->db->query("DELETE FROM player_roles WHERE user_id = ? AND role_id = ?", "ii", array($this->getId(), $role_id));
787
            } else {
788
                throw new Exception("Unrecognized role action");
789
            }
790
791 39
            return true;
792
        }
793
794
        return false;
795
    }
796
797
    /**
798
     * Given a player's BZID, get a player object
799
     *
800
     * @param  int    $bzid The player's BZID
801
     * @return Player
802
     */
803 1
    public static function getFromBZID($bzid)
804
    {
805 1
        return self::get(self::fetchIdFrom($bzid, "bzid", "s"));
806
    }
807
808
    /**
809
     * Get a single player by their username
810
     *
811
     * @param  string $username The username to look for
812
     * @return Player
813
     */
814 1
    public static function getFromUsername($username)
815
    {
816 1
        $player = static::get(self::fetchIdFrom($username, 'username', 's'));
817
818 1
        return $player->inject('name', $username);
819
    }
820
821
    /**
822
     * Get all the players in the database that have an active status
823
     * @return Player[] An array of player BZIDs
824
     */
825
    public static function getPlayers()
826
    {
827
        return self::arrayIdToModel(
828
            parent::fetchIdsFrom("status", array("active", "test"), "s", false)
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (fetchIdsFrom() instead of getPlayers()). Are you sure this is correct? If so, you might want to change this to $this->fetchIdsFrom().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
829
        );
830
    }
831
832
    /**
833
     * Show the number of notifications the user hasn't read yet
834
     * @return int
835
     */
836 1
    public function countUnreadNotifications()
837
    {
838 1
        return Notification::countUnreadNotifications($this->id);
839
    }
840
841
    /**
842
     * Show the number of messages the user hasn't read yet
843
     * @return int
844
     */
845 1
    public function countUnreadMessages()
846
    {
847 1
        return $this->fetchCount("WHERE `player` = ? AND `read` = 0",
848 1
            'i', $this->id, 'player_conversations'
0 ignored issues
show
Documentation introduced by
$this->id is of type integer, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
849
        );
850
    }
851
852
    /**
853
     * Get all of the members belonging to a team
854
     * @param  int      $teamID The ID of the team to fetch the members of
855
     * @return Player[] An array of Player objects of the team members
856
     */
857 2
    public static function getTeamMembers($teamID)
858
    {
859 2
        return self::arrayIdToModel(
860 2
            parent::fetchIds("WHERE team = ?", "i", array($teamID))
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (fetchIds() instead of getTeamMembers()). Are you sure this is correct? If so, you might want to change this to $this->fetchIds().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
861
        );
862
    }
863
864
    /**
865
     * {@inheritdoc}
866
     */
867 1
    public static function getActiveStatuses()
868
    {
869 1
        return array('active', 'reported', 'test');
870
    }
871
872
    /**
873
     * {@inheritdoc}
874
     */
875 39
    public static function getEagerColumns()
876
    {
877 39
        return 'id,bzid,team,username,alias,status,avatar,country';
878
    }
879
880
    /**
881
     * {@inheritdoc}
882
     */
883 39
    public static function getLazyColumns()
884
    {
885 39
        return 'email,verified,receives,confirm_code,outdated,description,timezone,joined,last_login,admin_notes';
886
    }
887
888
    /**
889
     * Get a query builder for players
890
     * @return QueryBuilder
891
     */
892 View Code Duplication
    public static function getQueryBuilder()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
893
    {
894
        return new QueryBuilder('Player', array(
895
            'columns' => array(
896
                'name'     => 'username',
897
                'team'     => 'team',
898
                'outdated' => 'outdated',
899
                'status'   => 'status'
900
            ),
901
            'name' => 'name',
902
        ));
903
    }
904
905
    /**
906
     * Enter a new player to the database
907
     * @param  int              $bzid        The player's bzid
908
     * @param  string           $username    The player's username
909
     * @param  int              $team        The player's team
910
     * @param  string           $status      The player's status
911
     * @param  int              $role_id     The player's role when they are first created
912
     * @param  string           $avatar      The player's profile avatar
913
     * @param  string           $description The player's profile description
914
     * @param  int              $country     The player's country
915
     * @param  string           $timezone    The player's timezone
916
     * @param  string|\TimeDate $joined      The date the player joined
917
     * @param  string|\TimeDate $last_login  The timestamp of the player's last login
918
     * @return Player           An object representing the player that was just entered
919
     */
920 39
    public static function newPlayer($bzid, $username, $team = null, $status = "active", $role_id = self::PLAYER, $avatar = "", $description = "", $country = 1, $timezone = null, $joined = "now", $last_login = "now")
921
    {
922 39
        $joined = TimeDate::from($joined);
923 39
        $last_login = TimeDate::from($last_login);
924 39
        $timezone = ($timezone) ?: date_default_timezone_get();
925
926 39
        $player = self::create(array(
927 39
            'bzid'        => $bzid,
928 39
            'team'        => $team,
929 39
            'username'    => $username,
930 39
            'alias'       => self::generateAlias($username),
931 39
            'status'      => $status,
932 39
            'avatar'      => $avatar,
933 39
            'description' => $description,
934 39
            'country'     => $country,
935 39
            'timezone'    => $timezone,
936 39
            'joined'      => $joined->toMysql(),
937 39
            'last_login'  => $last_login->toMysql(),
938 39
        ), 'iisssssisss');
939
940 39
        $player->addRole($role_id);
941 39
        $player->getIdenticon($player->getId());
942 39
        $player->setUsername($username);
943
944 39
        return $player;
945
    }
946
947
    /**
948
     * Determine if a player exists in the database
949
     * @param  int  $bzid The player's bzid
950
     * @return bool Whether the player exists in the database
951
     */
952
    public static function playerBZIDExists($bzid)
953
    {
954
        return self::getFromBZID($bzid)->isValid();
955
    }
956
957
    /**
958
     * Change a player's callsign and add it to the database if it does not
959
     * exist as a past callsign
960
     *
961
     * @param  string $username The new username of the player
962
     * @return self
963
     */
964 39
    public function setUsername($username)
965
    {
966
        // The player's username was just fetched from BzDB, it's definitely not
967
        // outdated
968 39
        $this->setOutdated(false);
969
970
        // Players who have this player's username are considered outdated
971 39
        $this->db->query("UPDATE {$this->table} SET outdated = 1 WHERE username = ? AND id != ?", "si", array($username, $this->id));
972
973 39
        if ($username === $this->name) {
974
            // The player's username hasn't changed, no need to do anything
975 1
            return $this;
976
        }
977
978
        // Players who used to have our player's username are not outdated anymore,
979
        // unless they are more than one.
980
        // Even though we are sure that the old and new usernames are not equal,
981
        // MySQL makes a different type of string equality tests, which is why we
982
        // also check IDs to make sure not to affect our own player's outdatedness.
983 38
        $this->db->query("
984 38
            UPDATE {$this->table} SET outdated =
985 38
                (SELECT (COUNT(*)>1) FROM (SELECT 1 FROM {$this->table} WHERE username = ? AND id != ?) t)
986 38
            WHERE username = ? AND id != ?",
987 38
            "sisi", array($this->name, $this->id, $this->name, $this->id));
988
989 38
        $this->updateProperty($this->name, 'username', $username, 's');
990 38
        $this->db->query("INSERT IGNORE INTO past_callsigns (player, username) VALUES (?, ?)", "is", array($this->id, $username));
991 38
        $this->resetAlias();
992
993 38
        return $this;
994
    }
995
996
    /**
997
     * Alphabetical order function for use in usort (case-insensitive)
998
     * @return Closure The sort function
999
     */
1000
    public static function getAlphabeticalSort()
1001
    {
1002 1
        return function (Player $a, Player $b) {
1003 1
            return strcasecmp($a->getUsername(), $b->getUsername());
1004 1
        };
1005
    }
1006
1007
    /**
1008
     * {@inheritdoc}
1009
     * @todo Add a constraint that does this automatically
1010
     */
1011 39
    public function wipe()
1012
    {
1013 39
        $this->db->query("DELETE FROM past_callsigns WHERE player = ?", "i", $this->id);
1014
1015 39
        parent::wipe();
1016 39
    }
1017
1018
    /**
1019
     * Find whether the player can delete a model
1020
     *
1021
     * @param  PermissionModel $model       The model that will be seen
1022
     * @param  bool         $showDeleted Whether to show deleted models to admins
1023
     * @return bool
1024
     */
1025 1
    public function canSee($model, $showDeleted = false)
1026
    {
1027 1
        return $model->canBeSeenBy($this, $showDeleted);
1028
    }
1029
1030
    /**
1031
     * Find whether the player can delete a model
1032
     *
1033
     * @param  PermissionModel $model The model that will be deleted
1034
     * @param  bool         $hard  Whether to check for hard-delete perms, as opposed
1035
     *                                to soft-delete ones
1036
     * @return bool
1037
     */
1038 1
    public function canDelete($model, $hard = false)
1039
    {
1040 1
        if ($hard) {
1041
            return $model->canBeHardDeletedBy($this);
1042
        } else {
1043 1
            return $model->canBeSoftDeletedBy($this);
1044
        }
1045
    }
1046
1047
    /**
1048
     * Find whether the player can create a model
1049
     *
1050
     * @param  string  $modelName The PHP class identifier of the model type
1051
     * @return bool
1052
     */
1053 1
    public function canCreate($modelName)
1054
    {
1055 1
        return $modelName::canBeCreatedBy($this);
1056
    }
1057
1058
    /**
1059
     * Find whether the player can edit a model
1060
     *
1061
     * @param  PermissionModel $model The model which will be edited
1062
     * @return bool
1063
     */
1064 1
    public function canEdit($model)
1065
    {
1066 1
        return $model->canBeEditedBy($this);
1067
    }
1068
}
1069