Completed
Push — master ( c26f0f...c7af1e )
by Konstantinos
18:52
created

Player::setName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
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 $ban;
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 38
    protected function assignResult($player)
152
    {
153 38
        $this->bzid = $player['bzid'];
154 38
        $this->name = $player['username'];
155 38
        $this->alias = $player['alias'];
156 38
        $this->team = $player['team'];
157 38
        $this->status = $player['status'];
158 38
        $this->avatar = $player['avatar'];
159 38
        $this->country = $player['country'];
160 38
    }
161
162
    /**
163
     * {@inheritdoc}
164
     */
165 38
    protected function assignLazyResult($player)
166
    {
167 38
        $this->email = $player['email'];
168 38
        $this->verified = $player['verified'];
169 38
        $this->receives = $player['receives'];
170 38
        $this->confirmCode = $player['confirm_code'];
171 38
        $this->outdated = $player['outdated'];
172 38
        $this->description = $player['description'];
173 38
        $this->timezone = $player['timezone'];
174 38
        $this->joined = TimeDate::fromMysql($player['joined']);
175 38
        $this->last_login = TimeDate::fromMysql($player['last_login']);
176 38
        $this->admin_notes = $player['admin_notes'];
177 38
        $this->ban = Ban::getBan($this->id);
178
179 38
        $this->updateUserPermissions();
180 38
    }
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 38
    public function addRole($role_id)
190
    {
191 38
        if ($role_id instanceof Role) {
192 1
            $role_id = $role_id->getId();
193 1
        }
194
195 38
        $this->lazyLoad();
196
197
        // Make sure the player doesn't already have the role
198 38
        foreach ($this->roles as $playerRole) {
199 14
            if ($playerRole->getId() == $role_id) {
200
                return false;
201
            }
202 38
        }
203
204 38
        $status = $this->modifyRole($role_id, "add");
205 38
        $this->refresh();
206
207 38
        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
    public function getCountry()
236
    {
237
        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
    public function canReceive($type)
296
    {
297
        $this->lazyLoad();
298
299
        if (!$this->email || !$this->isVerified()) {
300
            // Unverified e-mail means the user will receive nothing
301
            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
     * @param  string $code The confirmation code to check
317
     * @return bool `true` for a correct e-mail verification code
318
     */
319
    public function isCorrectConfirmCode($code)
320
    {
321
        $this->lazyLoad();
322
323
        if ($this->confirmCode === null) {
324
            return false;
325
        }
326
327
        return StringUtils::equals($code, $this->confirmCode);
328
    }
329
330
    /**
331
     * Get the player's sanitized description
332
     * @return string The description
333
     */
334
    public function getDescription()
335
    {
336
        $this->lazyLoad();
337
338
        return $this->description;
339
    }
340
341
    /**
342
     * Get the joined date of the player
343
     * @return TimeDate The joined date of the player
344
     */
345
    public function getJoinedDate()
346
    {
347
        $this->lazyLoad();
348
349
        return $this->joined->copy();
350
    }
351
352
    /**
353
     * Get all of the known IPs used by the player
354
     *
355
     * @return string[][] An array containing IPs and hosts
356
     */
357
    public function getKnownIPs()
358
    {
359
        return $this->db->query("SELECT DISTINCT ip, host FROM visits WHERE player = ? LIMIT 10", array($this->getId()));
360
    }
361
362
    /**
363
     * Get the last login for a player
364
     * @return TimeDate The date of the last login
365
     */
366
    public function getLastLogin()
367
    {
368
        $this->lazyLoad();
369
370
        return $this->last_login->copy();
371
    }
372
373
    /**
374
     * Get all of the callsigns a player has used to log in to the website
375
     * @return string[] An array containing all of the past callsigns recorded for a player
376
     */
377
    public function getPastCallsigns()
378
    {
379
        return self::fetchIds("WHERE player = ?", array($this->id), "past_callsigns", "username");
380
    }
381
382
    /**
383
     * Get the player's team
384
     * @return Team The object representing the team
385
     */
386 1
    public function getTeam()
387
    {
388 1
        return Team::get($this->team);
389
    }
390
391
    /**
392
     * Get the player's timezone PHP identifier (example: "Europe/Paris")
393
     * @return string The timezone
394
     */
395
    public function getTimezone()
396
    {
397
        $this->lazyLoad();
398
399
        return ($this->timezone) ?: date_default_timezone_get();
400
    }
401
402
    /**
403
     * Get the roles of the player
404
     * @return Role[]
405
     */
406
    public function getRoles()
407
    {
408
        $this->lazyLoad();
409
410
        return $this->roles;
411
    }
412
413
    /**
414
     * Rebuild the list of permissions a user has been granted
415
     */
416 38
    private function updateUserPermissions()
417
    {
418 38
        $this->roles = Role::getRoles($this->id);
0 ignored issues
show
Documentation Bug introduced by
It seems like \Role::getRoles($this->id) of type array<integer,object<Model>> is incompatible with the declared type array<integer,object<Role>> of property $roles.

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...
419 38
        $this->permissions = array();
420
421 38
        foreach ($this->roles as $role) {
422 38
            $this->permissions = array_merge($this->permissions, $role->getPerms());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Model as the method getPerms() does only exist in the following sub-classes of Model: Permission, Role. 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...
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...
423 38
        }
424 38
    }
425
426
    /**
427
     * Check if a player has a specific permission
428
     *
429
     * @param string|null $permission The permission to check for
430
     *
431
     * @return bool Whether or not the player has the permission
432
     */
433 1
    public function hasPermission($permission)
434
    {
435 1
        if ($permission === null) {
436
            return false;
437
        }
438
439 1
        $this->lazyLoad();
440
441 1
        return isset($this->permissions[$permission]);
442
    }
443
444
    /**
445
     * Check whether the callsign of the player is outdated
446
     *
447
     * Returns true if this player has probably changed their callsign, making
448
     * the current username stored in the database obsolete
449
     *
450
     * @return bool Whether or not the player is disabled
451
     */
452
    public function isOutdated()
453
    {
454
        $this->lazyLoad();
455
456
        return $this->outdated;
457
    }
458
459
    /**
460
     * Check if a player's account has been disabled
461
     *
462
     * @return bool Whether or not the player is disabled
463
     */
464
    public function isDisabled()
465
    {
466
        return $this->status == "disabled";
467
    }
468
469
    /**
470
     * Check if everyone can log in as this user on a test environment
471
     *
472
     * @return bool
473
     */
474
    public function isTestUser()
475
    {
476
        return $this->status == "test";
477
    }
478
479
    /**
480
     * Check if a player is teamless
481
     *
482
     * @return bool True if the player is teamless
483
     */
484 17
    public function isTeamless()
485
    {
486 17
        return empty($this->team);
487
    }
488
489
    /**
490
     * Mark a player's account as banned
491
     */
492 1
    public function markAsBanned()
493
    {
494 1
        if ($this->status != 'active') {
495
            return $this;
496
        }
497
498 1
        return $this->updateProperty($this->status, "status", "banned");
499
    }
500
501
    /**
502
     * Mark a player's account as unbanned
503
     */
504
    public function markAsUnbanned()
505
    {
506
        if ($this->status != 'banned') {
507
            return $this;
508
        }
509
510
        return $this->updateProperty($this->status, "status", "active");
511
    }
512
513
    /**
514
     * Find out if a player is banned
515
     *
516
     * @return bool
517
     */
518 2
    public function isBanned()
519
    {
520 2
        return Ban::getBan($this->id) !== null;
521
    }
522
523
    /**
524
     * Get the ban of the player
525
     *
526
     * This method performs a load of all the lazy parameters of the Player
527
     *
528
     * @return Ban|null The current ban of the player, or null if the player is
529
     *                  is not banned
530
     */
531
    public function getBan()
532
    {
533
        $this->lazyLoad();
534
535
        return $this->ban;
536
    }
537
538
    /**
539
     * Remove a player from a role
540
     *
541
     * @param int $role_id The role ID to add or remove
542
     *
543
     * @return bool Whether the operation was successful or not
544
     */
545
    public function removeRole($role_id)
546
    {
547
        $status = $this->modifyRole($role_id, "remove");
548
        $this->refresh();
549
550
        return $status;
551
    }
552
553
    /**
554
     * Set the player's email address and reset their verification status
555
     * @param string $email The address
556
     */
557
    public function setEmailAddress($email)
558
    {
559
        $this->lazyLoad();
560
561
        if ($this->email == $email) {
562
            // The e-mail hasn't changed, don't do anything
563
            return;
564
        }
565
566
        $this->setVerified(false);
567
        $this->generateNewConfirmCode();
568
569
        $this->email = $email;
570
        $this->update("email", $email);
571
    }
572
573
    /**
574
     * Set whether the player has verified their e-mail address
575
     *
576
     * @param  bool $verified Whether the player is verified or not
577
     * @return self
578
     */
579
    public function setVerified($verified)
580
    {
581
        $this->lazyLoad();
582
583
        if ($verified) {
584
            $this->setConfirmCode(null);
585
        }
586
587
        return $this->updateProperty($this->verified, 'verified', $verified);
588
    }
589
590
    /**
591
     * Generate a new random confirmation token for e-mail address verification
592
     *
593
     * @return self
594
     */
595
    public function generateNewConfirmCode()
596
    {
597
        $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...
598
        $random = $generator->nextBytes(16);
599
600
        return $this->setConfirmCode(bin2hex($random));
601
    }
602
603
    /**
604
     * Set the confirmation token for e-mail address verification
605
     *
606
     * @param  string $code The confirmation code
607
     * @return self
608
     */
609
    private function setConfirmCode($code)
610
    {
611
        $this->lazyLoad();
612
613
        return $this->updateProperty($this->confirmCode, 'confirm_code', $code);
614
    }
615
616
    /**
617
     * Set what kind of events the player should be e-mailed about
618
     *
619
     * @param  string $receives The type of notification
620
     * @return self
621
     */
622
    public function setReceives($receives)
623
    {
624
        $this->lazyLoad();
625
626
        return $this->updateProperty($this->receives, 'receives', $receives);
627
    }
628
629
    /**
630
     * Set whether the callsign of the player is outdated
631
     *
632
     * @param  bool $outdated Whether the callsign is outdated
633
     * @return self
634
     */
635 38
    public function setOutdated($outdated)
636
    {
637 38
        $this->lazyLoad();
638
639 38
        return $this->updateProperty($this->outdated, 'outdated', $outdated);
640
    }
641
642
    /**
643
     * Set the player's description
644
     * @param string $description The description
645
     */
646
    public function setDescription($description)
647
    {
648
        $this->description = $description;
649
        $this->update("description", $description);
650
    }
651
652
    /**
653
     * Set the player's timezone
654
     * @param string $timezone The timezone
655
     */
656
    public function setTimezone($timezone)
657
    {
658
        $this->timezone = $timezone;
659
        $this->update("timezone", $timezone);
660
    }
661
662
    /**
663
     * Set the player's team
664
     * @param int $team The team's ID
665
     */
666 17
    public function setTeam($team)
667
    {
668 17
        $this->team = $team;
669 17
        $this->update("team", $team);
670 17
    }
671
672
    /**
673
     * Set the player's status
674
     * @param string $status The new status
675
     */
676
    public function setStatus($status)
677
    {
678
        $this->updateProperty($this->status, 'status', $status);
679
    }
680
681
    /**
682
     * Set the player's admin notes
683
     * @param  string $admin_notes The new admin notes
684
     * @return self
685
     */
686
    public function setAdminNotes($admin_notes)
687
    {
688
        return $this->updateProperty($this->admin_notes, 'admin_notes', $admin_notes);
689
    }
690
691
    /**
692
     * Set the player's country
693
     * @param  int   $country The ID of the new country
694
     * @return self
695
     */
696
    public function setCountry($country)
697
    {
698
        return $this->updateProperty($this->country, 'country', $country);
699
    }
700
701
    /**
702
     * Updates this player's last login
703
     */
704
    public function updateLastLogin()
705
    {
706
        $this->update("last_login", TimeDate::now()->toMysql());
707
    }
708
709
    /**
710
     * Get the player's username
711
     * @return string The username
712
     */
713
    public function getUsername()
714
    {
715
        return $this->name;
716
    }
717
718
    /**
719
     * Get the player's username, safe for use in your HTML
720
     * @return string The username
721
     */
722
    public function getEscapedUsername()
723
    {
724
        return $this->getEscapedName();
725
    }
726
727
    /**
728
     * Alias for Player::setUsername()
729
     *
730
     * @param  string $username The new username
731
     * @return self
732
     */
733
    public function setName($username)
734
    {
735
        return $this->setUsername($username);
736
    }
737
738
    /**
739
     * Mark all the unread messages of a player as read
740
     *
741
     * @return void
742
     */
743
    public function markMessagesAsRead()
744
    {
745
        $this->db->execute(
746
            "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `read` = 0",
747
            array($this->id)
748
        );
749
    }
750
751
    /**
752
     * Set the roles of a user
753
     *
754
     * @todo   Is it worth making this faster?
755
     * @param  Role[] $roles The new roles of the user
756
     * @return self
757
     */
758
    public function setRoles($roles)
759
    {
760
        $this->lazyLoad();
761
762
        $oldRoles = Role::mapToIds($this->roles);
763
        $this->roles = $roles;
764
        $roleIds = Role::mapToIds($roles);
765
766
        $newRoles     = array_diff($roleIds, $oldRoles);
767
        $removedRoles = array_diff($oldRoles, $roleIds);
768
769
        foreach ($newRoles as $role) {
770
            $this->modifyRole($role, 'add');
771
        }
772
773
        foreach ($removedRoles as $role) {
774
            $this->modifyRole($role, 'remove');
775
        }
776
777
        $this->refresh();
778
779
        return $this;
780
    }
781
782
    /**
783
     * Give or remove a role to/form a player
784
     *
785
     * @param int    $role_id The role ID to add or remove
786
     * @param string $action  Whether to "add" or "remove" a role for a player
787
     *
788
     * @return bool Whether the operation was successful or not
789
     */
790 38
    private function modifyRole($role_id, $action)
791
    {
792 38
        $role = Role::get($role_id);
793
794 38
        if ($role->isValid()) {
795 38
            if ($action == "add") {
796 38
                $this->db->execute("INSERT INTO player_roles (user_id, role_id) VALUES (?, ?)", array($this->getId(), $role_id));
797 38
            } elseif ($action == "remove") {
798
                $this->db->execute("DELETE FROM player_roles WHERE user_id = ? AND role_id = ?", array($this->getId(), $role_id));
799
            } else {
800
                throw new Exception("Unrecognized role action");
801
            }
802
803 38
            return true;
804
        }
805
806
        return false;
807
    }
808
809
    /**
810
     * Given a player's BZID, get a player object
811
     *
812
     * @param  int    $bzid The player's BZID
813
     * @return Player
814
     */
815
    public static function getFromBZID($bzid)
816
    {
817
        return self::get(self::fetchIdFrom($bzid, "bzid"));
818
    }
819
820
    /**
821
     * Get a single player by their username
822
     *
823
     * @param  string $username The username to look for
824
     * @return Player
825
     */
826
    public static function getFromUsername($username)
827
    {
828
        $player = static::get(self::fetchIdFrom($username, 'username'));
829
830
        return $player->inject('name', $username);
831
    }
832
833
    /**
834
     * Get all the players in the database that have an active status
835
     * @return Player[] An array of player BZIDs
836
     */
837
    public static function getPlayers()
838
    {
839
        return self::arrayIdToModel(
840
            self::fetchIdsFrom("status", array("active", "test"), false)
841
        );
842
    }
843
844
    /**
845
     * Show the number of notifications the user hasn't read yet
846
     * @return int
847
     */
848
    public function countUnreadNotifications()
849
    {
850
        return Notification::countUnreadNotifications($this->id);
851
    }
852
853
    /**
854
     * Show the number of messages the user hasn't read yet
855
     * @return int
856
     */
857
    public function countUnreadMessages()
858
    {
859
        return $this->fetchCount("WHERE `player` = ? AND `read` = 0",
860
            $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...
861
        );
862
    }
863
864
    /**
865
     * Get all of the members belonging to a team
866
     * @param  int      $teamID The ID of the team to fetch the members of
867
     * @return Player[] An array of Player objects of the team members
868
     */
869 1
    public static function getTeamMembers($teamID)
870
    {
871 1
        return self::arrayIdToModel(
872 1
            self::fetchIds("WHERE team = ?", array($teamID))
873 1
        );
874
    }
875
876
    /**
877
     * {@inheritdoc}
878
     */
879
    public static function getActiveStatuses()
880
    {
881
        return array('active', 'reported', 'test');
882
    }
883
884
    /**
885
     * {@inheritdoc}
886
     */
887 38
    public static function getEagerColumns()
888
    {
889 38
        return 'id,bzid,team,username,alias,status,avatar,country';
890
    }
891
892
    /**
893
     * {@inheritdoc}
894
     */
895 38
    public static function getLazyColumns()
896
    {
897 38
        return 'email,verified,receives,confirm_code,outdated,description,timezone,joined,last_login,admin_notes';
898
    }
899
900
    /**
901
     * Get a query builder for players
902
     * @return QueryBuilder
903
     */
904 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...
905
    {
906
        return new QueryBuilder('Player', array(
907
            'columns' => array(
908
                'name'     => 'username',
909
                'team'     => 'team',
910
                'outdated' => 'outdated',
911
                'status'   => 'status'
912
            ),
913
            'name' => 'name',
914
        ));
915
    }
916
917
    /**
918
     * Enter a new player to the database
919
     * @param  int              $bzid        The player's bzid
920
     * @param  string           $username    The player's username
921
     * @param  int              $team        The player's team
922
     * @param  string           $status      The player's status
923
     * @param  int              $role_id     The player's role when they are first created
924
     * @param  string           $avatar      The player's profile avatar
925
     * @param  string           $description The player's profile description
926
     * @param  int              $country     The player's country
927
     * @param  string           $timezone    The player's timezone
928
     * @param  string|\TimeDate $joined      The date the player joined
929
     * @param  string|\TimeDate $last_login  The timestamp of the player's last login
930
     * @return Player           An object representing the player that was just entered
931
     */
932 38
    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")
933
    {
934 38
        $joined = TimeDate::from($joined);
935 38
        $last_login = TimeDate::from($last_login);
936 38
        $timezone = ($timezone) ?: date_default_timezone_get();
937
938 38
        $player = self::create(array(
939 38
            'bzid'        => $bzid,
940 38
            'team'        => $team,
941 38
            'username'    => $username,
942 38
            'alias'       => self::generateAlias($username),
943 38
            'status'      => $status,
944 38
            'avatar'      => $avatar,
945 38
            'description' => $description,
946 38
            'country'     => $country,
947 38
            'timezone'    => $timezone,
948 38
            'joined'      => $joined->toMysql(),
949 38
            'last_login'  => $last_login->toMysql(),
950 38
        ));
951
952 38
        $player->addRole($role_id);
953 38
        $player->getIdenticon($player->getId());
954 38
        $player->setUsername($username);
955
956 38
        return $player;
957
    }
958
959
    /**
960
     * Determine if a player exists in the database
961
     * @param  int  $bzid The player's bzid
962
     * @return bool Whether the player exists in the database
963
     */
964
    public static function playerBZIDExists($bzid)
965
    {
966
        return self::getFromBZID($bzid)->isValid();
967
    }
968
969
    /**
970
     * Change a player's callsign and add it to the database if it does not
971
     * exist as a past callsign
972
     *
973
     * @param  string $username The new username of the player
974
     * @return self
975
     */
976 38
    public function setUsername($username)
977
    {
978
        // The player's username was just fetched from BzDB, it's definitely not
979
        // outdated
980 38
        $this->setOutdated(false);
981
982
        // Players who have this player's username are considered outdated
983 38
        $this->db->execute("UPDATE {$this->table} SET outdated = 1 WHERE username = ? AND id != ?", array($username, $this->id));
984
985 38
        if ($username === $this->name) {
986
            // The player's username hasn't changed, no need to do anything
987
            return $this;
988
        }
989
990
        // Players who used to have our player's username are not outdated anymore,
991
        // unless they are more than one.
992
        // Even though we are sure that the old and new usernames are not equal,
993
        // MySQL makes a different type of string equality tests, which is why we
994
        // also check IDs to make sure not to affect our own player's outdatedness.
995 38
        $this->db->execute("
996 38
            UPDATE {$this->table} SET outdated =
997 38
                (SELECT (COUNT(*)>1) FROM (SELECT 1 FROM {$this->table} WHERE username = ? AND id != ?) t)
998 38
            WHERE username = ? AND id != ?",
999 38
            array($this->name, $this->id, $this->name, $this->id));
1000
1001 38
        $this->updateProperty($this->name, 'username', $username);
1002 38
        $this->db->execute("INSERT IGNORE INTO past_callsigns (player, username) VALUES (?, ?)", array($this->id, $username));
1003 38
        $this->resetAlias();
1004
1005 38
        return $this;
1006
    }
1007
1008
    /**
1009
     * Alphabetical order function for use in usort (case-insensitive)
1010
     * @return Closure The sort function
1011
     */
1012
    public static function getAlphabeticalSort()
1013
    {
1014
        return function (Player $a, Player $b) {
1015
            return strcasecmp($a->getUsername(), $b->getUsername());
1016
        };
1017
    }
1018
1019
    /**
1020
     * {@inheritdoc}
1021
     * @todo Add a constraint that does this automatically
1022
     */
1023 38
    public function wipe()
1024
    {
1025 38
        $this->db->execute("DELETE FROM past_callsigns WHERE player = ?", $this->id);
1026
1027 38
        parent::wipe();
1028 38
    }
1029
1030
    /**
1031
     * Find whether the player can delete a model
1032
     *
1033
     * @param  PermissionModel $model       The model that will be seen
1034
     * @param  bool         $showDeleted Whether to show deleted models to admins
1035
     * @return bool
1036
     */
1037
    public function canSee($model, $showDeleted = false)
1038
    {
1039
        return $model->canBeSeenBy($this, $showDeleted);
1040
    }
1041
1042
    /**
1043
     * Find whether the player can delete a model
1044
     *
1045
     * @param  PermissionModel $model The model that will be deleted
1046
     * @param  bool         $hard  Whether to check for hard-delete perms, as opposed
1047
     *                                to soft-delete ones
1048
     * @return bool
1049
     */
1050
    public function canDelete($model, $hard = false)
1051
    {
1052
        if ($hard) {
1053
            return $model->canBeHardDeletedBy($this);
1054
        } else {
1055
            return $model->canBeSoftDeletedBy($this);
1056
        }
1057
    }
1058
1059
    /**
1060
     * Find whether the player can create a model
1061
     *
1062
     * @param  string  $modelName The PHP class identifier of the model type
1063
     * @return bool
1064
     */
1065
    public function canCreate($modelName)
1066
    {
1067
        return $modelName::canBeCreatedBy($this);
1068
    }
1069
1070
    /**
1071
     * Find whether the player can edit a model
1072
     *
1073
     * @param  PermissionModel $model The model which will be edited
1074
     * @return bool
1075
     */
1076
    public function canEdit($model)
1077
    {
1078
        return $model->canBeEditedBy($this);
1079
    }
1080
}
1081