These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 Carbon\Carbon; |
||
10 | use Symfony\Component\Security\Core\Util\SecureRandom; |
||
11 | use Symfony\Component\Security\Core\Util\StringUtils; |
||
12 | |||
13 | /** |
||
14 | * A league player |
||
15 | * @package BZiON\Models |
||
16 | */ |
||
17 | class Player extends AvatarModel implements NamedModel, DuplexUrlInterface, EloInterface |
||
18 | { |
||
19 | /** |
||
20 | * These are built-in roles that cannot be deleted via the web interface so we will be storing these values as |
||
21 | * constant variables. Hopefully, a user won't be silly enough to delete them manually from the database. |
||
22 | * |
||
23 | * @TODO Deprecate these and use the Role constants |
||
24 | */ |
||
25 | const DEVELOPER = Role::DEVELOPER; |
||
26 | const ADMIN = Role::ADMINISTRATOR; |
||
27 | const COP = Role::COP; |
||
28 | const REFEREE = Role::REFEREE; |
||
29 | const S_ADMIN = Role::SYSADMIN; |
||
30 | const PLAYER = Role::PLAYER; |
||
31 | const PLAYER_NO_PM = Role::PLAYER_NO_PM; |
||
32 | |||
33 | /** |
||
34 | * The bzid of the player |
||
35 | * @var int |
||
36 | */ |
||
37 | protected $bzid; |
||
38 | |||
39 | /** |
||
40 | * The id of the player's team |
||
41 | * @var int |
||
42 | */ |
||
43 | protected $team; |
||
44 | |||
45 | /** |
||
46 | * The player's e-mail address |
||
47 | * @var string |
||
48 | */ |
||
49 | protected $email; |
||
50 | |||
51 | /** |
||
52 | * Whether the player has verified their e-mail address |
||
53 | * @var bool |
||
54 | */ |
||
55 | protected $verified; |
||
56 | |||
57 | /** |
||
58 | * What kind of events the player should be e-mailed about |
||
59 | * @var string |
||
60 | */ |
||
61 | protected $receives; |
||
62 | |||
63 | /** |
||
64 | * A confirmation code for the player's e-mail address verification |
||
65 | * @var string |
||
66 | */ |
||
67 | protected $confirmCode; |
||
68 | |||
69 | /** |
||
70 | * Whether the callsign of the player is outdated |
||
71 | * @var bool |
||
72 | */ |
||
73 | protected $outdated; |
||
74 | |||
75 | /** |
||
76 | * The player's profile description |
||
77 | * @var string |
||
78 | */ |
||
79 | protected $description; |
||
80 | |||
81 | /** |
||
82 | * The id of the player's country |
||
83 | * @var int |
||
84 | */ |
||
85 | protected $country; |
||
86 | |||
87 | /** |
||
88 | * The site theme this player has chosen |
||
89 | * @var string |
||
90 | */ |
||
91 | protected $theme; |
||
92 | |||
93 | /** |
||
94 | * The player's timezone PHP identifier, e.g. "Europe/Paris" |
||
95 | * @var string |
||
96 | */ |
||
97 | protected $timezone; |
||
98 | |||
99 | /** |
||
100 | * The date the player joined the site |
||
101 | * @var TimeDate |
||
102 | */ |
||
103 | protected $joined; |
||
104 | |||
105 | /** |
||
106 | * The date of the player's last login |
||
107 | * @var TimeDate |
||
108 | */ |
||
109 | protected $last_login; |
||
110 | |||
111 | /** |
||
112 | * The date of the player's last match |
||
113 | * @var Match |
||
114 | */ |
||
115 | protected $last_match; |
||
116 | |||
117 | /** |
||
118 | * The roles a player belongs to |
||
119 | * @var Role[] |
||
120 | */ |
||
121 | protected $roles; |
||
122 | |||
123 | /** |
||
124 | * The permissions a player has |
||
125 | * @var Permission[] |
||
126 | */ |
||
127 | protected $permissions; |
||
128 | |||
129 | /** |
||
130 | * A section for admins to write notes about players |
||
131 | * @var string |
||
132 | */ |
||
133 | protected $admin_notes; |
||
134 | |||
135 | /** |
||
136 | * The ban of the player, or null if the player is not banned |
||
137 | * @var Ban|null |
||
138 | */ |
||
139 | protected $ban; |
||
140 | |||
141 | /** |
||
142 | * Cached results for match summaries |
||
143 | * |
||
144 | * @var array |
||
145 | */ |
||
146 | private $cachedMatchSummary; |
||
147 | |||
148 | /** |
||
149 | * The cached match count for a player |
||
150 | * |
||
151 | * @var int |
||
152 | */ |
||
153 | private $cachedMatchCount = null; |
||
154 | |||
155 | /** |
||
156 | * The Elo for this player that has been explicitly set for this player from a database query. This value will take |
||
157 | * precedence over having to build to an Elo season history. |
||
158 | * |
||
159 | * @var int |
||
160 | */ |
||
161 | private $elo; |
||
162 | private $eloSeason; |
||
163 | private $eloSeasonHistory; |
||
164 | |||
165 | private $matchActivity; |
||
166 | |||
167 | /** |
||
168 | * The name of the database table used for queries |
||
169 | */ |
||
170 | const TABLE = "players"; |
||
171 | |||
172 | /** |
||
173 | * The location where avatars will be stored |
||
174 | */ |
||
175 | const AVATAR_LOCATION = "/web/assets/imgs/avatars/players/"; |
||
176 | |||
177 | 76 | const EDIT_PERMISSION = Permission::EDIT_USER; |
|
178 | const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_USER; |
||
179 | 76 | const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_USER; |
|
180 | 76 | ||
181 | 76 | /** |
|
182 | 76 | * {@inheritdoc} |
|
183 | 76 | */ |
|
184 | 76 | protected function assignResult($player) |
|
185 | 76 | { |
|
186 | $this->bzid = $player['bzid']; |
||
187 | 76 | $this->name = $player['username']; |
|
188 | $this->alias = $player['alias']; |
||
189 | $this->team = $player['team']; |
||
190 | 76 | $this->status = $player['status']; |
|
191 | $this->avatar = $player['avatar']; |
||
192 | $this->country = $player['country']; |
||
193 | |||
194 | if (array_key_exists('activity', $player)) { |
||
195 | 76 | $this->matchActivity = ($player['activity'] != null) ? $player['activity'] : 0.0; |
|
196 | } |
||
197 | 76 | ||
198 | 76 | if (array_key_exists('elo', $player)) { |
|
199 | 76 | $this->elo = $player['elo']; |
|
200 | 76 | } |
|
201 | 76 | } |
|
202 | 76 | ||
203 | 76 | /** |
|
204 | 76 | * {@inheritdoc} |
|
205 | 76 | */ |
|
206 | 76 | protected function assignLazyResult($player) |
|
207 | 76 | { |
|
208 | 76 | $this->email = $player['email']; |
|
209 | $this->verified = $player['verified']; |
||
210 | 76 | $this->receives = $player['receives']; |
|
211 | $this->confirmCode = $player['confirm_code']; |
||
212 | 76 | $this->outdated = $player['outdated']; |
|
213 | 76 | $this->description = $player['description']; |
|
214 | $this->timezone = $player['timezone']; |
||
215 | $this->joined = TimeDate::fromMysql($player['joined']); |
||
216 | $this->last_login = TimeDate::fromMysql($player['last_login']); |
||
217 | $this->last_match = Match::get($player['last_match']); |
||
218 | $this->admin_notes = $player['admin_notes']; |
||
219 | $this->ban = Ban::getBan($this->id); |
||
220 | |||
221 | $this->cachedMatchSummary = []; |
||
222 | 76 | ||
223 | // Theme user options |
||
224 | 76 | if (isset($player['theme'])) { |
|
225 | 1 | $this->theme = $player['theme']; |
|
226 | } else { |
||
227 | $themes = Service::getSiteThemes(); |
||
228 | 76 | $this->theme = $themes[0]['slug']; |
|
229 | } |
||
230 | |||
231 | 76 | $this->updateUserPermissions(); |
|
232 | 14 | } |
|
233 | 14 | ||
234 | /** |
||
235 | * Add a player a new role |
||
236 | * |
||
237 | 76 | * @param Role|int $role_id The role ID to add a player to |
|
238 | 76 | * |
|
239 | * @return bool Whether the operation was successful or not |
||
240 | 76 | */ |
|
241 | public function addRole($role_id) |
||
242 | { |
||
243 | if ($role_id instanceof Role) { |
||
244 | $role_id = $role_id->getId(); |
||
245 | } |
||
246 | |||
247 | $this->lazyLoad(); |
||
248 | |||
249 | // Make sure the player doesn't already have the role |
||
250 | foreach ($this->roles as $playerRole) { |
||
251 | if ($playerRole->getId() == $role_id) { |
||
252 | return false; |
||
253 | } |
||
254 | } |
||
255 | |||
256 | $status = $this->modifyRole($role_id, "add"); |
||
257 | $this->refresh(); |
||
258 | 22 | ||
259 | return $status; |
||
260 | 22 | } |
|
261 | |||
262 | /** |
||
263 | * Get the notes admins have left about a player |
||
264 | * @return string The notes |
||
265 | */ |
||
266 | public function getAdminNotes() |
||
267 | { |
||
268 | 1 | $this->lazyLoad(); |
|
269 | |||
270 | 1 | return $this->admin_notes; |
|
271 | } |
||
272 | |||
273 | /** |
||
274 | * Get the player's BZID |
||
275 | * @return int The BZID |
||
276 | */ |
||
277 | public function getBZID() |
||
278 | { |
||
279 | return $this->bzid; |
||
280 | } |
||
281 | |||
282 | /** |
||
283 | * Get the country a player belongs to |
||
284 | * |
||
285 | * @return Country The country belongs to |
||
286 | */ |
||
287 | public function getCountry() |
||
288 | { |
||
289 | return Country::get($this->country); |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | 30 | * Get the e-mail address of the player |
|
294 | * |
||
295 | 30 | * @return string The address |
|
296 | 29 | */ |
|
297 | public function getEmailAddress() |
||
298 | { |
||
299 | 30 | $this->lazyLoad(); |
|
300 | 29 | ||
301 | return $this->email; |
||
302 | } |
||
303 | 30 | ||
304 | /** |
||
305 | * Build a key that we'll use for caching season Elo data in this model |
||
306 | * |
||
307 | * @param string|null $season The season to get |
||
308 | * @param int|null $year The year of the season to get |
||
309 | * |
||
310 | * @return string |
||
311 | */ |
||
312 | private function buildSeasonKey(&$season, &$year) |
||
313 | 3 | { |
|
314 | if ($season === null) { |
||
315 | 3 | $season = Season::getCurrentSeason(); |
|
316 | } |
||
317 | 3 | ||
318 | if ($year === null) { |
||
319 | $year = Carbon::now()->year; |
||
320 | } |
||
321 | |||
322 | return sprintf('%s-%s', $year, $season); |
||
323 | } |
||
324 | |||
325 | /** |
||
326 | * Build a key to use for caching season Elo data in this model from a timestamp |
||
327 | * |
||
328 | * @param DateTime $timestamp |
||
329 | * |
||
330 | * @return string |
||
331 | */ |
||
332 | 3 | private function buildSeasonKeyFromTimestamp(\DateTime $timestamp) |
|
333 | { |
||
334 | 3 | $seasonInfo = Season::getSeason($timestamp); |
|
335 | 3 | ||
336 | return sprintf('%s-%s', $seasonInfo['year'], $seasonInfo['season']); |
||
337 | } |
||
338 | |||
339 | 3 | /** |
|
340 | 2 | * Remove all Elo data for this model for matches occurring after the given match (inclusive) |
|
341 | * |
||
342 | * This function will not remove the Elo data for this match from the database. Ideally, this function should only |
||
343 | * be called during Elo recalculation for this match. |
||
344 | * |
||
345 | 3 | * @internal |
|
346 | * |
||
347 | 3 | * @param Match $match |
|
348 | 1 | * |
|
349 | * @see Match::recalculateElo() |
||
350 | */ |
||
351 | public function invalidateMatchFromCache(Match $match) |
||
352 | { |
||
353 | 2 | $seasonKey = $this->buildSeasonKeyFromTimestamp($match->getTimestamp()); |
|
354 | 2 | $seasonElo = null; |
|
355 | |||
356 | 2 | // If we have an existing season history cached, save a reference to it for easy access. Don't create one if |
|
357 | // nothing is cached or else it'll cause for an empty cache to be created |
||
358 | if (isset($this->eloSeasonHistory[$seasonKey])) { |
||
359 | 2 | $seasonElo = &$this->eloSeasonHistory[$seasonKey]; |
|
360 | } |
||
361 | |||
362 | // Unset the currently cached Elo for a player so next time Player::getElo() is called, it'll pull the latest |
||
363 | // available Elo |
||
364 | unset($this->eloSeason[$seasonKey]); |
||
365 | |||
366 | if ($seasonElo === null) { |
||
367 | return; |
||
368 | } |
||
369 | 30 | ||
370 | // This function is called when we recalculate, so assume that the match will be recent, therefore towards the |
||
371 | 30 | // end of the Elo history array. We splice the array to have all Elo data after this match to be removed. |
|
372 | foreach (array_reverse($seasonElo) as $key => $match) { |
||
373 | if ($match['match'] === $match) { |
||
374 | 30 | $seasonElo = array_splice($seasonElo, $key); |
|
375 | 29 | break; |
|
376 | } |
||
377 | } |
||
378 | 30 | } |
|
379 | |||
380 | /** |
||
381 | * Get the Elo changes for a player for a given season |
||
382 | * |
||
383 | * @param string|null $season The season to get |
||
384 | * @param int|null $year The year of the season to get |
||
385 | * |
||
386 | * @return array |
||
387 | */ |
||
388 | public function getEloSeasonHistory($season = null, $year = null) |
||
389 | { |
||
390 | $seasonKey = $this->buildSeasonKey($season, $year); |
||
391 | |||
392 | 30 | // This season's already been cached |
|
393 | if (isset($this->eloSeasonHistory[$seasonKey])) { |
||
394 | 30 | return $this->eloSeasonHistory[$seasonKey]; |
|
395 | 30 | } |
|
396 | |||
397 | 30 | $this->eloSeasonHistory[$seasonKey] = $this->db->query(' |
|
398 | 30 | SELECT |
|
399 | 30 | elo_new AS elo, |
|
400 | match_id AS `match`, |
||
401 | MONTH(matches.timestamp) AS `month`, |
||
402 | 30 | YEAR(matches.timestamp) AS `year`, |
|
403 | DAY(matches.timestamp) AS `day` |
||
404 | FROM |
||
405 | player_elo |
||
406 | LEFT JOIN matches ON player_elo.match_id = matches.id |
||
407 | WHERE |
||
408 | user_id = ? AND season_period = ? AND season_year = ? |
||
409 | ORDER BY |
||
410 | match_id ASC |
||
411 | ', [ $this->getId(), $season, $year ]); |
||
412 | |||
413 | array_unshift($this->eloSeasonHistory[$seasonKey], [ |
||
414 | 'elo' => 1200, |
||
415 | 30 | 'match' => null, |
|
416 | 'month' => Season::getCurrentSeasonRange($season)->getStartOfRange()->month, |
||
417 | 30 | 'year' => $year, |
|
418 | 30 | 'day' => 1 |
|
419 | ]); |
||
420 | 30 | ||
421 | 29 | return $this->eloSeasonHistory[$seasonKey]; |
|
422 | } |
||
423 | |||
424 | 30 | /** |
|
425 | * Get the player's Elo for a season. |
||
426 | 30 | * |
|
427 | 30 | * With the default arguments, it will fetch the Elo for the current season. |
|
428 | 30 | * |
|
429 | * @param string|null $season The season we're looking for: winter, spring, summer, or fall |
||
430 | * @param int|null $year The year of the season we're looking for |
||
431 | * |
||
432 | * @return int The player's Elo |
||
433 | 30 | */ |
|
434 | public function getElo($season = null, $year = null) |
||
435 | { |
||
436 | // The Elo for this player has been forcefully set from a trusted database query, so just return that. |
||
437 | if ($this->elo !== null) { |
||
438 | return $this->elo; |
||
439 | } |
||
440 | |||
441 | $this->getEloSeasonHistory($season, $year); |
||
442 | $seasonKey = $this->buildSeasonKey($season, $year); |
||
443 | |||
444 | if (isset($this->eloSeason[$seasonKey])) { |
||
445 | 30 | return $this->eloSeason[$seasonKey]; |
|
446 | } |
||
447 | 30 | ||
448 | 30 | $season = &$this->eloSeasonHistory[$seasonKey]; |
|
449 | |||
450 | if (!empty($season)) { |
||
451 | 30 | $elo = end($season); |
|
452 | 30 | $this->eloSeason[$seasonKey] = ($elo !== false) ? $elo['elo'] : 1200; |
|
453 | } else { |
||
454 | 30 | $this->eloSeason[$seasonKey] = 1200; |
|
455 | } |
||
456 | 30 | ||
457 | 29 | return $this->eloSeason[$seasonKey]; |
|
458 | } |
||
459 | 29 | ||
460 | /** |
||
461 | 30 | * Adjust the Elo of a player for the current season based on a Match |
|
462 | * |
||
463 | * **Warning:** If $match is null, the Elo for the player will be modified but the value will not be persisted to |
||
464 | * the database. |
||
465 | * |
||
466 | * @param int $adjust The value to be added to the current ELO (negative to subtract) |
||
467 | * @param Match|null $match The match where this Elo change took place |
||
468 | */ |
||
469 | public function adjustElo($adjust, Match $match = null) |
||
470 | { |
||
471 | $timestamp = ($match !== null) ? $match->getTimestamp() : (Carbon::now()); |
||
472 | $seasonInfo = Season::getSeason($timestamp); |
||
473 | |||
474 | // Get the current Elo for the player, even if it's the default 1200. We need the value for adjusting |
||
475 | $elo = $this->getElo($seasonInfo['season'], $seasonInfo['year']); |
||
476 | $seasonKey = sprintf('%s-%s', $seasonInfo['year'], $seasonInfo['season']); |
||
477 | |||
478 | $this->eloSeason[$seasonKey] += $adjust; |
||
479 | |||
480 | if ($match !== null && $this->isValid()) { |
||
481 | $this->db->execute(' |
||
482 | INSERT INTO player_elo VALUES (?, ?, ?, ?, ?, ?) |
||
483 | ', [ $this->getId(), $match->getId(), $seasonInfo['season'], $seasonInfo['year'], $elo, $this->eloSeason[$seasonKey] ]); |
||
484 | } |
||
485 | } |
||
486 | |||
487 | /** |
||
488 | * Returns whether the player has verified their e-mail address |
||
489 | * |
||
490 | * @return bool `true` for verified players |
||
491 | */ |
||
492 | public function isVerified() |
||
493 | { |
||
494 | $this->lazyLoad(); |
||
495 | |||
496 | return $this->verified; |
||
497 | } |
||
498 | |||
499 | /** |
||
500 | * Returns the confirmation code for the player's e-mail address verification |
||
501 | * |
||
502 | * @return string The player's confirmation code |
||
503 | */ |
||
504 | public function getConfirmCode() |
||
505 | { |
||
506 | 1 | $this->lazyLoad(); |
|
507 | |||
508 | 1 | return $this->confirmCode; |
|
509 | } |
||
510 | 1 | ||
511 | /** |
||
512 | 1 | * Returns what kind of events the player should be e-mailed about |
|
513 | * |
||
514 | * @return string The type of notifications |
||
515 | */ |
||
516 | public function getReceives() |
||
517 | { |
||
518 | $this->lazyLoad(); |
||
519 | |||
520 | return $this->receives; |
||
521 | } |
||
522 | |||
523 | /** |
||
524 | * Finds out whether the specified player wants and can receive an e-mail |
||
525 | * message |
||
526 | * |
||
527 | * @param string $type |
||
528 | * @return bool `true` if the player should be sent an e-mail |
||
529 | */ |
||
530 | public function canReceive($type) |
||
531 | { |
||
532 | $this->lazyLoad(); |
||
533 | |||
534 | if (!$this->email || !$this->isVerified()) { |
||
535 | // Unverified e-mail means the user will receive nothing |
||
536 | return false; |
||
537 | } |
||
538 | |||
539 | if ($this->receives == 'everything') { |
||
540 | return true; |
||
541 | } |
||
542 | |||
543 | return $this->receives == $type; |
||
544 | } |
||
545 | |||
546 | /** |
||
547 | * Find out whether the specified confirmation code is correct |
||
548 | * |
||
549 | * This method protects against timing attacks |
||
550 | * |
||
551 | * @param string $code The confirmation code to check |
||
552 | * @return bool `true` for a correct e-mail verification code |
||
553 | */ |
||
554 | public function isCorrectConfirmCode($code) |
||
555 | { |
||
556 | $this->lazyLoad(); |
||
557 | |||
558 | if ($this->confirmCode === null) { |
||
559 | return false; |
||
560 | } |
||
561 | |||
562 | return StringUtils::equals($code, $this->confirmCode); |
||
563 | } |
||
564 | |||
565 | /** |
||
566 | * Get the player's sanitized description |
||
567 | * @return string The description |
||
568 | */ |
||
569 | public function getDescription() |
||
570 | { |
||
571 | $this->lazyLoad(); |
||
572 | |||
573 | return $this->description; |
||
574 | } |
||
575 | |||
576 | /** |
||
577 | * Get the joined date of the player |
||
578 | * @return TimeDate The joined date of the player |
||
579 | */ |
||
580 | public function getJoinedDate() |
||
581 | { |
||
582 | $this->lazyLoad(); |
||
583 | |||
584 | return $this->joined->copy(); |
||
585 | } |
||
586 | |||
587 | /** |
||
588 | * Get all of the known IPs used by the player |
||
589 | * |
||
590 | * @return string[][] An array containing IPs and hosts |
||
591 | */ |
||
592 | public function getKnownIPs() |
||
593 | { |
||
594 | return $this->db->query( |
||
595 | 'SELECT DISTINCT ip, host FROM visits WHERE player = ? GROUP BY ip, host ORDER BY MAX(timestamp) DESC LIMIT 10', |
||
596 | array($this->getId()) |
||
597 | ); |
||
598 | } |
||
599 | |||
600 | /** |
||
601 | * Get the last login for a player |
||
602 | * @return TimeDate The date of the last login |
||
603 | */ |
||
604 | public function getLastLogin() |
||
605 | { |
||
606 | $this->lazyLoad(); |
||
607 | |||
608 | return $this->last_login->copy(); |
||
609 | } |
||
610 | |||
611 | 23 | /** |
|
612 | * Get the last match |
||
613 | 23 | * @return Match |
|
614 | */ |
||
615 | public function getLastMatch() |
||
616 | { |
||
617 | $this->lazyLoad(); |
||
618 | |||
619 | return $this->last_match; |
||
620 | 1 | } |
|
621 | |||
622 | 1 | /** |
|
623 | * Get all of the callsigns a player has used to log in to the website |
||
624 | 1 | * @return string[] An array containing all of the past callsigns recorded for a player |
|
625 | */ |
||
626 | public function getPastCallsigns() |
||
627 | { |
||
628 | return self::fetchIds("WHERE player = ?", array($this->id), "past_callsigns", "username"); |
||
629 | } |
||
630 | |||
631 | /** |
||
632 | * Get the player's team |
||
633 | * @return Team The object representing the team |
||
634 | */ |
||
635 | public function getTeam() |
||
636 | { |
||
637 | return Team::get($this->team); |
||
638 | } |
||
639 | |||
640 | /** |
||
641 | 76 | * Get the player's timezone PHP identifier (example: "Europe/Paris") |
|
642 | * @return string The timezone |
||
643 | 76 | */ |
|
644 | 76 | public function getTimezone() |
|
645 | { |
||
646 | 76 | $this->lazyLoad(); |
|
647 | 76 | ||
648 | return ($this->timezone) ?: date_default_timezone_get(); |
||
649 | 76 | } |
|
650 | |||
651 | /** |
||
652 | * Get the roles of the player |
||
653 | * @return Role[] |
||
654 | */ |
||
655 | public function getRoles() |
||
656 | { |
||
657 | $this->lazyLoad(); |
||
658 | 2 | ||
659 | return $this->roles; |
||
660 | 2 | } |
|
661 | 1 | ||
662 | /** |
||
663 | * Rebuild the list of permissions a user has been granted |
||
664 | 2 | */ |
|
665 | private function updateUserPermissions() |
||
666 | 2 | { |
|
667 | $this->roles = Role::getRoles($this->id); |
||
0 ignored issues
–
show
|
|||
668 | $this->permissions = array(); |
||
669 | |||
670 | foreach ($this->roles as $role) { |
||
671 | $this->permissions = array_merge($this->permissions, $role->getPerms()); |
||
0 ignored issues
–
show
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
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
![]() 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.. ![]() |
|||
672 | } |
||
673 | } |
||
674 | |||
675 | /** |
||
676 | * Check if a player has a specific permission |
||
677 | * |
||
678 | * @param string|null $permission The permission to check for |
||
679 | * |
||
680 | * @return bool Whether or not the player has the permission |
||
681 | */ |
||
682 | public function hasPermission($permission) |
||
683 | { |
||
684 | if ($permission === null) { |
||
685 | return false; |
||
686 | } |
||
687 | |||
688 | $this->lazyLoad(); |
||
689 | |||
690 | return isset($this->permissions[$permission]); |
||
691 | } |
||
692 | |||
693 | /** |
||
694 | * Check whether or not a player been in a match or has logged on in the specified amount of time to be considered |
||
695 | * active |
||
696 | * |
||
697 | * @return bool True if the player has been active |
||
698 | */ |
||
699 | public function hasBeenActive() |
||
700 | { |
||
701 | $this->lazyLoad(); |
||
702 | |||
703 | $interval = Service::getParameter('bzion.miscellaneous.active_interval'); |
||
704 | $lastLogin = $this->last_login->copy()->modify($interval); |
||
705 | |||
706 | $hasBeenActive = (TimeDate::now() <= $lastLogin); |
||
707 | |||
708 | if ($this->last_match->isValid()) { |
||
709 | $lastMatch = $this->last_match->getTimestamp()->copy()->modify($interval); |
||
710 | $hasBeenActive = ($hasBeenActive || TimeDate::now() <= $lastMatch); |
||
711 | } |
||
712 | |||
713 | return $hasBeenActive; |
||
714 | } |
||
715 | |||
716 | /** |
||
717 | * Check whether the callsign of the player is outdated |
||
718 | * |
||
719 | * Returns true if this player has probably changed their callsign, making |
||
720 | * the current username stored in the database obsolete |
||
721 | * |
||
722 | 1 | * @return bool Whether or not the player is disabled |
|
723 | */ |
||
724 | 1 | public function isOutdated() |
|
725 | { |
||
726 | $this->lazyLoad(); |
||
727 | |||
728 | return $this->outdated; |
||
729 | } |
||
730 | |||
731 | /** |
||
732 | 55 | * Check if a player's account has been disabled |
|
733 | * |
||
734 | 55 | * @return bool Whether or not the player is disabled |
|
735 | */ |
||
736 | public function isDisabled() |
||
737 | { |
||
738 | return $this->status == "disabled"; |
||
739 | } |
||
740 | 1 | ||
741 | /** |
||
742 | 1 | * Check if everyone can log in as this user on a test environment |
|
743 | * |
||
744 | * @return bool |
||
745 | */ |
||
746 | 1 | public function isTestUser() |
|
747 | { |
||
748 | return $this->status == "test"; |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Check if a player is teamless |
||
753 | * |
||
754 | * @return bool True if the player is teamless |
||
755 | */ |
||
756 | public function isTeamless() |
||
757 | { |
||
758 | return empty($this->team); |
||
759 | } |
||
760 | |||
761 | /** |
||
762 | * Mark a player's account as banned |
||
763 | * |
||
764 | * @deprecated The players table shouldn't have any indicators of banned status, the Bans table is the authoritative source |
||
765 | */ |
||
766 | 2 | public function markAsBanned() |
|
767 | { |
||
768 | 2 | return; |
|
0 ignored issues
–
show
|
|||
769 | } |
||
770 | |||
771 | /** |
||
772 | * Mark a player's account as unbanned |
||
773 | * |
||
774 | * @deprecated The players table shouldn't have any indicators of banned status, the Bans table is the authoritative source |
||
775 | */ |
||
776 | public function markAsUnbanned() |
||
777 | { |
||
778 | return; |
||
0 ignored issues
–
show
|
|||
779 | } |
||
780 | |||
781 | /** |
||
782 | * Find out if a player is hard banned |
||
783 | * |
||
784 | * @return bool |
||
785 | */ |
||
786 | public function isBanned() |
||
787 | { |
||
788 | $ban = Ban::getBan($this->id); |
||
789 | |||
790 | return ($ban !== null && !$ban->isSoftBan()); |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * Get the ban of the player |
||
795 | * |
||
796 | * This method performs a load of all the lazy parameters of the Player |
||
797 | * |
||
798 | * @return Ban|null The current ban of the player, or null if the player is |
||
799 | * is not banned |
||
800 | */ |
||
801 | public function getBan() |
||
802 | { |
||
803 | $this->lazyLoad(); |
||
804 | |||
805 | return $this->ban; |
||
806 | } |
||
807 | |||
808 | /** |
||
809 | * Remove a player from a role |
||
810 | * |
||
811 | * @param int $role_id The role ID to add or remove |
||
812 | * |
||
813 | * @return bool Whether the operation was successful or not |
||
814 | */ |
||
815 | public function removeRole($role_id) |
||
816 | { |
||
817 | $status = $this->modifyRole($role_id, "remove"); |
||
818 | $this->refresh(); |
||
819 | |||
820 | return $status; |
||
821 | } |
||
822 | |||
823 | /** |
||
824 | * Set the player's email address and reset their verification status |
||
825 | * @param string $email The address |
||
826 | */ |
||
827 | public function setEmailAddress($email) |
||
828 | { |
||
829 | $this->lazyLoad(); |
||
830 | |||
831 | if ($this->email == $email) { |
||
832 | // The e-mail hasn't changed, don't do anything |
||
833 | return; |
||
834 | } |
||
835 | |||
836 | $this->setVerified(false); |
||
837 | $this->generateNewConfirmCode(); |
||
838 | |||
839 | $this->updateProperty($this->email, 'email', $email); |
||
840 | } |
||
841 | |||
842 | /** |
||
843 | * Set whether the player has verified their e-mail address |
||
844 | * |
||
845 | * @param bool $verified Whether the player is verified or not |
||
846 | * @return self |
||
847 | */ |
||
848 | public function setVerified($verified) |
||
849 | { |
||
850 | $this->lazyLoad(); |
||
851 | |||
852 | if ($verified) { |
||
853 | $this->setConfirmCode(null); |
||
854 | } |
||
855 | |||
856 | return $this->updateProperty($this->verified, 'verified', $verified); |
||
857 | } |
||
858 | |||
859 | /** |
||
860 | * Generate a new random confirmation token for e-mail address verification |
||
861 | * |
||
862 | * @return self |
||
863 | */ |
||
864 | public function generateNewConfirmCode() |
||
865 | { |
||
866 | $generator = new SecureRandom(); |
||
0 ignored issues
–
show
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. ![]() |
|||
867 | $random = $generator->nextBytes(16); |
||
868 | |||
869 | return $this->setConfirmCode(bin2hex($random)); |
||
870 | } |
||
871 | |||
872 | /** |
||
873 | * Set the confirmation token for e-mail address verification |
||
874 | * |
||
875 | * @param string $code The confirmation code |
||
876 | * @return self |
||
877 | */ |
||
878 | private function setConfirmCode($code) |
||
879 | { |
||
880 | $this->lazyLoad(); |
||
881 | |||
882 | 76 | return $this->updateProperty($this->confirmCode, 'confirm_code', $code); |
|
883 | } |
||
884 | 76 | ||
885 | /** |
||
886 | 76 | * Set what kind of events the player should be e-mailed about |
|
887 | * |
||
888 | * @param string $receives The type of notification |
||
889 | * @return self |
||
890 | */ |
||
891 | public function setReceives($receives) |
||
892 | { |
||
893 | $this->lazyLoad(); |
||
894 | |||
895 | return $this->updateProperty($this->receives, 'receives', $receives); |
||
896 | } |
||
897 | |||
898 | /** |
||
899 | * Set whether the callsign of the player is outdated |
||
900 | * |
||
901 | * @param bool $outdated Whether the callsign is outdated |
||
902 | * @return self |
||
903 | */ |
||
904 | public function setOutdated($outdated) |
||
905 | { |
||
906 | $this->lazyLoad(); |
||
907 | |||
908 | return $this->updateProperty($this->outdated, 'outdated', $outdated); |
||
909 | } |
||
910 | |||
911 | 55 | /** |
|
912 | * Set the player's description |
||
913 | 55 | * @param string $description The description |
|
914 | 55 | */ |
|
915 | public function setDescription($description) |
||
916 | { |
||
917 | $this->updateProperty($this->description, "description", $description); |
||
918 | } |
||
919 | |||
920 | /** |
||
921 | 29 | * Set the player's timezone |
|
922 | * @param string $timezone The timezone |
||
923 | 29 | */ |
|
924 | 29 | public function setTimezone($timezone) |
|
925 | { |
||
926 | $this->updateProperty($this->timezone, "timezone", $timezone); |
||
927 | } |
||
928 | |||
929 | /** |
||
930 | * Set the player's team |
||
931 | * @param int $team The team's ID |
||
932 | */ |
||
933 | public function setTeam($team) |
||
934 | { |
||
935 | $this->updateProperty($this->team, "team", $team); |
||
936 | } |
||
937 | |||
938 | /** |
||
939 | * Set the match the player last participated in |
||
940 | * |
||
941 | * @param int $match The match's ID |
||
942 | */ |
||
943 | public function setLastMatch($match) |
||
944 | { |
||
945 | $this->updateProperty($this->last_match, 'last_match', $match); |
||
946 | } |
||
947 | |||
948 | /** |
||
949 | * Set the player's status |
||
950 | * @param string $status The new status |
||
951 | */ |
||
952 | public function setStatus($status) |
||
953 | { |
||
954 | $this->updateProperty($this->status, 'status', $status); |
||
955 | } |
||
956 | |||
957 | /** |
||
958 | * Set the player's admin notes |
||
959 | * @param string $admin_notes The new admin notes |
||
960 | * @return self |
||
961 | */ |
||
962 | public function setAdminNotes($admin_notes) |
||
963 | { |
||
964 | return $this->updateProperty($this->admin_notes, 'admin_notes', $admin_notes); |
||
965 | } |
||
966 | |||
967 | 1 | /** |
|
968 | * Set the player's country |
||
969 | 1 | * @param int $country The ID of the new country |
|
970 | * @return self |
||
971 | */ |
||
972 | public function setCountry($country) |
||
973 | { |
||
974 | return $this->updateProperty($this->country, 'country', $country); |
||
975 | } |
||
976 | 1 | ||
977 | /** |
||
978 | 1 | * Get the player's chosen theme preference |
|
979 | * |
||
980 | * @return string |
||
981 | */ |
||
982 | public function getTheme() |
||
983 | { |
||
984 | $this->lazyLoad(); |
||
985 | |||
986 | return $this->theme; |
||
987 | } |
||
988 | |||
989 | /** |
||
990 | * Set the site theme for the player |
||
991 | * |
||
992 | * If the chosen site theme is invalid, it'll be defaulted to the site default (the first theme defined) |
||
993 | * |
||
994 | * @param string $theme |
||
995 | */ |
||
996 | public function setTheme($theme) |
||
997 | { |
||
998 | $themes = array_column(Service::getSiteThemes(), 'slug'); |
||
999 | |||
1000 | if (!in_array($theme, $themes)) { |
||
1001 | $theme = Service::getDefaultSiteTheme(); |
||
1002 | } |
||
1003 | |||
1004 | $this->updateProperty($this->theme, 'theme', $theme); |
||
1005 | } |
||
1006 | |||
1007 | /** |
||
1008 | * Updates this player's last login |
||
1009 | */ |
||
1010 | public function updateLastLogin() |
||
1011 | { |
||
1012 | $this->update("last_login", TimeDate::now()->toMysql()); |
||
1013 | } |
||
1014 | |||
1015 | /** |
||
1016 | * Get the player's username |
||
1017 | * @return string The username |
||
1018 | */ |
||
1019 | public function getUsername() |
||
1020 | { |
||
1021 | return $this->name; |
||
1022 | } |
||
1023 | |||
1024 | /** |
||
1025 | * Get the player's username, safe for use in your HTML |
||
1026 | * @return string The username |
||
1027 | */ |
||
1028 | public function getEscapedUsername() |
||
1029 | { |
||
1030 | return $this->getEscapedName(); |
||
1031 | } |
||
1032 | |||
1033 | /** |
||
1034 | * Alias for Player::setUsername() |
||
1035 | * |
||
1036 | * @param string $username The new username |
||
1037 | * @return self |
||
1038 | */ |
||
1039 | public function setName($username) |
||
1040 | { |
||
1041 | return $this->setUsername($username); |
||
1042 | } |
||
1043 | |||
1044 | 76 | /** |
|
1045 | * Mark all the unread messages of a player as read |
||
1046 | 76 | * |
|
1047 | * @return void |
||
1048 | 76 | */ |
|
1049 | 76 | public function markMessagesAsRead() |
|
1050 | 76 | { |
|
1051 | $this->db->execute( |
||
1052 | "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `read` = 0", |
||
1053 | array($this->id) |
||
1054 | ); |
||
1055 | } |
||
1056 | |||
1057 | 76 | /** |
|
1058 | * Set the roles of a user |
||
1059 | * |
||
1060 | * @todo Is it worth making this faster? |
||
1061 | * @param Role[] $roles The new roles of the user |
||
1062 | * @return self |
||
1063 | */ |
||
1064 | public function setRoles($roles) |
||
1065 | { |
||
1066 | $this->lazyLoad(); |
||
1067 | |||
1068 | $oldRoles = Role::mapToIds($this->roles); |
||
1069 | 23 | $this->roles = $roles; |
|
1070 | $roleIds = Role::mapToIds($roles); |
||
1071 | 23 | ||
1072 | $newRoles = array_diff($roleIds, $oldRoles); |
||
1073 | $removedRoles = array_diff($oldRoles, $roleIds); |
||
1074 | |||
1075 | foreach ($newRoles as $role) { |
||
1076 | $this->modifyRole($role, 'add'); |
||
1077 | } |
||
1078 | |||
1079 | foreach ($removedRoles as $role) { |
||
1080 | 1 | $this->modifyRole($role, 'remove'); |
|
1081 | } |
||
1082 | 1 | ||
1083 | $this->refresh(); |
||
1084 | 1 | ||
1085 | return $this; |
||
1086 | } |
||
1087 | |||
1088 | /** |
||
1089 | * Give or remove a role to/form a player |
||
1090 | * |
||
1091 | * @param int $role_id The role ID to add or remove |
||
1092 | * @param string $action Whether to "add" or "remove" a role for a player |
||
1093 | * |
||
1094 | * @return bool Whether the operation was successful or not |
||
1095 | */ |
||
1096 | private function modifyRole($role_id, $action) |
||
1097 | { |
||
1098 | $role = Role::get($role_id); |
||
1099 | |||
1100 | if ($role->isValid()) { |
||
1101 | if ($action == "add") { |
||
1102 | 1 | $this->db->execute("INSERT INTO player_roles (user_id, role_id) VALUES (?, ?)", array($this->getId(), $role_id)); |
|
1103 | } elseif ($action == "remove") { |
||
1104 | 1 | $this->db->execute("DELETE FROM player_roles WHERE user_id = ? AND role_id = ?", array($this->getId(), $role_id)); |
|
1105 | } else { |
||
1106 | throw new Exception("Unrecognized role action"); |
||
1107 | } |
||
1108 | |||
1109 | return true; |
||
1110 | } |
||
1111 | |||
1112 | return false; |
||
1113 | } |
||
1114 | |||
1115 | /** |
||
1116 | * Given a player's BZID, get a player object |
||
1117 | * |
||
1118 | * @param int $bzid The player's BZID |
||
1119 | * @return Player |
||
1120 | */ |
||
1121 | public static function getFromBZID($bzid) |
||
1122 | { |
||
1123 | return self::get(self::fetchIdFrom($bzid, "bzid")); |
||
1124 | } |
||
1125 | |||
1126 | /** |
||
1127 | * Get a single player by their username |
||
1128 | * |
||
1129 | * @param string $username The username to look for |
||
1130 | * @return Player |
||
1131 | */ |
||
1132 | public static function getFromUsername($username) |
||
1133 | { |
||
1134 | $player = static::get(self::fetchIdFrom($username, 'username')); |
||
1135 | |||
1136 | return $player->inject('name', $username); |
||
1137 | } |
||
1138 | |||
1139 | /** |
||
1140 | * Get all the players in the database that have an active status |
||
1141 | * @return Player[] An array of player BZIDs |
||
1142 | */ |
||
1143 | public static function getPlayers() |
||
1144 | { |
||
1145 | return self::arrayIdToModel( |
||
1146 | self::fetchIdsFrom("status", array("active", "test"), false) |
||
1147 | ); |
||
1148 | } |
||
1149 | |||
1150 | /** |
||
1151 | * Show the number of notifications the user hasn't read yet |
||
1152 | * @return int |
||
1153 | */ |
||
1154 | public function countUnreadNotifications() |
||
1155 | { |
||
1156 | return Notification::countUnreadNotifications($this->id); |
||
1157 | } |
||
1158 | |||
1159 | /** |
||
1160 | * Count the number of matches a player has participated in |
||
1161 | * @return int |
||
1162 | */ |
||
1163 | public function getMatchCount() |
||
1164 | { |
||
1165 | if ($this->cachedMatchCount === null) { |
||
1166 | $this->cachedMatchCount = Match::getQueryBuilder() |
||
1167 | ->active() |
||
1168 | ->with($this) |
||
1169 | ->count(); |
||
1170 | } |
||
1171 | |||
1172 | return $this->cachedMatchCount; |
||
1173 | } |
||
1174 | |||
1175 | /** |
||
1176 | * Get the (victory/total matches) ratio of the player |
||
1177 | * @return float |
||
1178 | */ |
||
1179 | public function getMatchWinRatio() |
||
1180 | { |
||
1181 | $count = $this->getMatchCount(); |
||
1182 | |||
1183 | if ($count == 0) { |
||
1184 | return 0; |
||
1185 | } |
||
1186 | |||
1187 | $wins = Match::getQueryBuilder() |
||
1188 | ->active() |
||
1189 | ->with($this, 'win') |
||
1190 | ->count(); |
||
1191 | |||
1192 | return $wins / $count; |
||
1193 | } |
||
1194 | |||
1195 | /** |
||
1196 | * Get the (total caps made by team/total matches) ratio of the player |
||
1197 | * @return float |
||
1198 | */ |
||
1199 | public function getMatchAverageCaps() |
||
1200 | { |
||
1201 | $count = $this->getMatchCount(); |
||
1202 | |||
1203 | if ($count == 0) { |
||
1204 | return 0; |
||
1205 | } |
||
1206 | |||
1207 | // Get the sum of team A points if the player was in team A, team B points if the player was in team B |
||
1208 | $query = $this->db->query(" |
||
1209 | SELECT |
||
1210 | SUM( |
||
1211 | IF(mp.team_loyalty = 0, team_a_points, team_b_points) |
||
1212 | ) AS sum |
||
1213 | FROM |
||
1214 | matches |
||
1215 | INNER JOIN |
||
1216 | match_participation mp ON mp.match_id = matches.id |
||
1217 | WHERE |
||
1218 | status = 'entered' AND mp.user_id = ? |
||
1219 | ", [$this->id]); |
||
1220 | |||
1221 | return $query[0]['sum'] / $count; |
||
1222 | } |
||
1223 | |||
1224 | /** |
||
1225 | * Get the match activity in matches per day for a player |
||
1226 | * |
||
1227 | * @return float |
||
1228 | */ |
||
1229 | 1 | public function getMatchActivity() |
|
1230 | { |
||
1231 | 1 | if ($this->matchActivity !== null) { |
|
1232 | 1 | return $this->matchActivity; |
|
1233 | } |
||
1234 | |||
1235 | $activity = 0.0; |
||
1236 | |||
1237 | $matches = Match::getQueryBuilder() |
||
1238 | ->active() |
||
1239 | ->with($this) |
||
1240 | ->where('time')->isAfter(TimeDate::from('45 days ago')) |
||
1241 | 2 | ->getModels($fast = true); |
|
1242 | |||
1243 | 2 | foreach ($matches as $match) { |
|
1244 | 2 | $activity += $match->getActivity(); |
|
1245 | } |
||
1246 | |||
1247 | return $activity; |
||
1248 | } |
||
1249 | |||
1250 | /** |
||
1251 | 1 | * Return an array of matches this player participated in per month. |
|
1252 | * |
||
1253 | 1 | * ``` |
|
1254 | * ['yyyy-mm'] = <number of matches> |
||
1255 | * ``` |
||
1256 | * |
||
1257 | * @param TimeDate|string $timePeriod |
||
1258 | * |
||
1259 | 76 | * @return int[] |
|
1260 | */ |
||
1261 | public function getMatchSummary($timePeriod = '1 year ago') |
||
1262 | 76 | { |
|
1263 | $since = ($timePeriod instanceof TimeDate) ? $timePeriod : TimeDate::from($timePeriod); |
||
1264 | |||
1265 | if (!isset($this->cachedMatchSummary[(string)$timePeriod])) { |
||
1266 | $this->cachedMatchSummary[(string)$timePeriod] = Match::getQueryBuilder() |
||
1267 | ->active() |
||
1268 | ->with($this) |
||
1269 | ->where('time')->isAfter($since) |
||
1270 | ->getSummary($since) |
||
1271 | ; |
||
1272 | 76 | } |
|
1273 | |||
1274 | return $this->cachedMatchSummary[(string)$timePeriod]; |
||
1275 | } |
||
1276 | |||
1277 | /** |
||
1278 | 76 | * Show the number of messages the user hasn't read yet |
|
1279 | * @return int |
||
1280 | */ |
||
1281 | 76 | public function countUnreadMessages() |
|
1282 | { |
||
1283 | return $this->fetchCount("WHERE `player` = ? AND `read` = 0", |
||
1284 | $this->id, 'player_conversations' |
||
0 ignored issues
–
show
$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);
![]() |
|||
1285 | ); |
||
1286 | } |
||
1287 | |||
1288 | /** |
||
1289 | * Get all of the members belonging to a team |
||
1290 | * @param int $teamID The ID of the team to fetch the members of |
||
1291 | * @return Player[] An array of Player objects of the team members |
||
1292 | */ |
||
1293 | public static function getTeamMembers($teamID) |
||
1294 | 76 | { |
|
1295 | return self::arrayIdToModel( |
||
1296 | self::fetchIds("WHERE team = ?", array($teamID)) |
||
1297 | ); |
||
1298 | } |
||
1299 | |||
1300 | /** |
||
1301 | * {@inheritdoc} |
||
1302 | */ |
||
1303 | public static function getActiveStatuses() |
||
1304 | { |
||
1305 | return array('active', 'reported', 'test'); |
||
1306 | } |
||
1307 | |||
1308 | /** |
||
1309 | * {@inheritdoc} |
||
1310 | */ |
||
1311 | public static function getEagerColumns($prefix = null) |
||
1312 | { |
||
1313 | $columns = [ |
||
1314 | 'id', |
||
1315 | 'bzid', |
||
1316 | 'team', |
||
1317 | 'username', |
||
1318 | 'alias', |
||
1319 | 'status', |
||
1320 | 'avatar', |
||
1321 | 'country', |
||
1322 | ]; |
||
1323 | |||
1324 | return self::formatColumns($prefix, $columns); |
||
1325 | } |
||
1326 | |||
1327 | /** |
||
1328 | * {@inheritdoc} |
||
1329 | 76 | */ |
|
1330 | public static function getLazyColumns($prefix = null) |
||
1331 | 76 | { |
|
1332 | 76 | $columns = [ |
|
1333 | 76 | 'email', |
|
1334 | 'verified', |
||
1335 | 76 | 'receives', |
|
1336 | 76 | 'confirm_code', |
|
1337 | 76 | 'outdated', |
|
1338 | 76 | 'description', |
|
1339 | 76 | 'theme', |
|
1340 | 76 | 'timezone', |
|
1341 | 76 | 'joined', |
|
1342 | 76 | 'last_login', |
|
1343 | 76 | 'last_match', |
|
1344 | 76 | 'admin_notes', |
|
1345 | 76 | ]; |
|
1346 | 76 | ||
1347 | return self::formatColumns($prefix, $columns); |
||
1348 | } |
||
1349 | 76 | ||
1350 | 76 | /** |
|
1351 | 76 | * Get a query builder for players |
|
1352 | * @return PlayerQueryBuilder |
||
1353 | 76 | */ |
|
1354 | public static function getQueryBuilder() |
||
1355 | { |
||
1356 | return new PlayerQueryBuilder('Player', array( |
||
1357 | 'columns' => array( |
||
1358 | 'name' => 'username', |
||
1359 | 'team' => 'team', |
||
1360 | 'outdated' => 'outdated', |
||
1361 | 'status' => 'status', |
||
1362 | ), |
||
1363 | 'name' => 'name', |
||
1364 | )); |
||
1365 | } |
||
1366 | |||
1367 | /** |
||
1368 | * Enter a new player to the database |
||
1369 | * @param int $bzid The player's bzid |
||
1370 | * @param string $username The player's username |
||
1371 | * @param int $team The player's team |
||
1372 | * @param string $status The player's status |
||
1373 | 76 | * @param int $role_id The player's role when they are first created |
|
1374 | * @param string $avatar The player's profile avatar |
||
1375 | * @param string $description The player's profile description |
||
1376 | * @param int $country The player's country |
||
1377 | 76 | * @param string $timezone The player's timezone |
|
1378 | * @param string|\TimeDate $joined The date the player joined |
||
1379 | * @param string|\TimeDate $last_login The timestamp of the player's last login |
||
1380 | 76 | * @return Player An object representing the player that was just entered |
|
1381 | */ |
||
1382 | 76 | 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") |
|
1383 | { |
||
1384 | 76 | $joined = TimeDate::from($joined); |
|
1385 | $last_login = TimeDate::from($last_login); |
||
1386 | $timezone = ($timezone) ?: date_default_timezone_get(); |
||
1387 | |||
1388 | $player = self::create(array( |
||
1389 | 'bzid' => $bzid, |
||
1390 | 'team' => $team, |
||
1391 | 'username' => $username, |
||
1392 | 'alias' => self::generateAlias($username), |
||
1393 | 'status' => $status, |
||
1394 | 'avatar' => $avatar, |
||
1395 | 'description' => $description, |
||
1396 | 'country' => $country, |
||
1397 | 'timezone' => $timezone, |
||
1398 | 'joined' => $joined->toMysql(), |
||
1399 | 'last_login' => $last_login->toMysql(), |
||
1400 | )); |
||
1401 | |||
1402 | $player->addRole($role_id); |
||
1403 | $player->getIdenticon($player->getId()); |
||
1404 | $player->setUsername($username); |
||
1405 | |||
1406 | return $player; |
||
1407 | } |
||
1408 | |||
1409 | /** |
||
1410 | * Determine if a player exists in the database |
||
1411 | 1 | * @param int $bzid The player's bzid |
|
1412 | 1 | * @return bool Whether the player exists in the database |
|
1413 | 1 | */ |
|
1414 | public static function playerBZIDExists($bzid) |
||
1415 | { |
||
1416 | return self::getFromBZID($bzid)->isValid(); |
||
1417 | } |
||
1418 | |||
1419 | /** |
||
1420 | 76 | * Change a player's callsign and add it to the database if it does not |
|
1421 | * exist as a past callsign |
||
1422 | 76 | * |
|
1423 | * @param string $username The new username of the player |
||
1424 | 76 | * @return self |
|
1425 | 76 | */ |
|
1426 | public function setUsername($username) |
||
1427 | { |
||
1428 | // The player's username was just fetched from BzDB, it's definitely not |
||
1429 | // outdated |
||
1430 | $this->setOutdated(false); |
||
1431 | |||
1432 | // Players who have this player's username are considered outdated |
||
1433 | $this->db->execute("UPDATE {$this->table} SET outdated = 1 WHERE username = ? AND id != ?", array($username, $this->id)); |
||
1434 | 1 | ||
1435 | if ($username === $this->name) { |
||
1436 | 1 | // The player's username hasn't changed, no need to do anything |
|
1437 | return $this; |
||
1438 | } |
||
1439 | |||
1440 | // Players who used to have our player's username are not outdated anymore, |
||
1441 | // unless they are more than one. |
||
1442 | // Even though we are sure that the old and new usernames are not equal, |
||
1443 | // MySQL makes a different type of string equality tests, which is why we |
||
1444 | // also check IDs to make sure not to affect our own player's outdatedness. |
||
1445 | $this->db->execute(" |
||
1446 | UPDATE {$this->table} SET outdated = |
||
1447 | 1 | (SELECT (COUNT(*)>1) FROM (SELECT 1 FROM {$this->table} WHERE username = ? AND id != ?) t) |
|
1448 | WHERE username = ? AND id != ?", |
||
1449 | 1 | array($this->name, $this->id, $this->name, $this->id)); |
|
1450 | |||
1451 | $this->updateProperty($this->name, 'username', $username); |
||
1452 | 1 | $this->db->execute("INSERT IGNORE INTO past_callsigns (player, username) VALUES (?, ?)", array($this->id, $username)); |
|
1453 | $this->resetAlias(); |
||
1454 | |||
1455 | return $this; |
||
1456 | } |
||
1457 | |||
1458 | /** |
||
1459 | * Alphabetical order function for use in usort (case-insensitive) |
||
1460 | * @return Closure The sort function |
||
1461 | */ |
||
1462 | 1 | public static function getAlphabeticalSort() |
|
1463 | { |
||
1464 | 1 | return function (Player $a, Player $b) { |
|
1465 | return strcasecmp($a->getUsername(), $b->getUsername()); |
||
1466 | }; |
||
1467 | } |
||
1468 | |||
1469 | /** |
||
1470 | * {@inheritdoc} |
||
1471 | * @todo Add a constraint that does this automatically |
||
1472 | */ |
||
1473 | 1 | public function wipe() |
|
1474 | { |
||
1475 | 1 | $this->db->execute("DELETE FROM past_callsigns WHERE player = ?", $this->id); |
|
1476 | |||
1477 | parent::wipe(); |
||
1478 | } |
||
1479 | |||
1480 | /** |
||
1481 | * Find whether the player can delete a model |
||
1482 | * |
||
1483 | * @param PermissionModel $model The model that will be seen |
||
1484 | * @param bool $showDeleted Whether to show deleted models to admins |
||
1485 | * @return bool |
||
1486 | */ |
||
1487 | public function canSee($model, $showDeleted = false) |
||
1488 | { |
||
1489 | return $model->canBeSeenBy($this, $showDeleted); |
||
1490 | } |
||
1491 | |||
1492 | /** |
||
1493 | * Find whether the player can delete a model |
||
1494 | * |
||
1495 | * @param PermissionModel $model The model that will be deleted |
||
1496 | * @param bool $hard Whether to check for hard-delete perms, as opposed |
||
1497 | * to soft-delete ones |
||
1498 | * @return bool |
||
1499 | */ |
||
1500 | public function canDelete($model, $hard = false) |
||
1501 | { |
||
1502 | if ($hard) { |
||
1503 | return $model->canBeHardDeletedBy($this); |
||
1504 | } else { |
||
1505 | return $model->canBeSoftDeletedBy($this); |
||
1506 | } |
||
1507 | } |
||
1508 | |||
1509 | /** |
||
1510 | * Find whether the player can create a model |
||
1511 | * |
||
1512 | * @param string $modelName The PHP class identifier of the model type |
||
1513 | * @return bool |
||
1514 | */ |
||
1515 | public function canCreate($modelName) |
||
1516 | { |
||
1517 | return $modelName::canBeCreatedBy($this); |
||
1518 | } |
||
1519 | |||
1520 | /** |
||
1521 | * Find whether the player can edit a model |
||
1522 | * |
||
1523 | * @param PermissionModel $model The model which will be edited |
||
1524 | * @return bool |
||
1525 | */ |
||
1526 | public function canEdit($model) |
||
1527 | { |
||
1528 | return $model->canBeEditedBy($this); |
||
1529 | } |
||
1530 | } |
||
1531 |
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..