Completed
Push — fm-support ( 3b5d1b...2f8546 )
by Konstantinos
12:56
created

Player::getUsername()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 cached match count for a player
136
     *
137
     * @var int
138
     */
139
    private $cachedMatchCount = null;
140
141
    /**
142
     * The name of the database table used for queries
143
     */
144
    const TABLE = "players";
145
146
    /**
147
     * The location where avatars will be stored
148
     */
149
    const AVATAR_LOCATION = "/web/assets/imgs/avatars/players/";
150
151
    const EDIT_PERMISSION = Permission::EDIT_USER;
152
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_USER;
153
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_USER;
154
155
    /**
156
     * {@inheritdoc}
157
     */
158 39
    protected function assignResult($player)
159
    {
160 39
        $this->bzid = $player['bzid'];
161 39
        $this->name = $player['username'];
162 39
        $this->alias = $player['alias'];
163 39
        $this->team = $player['team'];
164 39
        $this->status = $player['status'];
165 39
        $this->avatar = $player['avatar'];
166 39
        $this->country = $player['country'];
167 39
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172 39
    protected function assignLazyResult($player)
173
    {
174 39
        $this->email = $player['email'];
175 39
        $this->verified = $player['verified'];
176 39
        $this->receives = $player['receives'];
177 39
        $this->confirmCode = $player['confirm_code'];
178 39
        $this->outdated = $player['outdated'];
179 39
        $this->description = $player['description'];
180 39
        $this->timezone = $player['timezone'];
181 39
        $this->joined = TimeDate::fromMysql($player['joined']);
182 39
        $this->last_login = TimeDate::fromMysql($player['last_login']);
183 39
        $this->admin_notes = $player['admin_notes'];
184 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...
185
186 39
        $this->updateUserPermissions();
187 39
    }
188
189
    /**
190
     * Add a player a new role
191
     *
192
     * @param Role|int $role_id The role ID to add a player to
193
     *
194
     * @return bool Whether the operation was successful or not
195
     */
196 39
    public function addRole($role_id)
197
    {
198 39
        if ($role_id instanceof Role) {
199 1
            $role_id = $role_id->getId();
200
        }
201
202 39
        $this->lazyLoad();
203
204
        // Make sure the player doesn't already have the role
205 39
        foreach ($this->roles as $playerRole) {
206 14
            if ($playerRole->getId() == $role_id) {
207 14
                return false;
208
            }
209
        }
210
211 39
        $status = $this->modifyRole($role_id, "add");
212 39
        $this->refresh();
213
214 39
        return $status;
215
    }
216
217
    /**
218
     * Get the notes admins have left about a player
219
     * @return string The notes
220
     */
221
    public function getAdminNotes()
222
    {
223
        $this->lazyLoad();
224
225
        return $this->admin_notes;
226
    }
227
228
    /**
229
     * Get the player's BZID
230
     * @return int The BZID
231
     */
232
    public function getBZID()
233
    {
234
        return $this->bzid;
235
    }
236
237
    /**
238
     * Get the country a player belongs to
239
     *
240
     * @return Country The country belongs to
241
     */
242 1
    public function getCountry()
243
    {
244 1
        return Country::get($this->country);
245
    }
246
247
    /**
248
     * Get the e-mail address of the player
249
     *
250
     * @return string The address
251
     */
252
    public function getEmailAddress()
253
    {
254
        $this->lazyLoad();
255
256
        return $this->email;
257
    }
258
259
    /**
260
     * Returns whether the player has verified their e-mail address
261
     *
262
     * @return bool `true` for verified players
263
     */
264
    public function isVerified()
265
    {
266
        $this->lazyLoad();
267
268
        return $this->verified;
269
    }
270
271
    /**
272
     * Returns the confirmation code for the player's e-mail address verification
273
     *
274
     * @return string The player's confirmation code
275
     */
276
    public function getConfirmCode()
277
    {
278
        $this->lazyLoad();
279
280
        return $this->confirmCode;
281
    }
282
283
    /**
284
     * Returns what kind of events the player should be e-mailed about
285
     *
286
     * @return string The type of notifications
287
     */
288
    public function getReceives()
289
    {
290
        $this->lazyLoad();
291
292
        return $this->receives;
293
    }
294
295
    /**
296
     * Finds out whether the specified player wants and can receive an e-mail
297
     * message
298
     *
299
     * @param  string  $type
300
     * @return bool `true` if the player should be sent an e-mail
301
     */
302 1
    public function canReceive($type)
303
    {
304 1
        $this->lazyLoad();
305
306 1
        if (!$this->email || !$this->isVerified()) {
307
            // Unverified e-mail means the user will receive nothing
308 1
            return false;
309
        }
310
311
        if ($this->receives == 'everything') {
312
            return true;
313
        }
314
315
        return $this->receives == $type;
316
    }
317
318
    /**
319
     * Find out whether the specified confirmation code is correct
320
     *
321
     * This method protects against timing attacks
322
     *
323
     * @return bool `true` for a correct e-mail verification code
324
     */
325
    public function isCorrectConfirmCode($code)
326
    {
327
        $this->lazyLoad();
328
329
        if ($this->confirmCode === null) {
330
            return false;
331
        }
332
333
        return StringUtils::equals($code, $this->confirmCode);
334
    }
335
336
    /**
337
     * Get the player's sanitized description
338
     * @return string The description
339
     */
340
    public function getDescription()
341
    {
342
        $this->lazyLoad();
343
344
        return $this->description;
345
    }
346
347
    /**
348
     * Get the joined date of the player
349
     * @return TimeDate The joined date of the player
350
     */
351
    public function getJoinedDate()
352
    {
353
        $this->lazyLoad();
354
355
        return $this->joined->copy();
356
    }
357
358
    /**
359
     * Get all of the known IPs used by the player
360
     *
361
     * @return string[][] An array containing IPs and hosts
362
     */
363
    public function getKnownIPs()
364
    {
365
        return $this->db->query("SELECT DISTINCT ip, host FROM visits WHERE player = ? LIMIT 10", "i", array($this->getId()));
366
    }
367
368
    /**
369
     * Get the last login for a player
370
     * @return TimeDate The date of the last login
371
     */
372
    public function getLastLogin()
373
    {
374
        $this->lazyLoad();
375
376
        return $this->last_login->copy();
377
    }
378
379
    /**
380
     * Get all of the callsigns a player has used to log in to the website
381
     * @return string[] An array containing all of the past callsigns recorded for a player
382
     */
383
    public function getPastCallsigns()
384
    {
385
        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...
386
    }
387
388
    /**
389
     * Get the player's team
390
     * @return Team The object representing the team
391
     */
392 2
    public function getTeam()
393
    {
394 2
        return Team::get($this->team);
395
    }
396
397
    /**
398
     * Get the player's timezone PHP identifier (example: "Europe/Paris")
399
     * @return string The timezone
400
     */
401 1
    public function getTimezone()
402
    {
403 1
        $this->lazyLoad();
404
405 1
        return ($this->timezone) ?: date_default_timezone_get();
406
    }
407
408
    /**
409
     * Rebuild the list of permissions a user has been granted
410
     */
411 39
    private function updateUserPermissions()
412
    {
413 39
        $this->roles = Role::getRoles($this->id);
414 39
        $this->permissions = array();
415
416 39
        foreach ($this->roles as $role) {
417 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...
418
        }
419 39
    }
420
421
    /**
422
     * Check if a player has a specific permission
423
     *
424
     * @param string|null $permission The permission to check for
425
     *
426
     * @return bool Whether or not the player has the permission
427
     */
428 2
    public function hasPermission($permission)
429
    {
430 2
        if ($permission === null) {
431 1
            return false;
432
        }
433
434 2
        $this->lazyLoad();
435
436 2
        return isset($this->permissions[$permission]);
437
    }
438
439
    /**
440
     * Check whether the callsign of the player is outdated
441
     *
442
     * Returns true if this player has probably changed their callsign, making
443
     * the current username stored in the database obsolete
444
     *
445
     * @return bool Whether or not the player is disabled
446
     */
447
    public function isOutdated()
448
    {
449
        $this->lazyLoad();
450
451
        return $this->outdated;
452
    }
453
454
    /**
455
     * Check if a player's account has been disabled
456
     *
457
     * @return bool Whether or not the player is disabled
458
     */
459
    public function isDisabled()
460
    {
461
        return $this->status == "disabled";
462
    }
463
464
    /**
465
     * Check if everyone can log in as this user on a test environment
466
     *
467
     * @return bool
468
     */
469 1
    public function isTestUser()
470
    {
471 1
        return $this->status == "test";
472
    }
473
474
    /**
475
     * Check if a player is teamless
476
     *
477
     * @return bool True if the player is teamless
478
     */
479 18
    public function isTeamless()
480
    {
481 18
        return empty($this->team);
482
    }
483
484
    /**
485
     * Mark a player's account as banned
486
     */
487 1
    public function markAsBanned()
488
    {
489 1
        if ($this->status != 'active') {
490
            return $this;
491
        }
492
493 1
        return $this->updateProperty($this->status, "status", "banned", 's');
494
    }
495
496
    /**
497
     * Mark a player's account as unbanned
498
     */
499
    public function markAsUnbanned()
500
    {
501
        if ($this->status != 'banned') {
502
            return $this;
503
        }
504
505
        return $this->updateProperty($this->status, "status", "active", 's');
506
    }
507
508
    /**
509
     * Find out if a player is banned
510
     *
511
     * @return bool
512
     */
513 2
    public function isBanned()
514
    {
515 2
        return Ban::getBan($this->id) !== null;
516
    }
517
518
    /**
519
     * Get the ban of the player
520
     *
521
     * This method performs a load of all the lazy parameters of the Player
522
     *
523
     * @return Ban|null The current ban of the player, or null if the player is
524
     *                  is not banned
525
     */
526
    public function getBan()
527
    {
528
        $this->lazyLoad();
529
530
        return $this->ban;
531
    }
532
533
    /**
534
     * Remove a player from a role
535
     *
536
     * @param int $role_id The role ID to add or remove
537
     *
538
     * @return bool Whether the operation was successful or not
539
     */
540
    public function removeRole($role_id)
541
    {
542
        $status = $this->modifyRole($role_id, "remove");
543
        $this->refresh();
544
545
        return $status;
546
    }
547
548
    /**
549
     * Set the player's email address and reset their verification status
550
     * @param string $email The address
551
     */
552
    public function setEmailAddress($email)
553
    {
554
        $this->lazyLoad();
555
556
        if ($this->email == $email) {
557
            // The e-mail hasn't changed, don't do anything
558
            return;
559
        }
560
561
        $this->setVerified(false);
562
        $this->generateNewConfirmCode();
563
564
        $this->email = $email;
565
        $this->update("email", $email, 's');
566
    }
567
568
    /**
569
     * Set whether the player has verified their e-mail address
570
     *
571
     * @param  bool $verified Whether the player is verified or not
572
     * @return self
573
     */
574
    public function setVerified($verified)
575
    {
576
        $this->lazyLoad();
577
578
        if ($verified) {
579
            $this->setConfirmCode(null);
580
        }
581
582
        return $this->updateProperty($this->verified, 'verified', $verified, 'i');
583
    }
584
585
    /**
586
     * Generate a new random confirmation token for e-mail address verification
587
     *
588
     * @return self
589
     */
590
    public function generateNewConfirmCode()
591
    {
592
        $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...
593
        $random = $generator->nextBytes(16);
594
595
        return $this->setConfirmCode(bin2hex($random));
596
    }
597
598
    /**
599
     * Set the confirmation token for e-mail address verification
600
     *
601
     * @param  string $code The confirmation code
602
     * @return self
603
     */
604
    private function setConfirmCode($code)
605
    {
606
        $this->lazyLoad();
607
608
        return $this->updateProperty($this->confirmCode, 'confirm_code', $code, 's');
609
    }
610
611
    /**
612
     * Set what kind of events the player should be e-mailed about
613
     *
614
     * @param  string $receives The type of notification
615
     * @return self
616
     */
617
    public function setReceives($receives)
618
    {
619
        $this->lazyLoad();
620
621
        return $this->updateProperty($this->receives, 'receives', $receives, 's');
622
    }
623
624
    /**
625
     * Set whether the callsign of the player is outdated
626
     *
627
     * @param  bool $outdated Whether the callsign is outdated
628
     * @return self
629
     */
630 39
    public function setOutdated($outdated)
631
    {
632 39
        $this->lazyLoad();
633
634 39
        return $this->updateProperty($this->outdated, 'outdated', $outdated, 'i');
635
    }
636
637
    /**
638
     * Set the player's description
639
     * @param string $description The description
640
     */
641
    public function setDescription($description)
642
    {
643
        $this->description = $description;
644
        $this->update("description", $description, 's');
645
    }
646
647
    /**
648
     * Set the player's timezone
649
     * @param string $timezone The timezone
650
     */
651
    public function setTimezone($timezone)
652
    {
653
        $this->timezone = $timezone;
654
        $this->update("timezone", $timezone, 's');
655
    }
656
657
    /**
658
     * Set the player's team
659
     * @param int $team The team's ID
660
     */
661 18
    public function setTeam($team)
662
    {
663 18
        $this->team = $team;
664 18
        $this->update("team", $team, 'i');
665 18
    }
666
667
    /**
668
     * Set the player's status
669
     * @param string $status The new status
670
     */
671
    public function setStatus($status)
672
    {
673
        $this->updateProperty($this->status, 'status', $status, 's');
674
    }
675
676
    /**
677
     * Set the player's admin notes
678
     * @param  string $admin_notes The new admin notes
679
     * @return self
680
     */
681
    public function setAdminNotes($admin_notes)
682
    {
683
        return $this->updateProperty($this->admin_notes, 'admin_notes', $admin_notes, 's');
684
    }
685
686
    /**
687
     * Set the player's country
688
     * @param  int   $country The ID of the new country
689
     * @return self
690
     */
691
    public function setCountry($country)
692
    {
693
        return $this->updateProperty($this->country, 'country', $country, 'i');
694
    }
695
696
    /**
697
     * Updates this player's last login
698
     */
699
    public function updateLastLogin()
700
    {
701
        $this->update("last_login", TimeDate::now()->toMysql(), 's');
702
    }
703
704
    /**
705
     * Get the player's username
706
     * @return string The username
707
     */
708 1
    public function getUsername()
709
    {
710 1
        return $this->name;
711
    }
712
713
    /**
714
     * Get the player's username, safe for use in your HTML
715
     * @return string The username
716
     */
717 1
    public function getEscapedUsername()
718
    {
719 1
        return $this->getEscapedName();
720
    }
721
722
    /**
723
     * Alias for Player::setUsername()
724
     *
725
     * @param  string $username The new username
726
     * @return self
727
     */
728
    public function setName($username)
729
    {
730
        return $this->setUsername($username);
731
    }
732
733
    /**
734
     * Mark all the unread messages of a player as read
735
     *
736
     * @return void
737
     */
738
    public function markMessagesAsRead()
739
    {
740
        $this->db->query(
741
            "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `read` = 0",
742
            'i', array($this->id)
743
        );
744
    }
745
746
    /**
747
     * Set the roles of a user
748
     *
749
     * @todo   Is it worth making this faster?
750
     * @param  Role[] $roles The new roles of the user
751
     * @return self
752
     */
753
    public function setRoles($roles)
754
    {
755
        $this->lazyLoad();
756
757
        $oldRoles = Role::mapToIds($this->roles);
758
        $this->roles = $roles;
759
        $roleIds = Role::mapToIds($roles);
760
761
        $newRoles     = array_diff($roleIds, $oldRoles);
762
        $removedRoles = array_diff($oldRoles, $roleIds);
763
764
        foreach ($newRoles as $role) {
765
            $this->modifyRole($role, 'add');
766
        }
767
768
        foreach ($removedRoles as $role) {
769
            $this->modifyRole($role, 'remove');
770
        }
771
772
        $this->refresh();
773
774
        return $this;
775
    }
776
777
    /**
778
     * Give or remove a role to/form a player
779
     *
780
     * @param int    $role_id The role ID to add or remove
781
     * @param string $action  Whether to "add" or "remove" a role for a player
782
     *
783
     * @return bool Whether the operation was successful or not
784
     */
785 39
    private function modifyRole($role_id, $action)
786
    {
787 39
        $role = Role::get($role_id);
788
789 39
        if ($role->isValid()) {
790 39
            if ($action == "add") {
791 39
                $this->db->query("INSERT INTO player_roles (user_id, role_id) VALUES (?, ?)", "ii", array($this->getId(), $role_id));
792
            } elseif ($action == "remove") {
793
                $this->db->query("DELETE FROM player_roles WHERE user_id = ? AND role_id = ?", "ii", array($this->getId(), $role_id));
794
            } else {
795
                throw new Exception("Unrecognized role action");
796
            }
797
798 39
            return true;
799
        }
800
801
        return false;
802
    }
803
804
    /**
805
     * Given a player's BZID, get a player object
806
     *
807
     * @param  int    $bzid The player's BZID
808
     * @return Player
809
     */
810 1
    public static function getFromBZID($bzid)
811
    {
812 1
        return self::get(self::fetchIdFrom($bzid, "bzid", "s"));
813
    }
814
815
    /**
816
     * Get a single player by their username
817
     *
818
     * @param  string $username The username to look for
819
     * @return Player
820
     */
821 1
    public static function getFromUsername($username)
822
    {
823 1
        $player = static::get(self::fetchIdFrom($username, 'username', 's'));
824
825 1
        return $player->inject('name', $username);
826
    }
827
828
    /**
829
     * Get all the players in the database that have an active status
830
     * @return Player[] An array of player BZIDs
831
     */
832
    public static function getPlayers()
833
    {
834
        return self::arrayIdToModel(
835
            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...
836
        );
837
    }
838
839
    /**
840
     * Show the number of notifications the user hasn't read yet
841
     * @return int
842
     */
843 1
    public function countUnreadNotifications()
844
    {
845 1
        return Notification::countUnreadNotifications($this->id);
846
    }
847
848
    /**
849
     * Count the number of matches a player has participated in
850
     * @return int
851
     */
852
    public function getMatchCount()
853
    {
854
        if ($this->cachedMatchCount === null) {
855
            $this->cachedMatchCount = Match::getQueryBuilder()
856
                ->active()
857
                ->with($this)
858
                ->count();
859
        }
860
861
        return $this->cachedMatchCount;
862
    }
863
864
    /**
865
     * Get the (victory/total matches) ratio of the player
866
     * @return float
867
     */
868
    public function getMatchWinRatio()
869
    {
870
        $count = $this->getMatchCount();
871
        $wins = Match::getQueryBuilder()
872
            ->active()
873
            ->with($this, 'win')
874
            ->count();
875
876
        if ($wins == 0) {
877
            return 0;
878
        }
879
880
        return $wins/$count;
881
    }
882
883
    /**
884
     * Show the number of messages the user hasn't read yet
885
     * @return int
886
     */
887 1
    public function countUnreadMessages()
888
    {
889 1
        return $this->fetchCount("WHERE `player` = ? AND `read` = 0",
890 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...
891
        );
892
    }
893
894
    /**
895
     * Get all of the members belonging to a team
896
     * @param  int      $teamID The ID of the team to fetch the members of
897
     * @return Player[] An array of Player objects of the team members
898
     */
899 2
    public static function getTeamMembers($teamID)
900
    {
901 2
        return self::arrayIdToModel(
902 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...
903
        );
904
    }
905
906
    /**
907
     * {@inheritdoc}
908
     */
909 1
    public static function getActiveStatuses()
910
    {
911 1
        return array('active', 'reported', 'test');
912
    }
913
914
    /**
915
     * {@inheritdoc}
916
     */
917 39
    public static function getEagerColumns()
918
    {
919 39
        return 'id,bzid,team,username,alias,status,avatar,country';
920
    }
921
922
    /**
923
     * {@inheritdoc}
924
     */
925 39
    public static function getLazyColumns()
926
    {
927 39
        return 'email,verified,receives,confirm_code,outdated,description,timezone,joined,last_login,admin_notes';
928
    }
929
930
    /**
931
     * Get a query builder for players
932
     * @return QueryBuilder
933
     */
934 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...
935
    {
936
        return new QueryBuilder('Player', array(
937
            'columns' => array(
938
                'name'     => 'username',
939
                'team'     => 'team',
940
                'outdated' => 'outdated',
941
                'status'   => 'status'
942
            ),
943
            'name' => 'name',
944
        ));
945
    }
946
947
    /**
948
     * Enter a new player to the database
949
     * @param  int              $bzid        The player's bzid
950
     * @param  string           $username    The player's username
951
     * @param  int              $team        The player's team
952
     * @param  string           $status      The player's status
953
     * @param  int              $role_id     The player's role when they are first created
954
     * @param  string           $avatar      The player's profile avatar
955
     * @param  string           $description The player's profile description
956
     * @param  int              $country     The player's country
957
     * @param  string           $timezone    The player's timezone
958
     * @param  string|\TimeDate $joined      The date the player joined
959
     * @param  string|\TimeDate $last_login  The timestamp of the player's last login
960
     * @return Player           An object representing the player that was just entered
961
     */
962 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")
963
    {
964 39
        $joined = TimeDate::from($joined);
965 39
        $last_login = TimeDate::from($last_login);
966 39
        $timezone = ($timezone) ?: date_default_timezone_get();
967
968 39
        $player = self::create(array(
969 39
            'bzid'        => $bzid,
970 39
            'team'        => $team,
971 39
            'username'    => $username,
972 39
            'alias'       => self::generateAlias($username),
973 39
            'status'      => $status,
974 39
            'avatar'      => $avatar,
975 39
            'description' => $description,
976 39
            'country'     => $country,
977 39
            'timezone'    => $timezone,
978 39
            'joined'      => $joined->toMysql(),
979 39
            'last_login'  => $last_login->toMysql(),
980 39
        ), 'iisssssisss');
981
982 39
        $player->addRole($role_id);
983 39
        $player->getIdenticon($player->getId());
984 39
        $player->setUsername($username);
985
986 39
        return $player;
987
    }
988
989
    /**
990
     * Determine if a player exists in the database
991
     * @param  int  $bzid The player's bzid
992
     * @return bool Whether the player exists in the database
993
     */
994
    public static function playerBZIDExists($bzid)
995
    {
996
        return self::getFromBZID($bzid)->isValid();
997
    }
998
999
    /**
1000
     * Change a player's callsign and add it to the database if it does not
1001
     * exist as a past callsign
1002
     *
1003
     * @param  string $username The new username of the player
1004
     * @return self
1005
     */
1006 39
    public function setUsername($username)
1007
    {
1008
        // The player's username was just fetched from BzDB, it's definitely not
1009
        // outdated
1010 39
        $this->setOutdated(false);
1011
1012
        // Players who have this player's username are considered outdated
1013 39
        $this->db->query("UPDATE {$this->table} SET outdated = 1 WHERE username = ? AND id != ?", "si", array($username, $this->id));
1014
1015 39
        if ($username === $this->name) {
1016
            // The player's username hasn't changed, no need to do anything
1017 1
            return $this;
1018
        }
1019
1020
        // Players who used to have our player's username are not outdated anymore,
1021
        // unless they are more than one.
1022
        // Even though we are sure that the old and new usernames are not equal,
1023
        // MySQL makes a different type of string equality tests, which is why we
1024
        // also check IDs to make sure not to affect our own player's outdatedness.
1025 38
        $this->db->query("
1026 38
            UPDATE {$this->table} SET outdated =
1027 38
                (SELECT (COUNT(*)>1) FROM (SELECT 1 FROM {$this->table} WHERE username = ? AND id != ?) t)
1028 38
            WHERE username = ? AND id != ?",
1029 38
            "sisi", array($this->name, $this->id, $this->name, $this->id));
1030
1031 38
        $this->updateProperty($this->name, 'username', $username, 's');
1032 38
        $this->db->query("INSERT IGNORE INTO past_callsigns (player, username) VALUES (?, ?)", "is", array($this->id, $username));
1033 38
        $this->resetAlias();
1034
1035 38
        return $this;
1036
    }
1037
1038
    /**
1039
     * Alphabetical order function for use in usort (case-insensitive)
1040
     * @return Closure The sort function
1041
     */
1042
    public static function getAlphabeticalSort()
1043
    {
1044 1
        return function (Player $a, Player $b) {
1045 1
            return strcasecmp($a->getUsername(), $b->getUsername());
1046 1
        };
1047
    }
1048
1049
    /**
1050
     * {@inheritdoc}
1051
     * @todo Add a constraint that does this automatically
1052
     */
1053 39
    public function wipe()
1054
    {
1055 39
        $this->db->query("DELETE FROM past_callsigns WHERE player = ?", "i", $this->id);
1056
1057 39
        parent::wipe();
1058 39
    }
1059
1060
    /**
1061
     * Find whether the player can delete a model
1062
     *
1063
     * @param  PermissionModel $model       The model that will be seen
1064
     * @param  bool         $showDeleted Whether to show deleted models to admins
1065
     * @return bool
1066
     */
1067 1
    public function canSee($model, $showDeleted = false)
1068
    {
1069 1
        return $model->canBeSeenBy($this, $showDeleted);
1070
    }
1071
1072
    /**
1073
     * Find whether the player can delete a model
1074
     *
1075
     * @param  PermissionModel $model The model that will be deleted
1076
     * @param  bool         $hard  Whether to check for hard-delete perms, as opposed
1077
     *                                to soft-delete ones
1078
     * @return bool
1079
     */
1080 1
    public function canDelete($model, $hard = false)
1081
    {
1082 1
        if ($hard) {
1083
            return $model->canBeHardDeletedBy($this);
1084
        } else {
1085 1
            return $model->canBeSoftDeletedBy($this);
1086
        }
1087
    }
1088
1089
    /**
1090
     * Find whether the player can create a model
1091
     *
1092
     * @param  string  $modelName The PHP class identifier of the model type
1093
     * @return bool
1094
     */
1095 1
    public function canCreate($modelName)
1096
    {
1097 1
        return $modelName::canBeCreatedBy($this);
1098
    }
1099
1100
    /**
1101
     * Find whether the player can edit a model
1102
     *
1103
     * @param  PermissionModel $model The model which will be edited
1104
     * @return bool
1105
     */
1106 1
    public function canEdit($model)
1107
    {
1108 1
        return $model->canBeEditedBy($this);
1109
    }
1110
}
1111