Completed
Push — feature/player-elo ( 127bff...a3fab4 )
by Vladimir
07:14
created

Team::setStatus()   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 the teams belonging to the current league
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
/**
10
 * A league team
11
 * @package    BZiON\Models
12
 */
13
class Team extends EloModel implements TeamInterface, DuplexUrlInterface
14
{
15
    /**
16
     * The description of the team written in markdown
17
     *
18
     * @var string
19
     */
20
    protected $description;
21
22
    /**
23
     * The creation date of the team
24
     *
25
     * @var TimeDate
26
     */
27
    protected $created;
28
29
    /**
30
     * The team's activity
31
     *
32
     * null if we haven't calculated it yet
33
     *
34
     * @var float|null
35
     */
36
    protected $activity = null;
37
38
    /**
39
     * The id of the team leader
40
     *
41
     * @var int
42
     */
43
    protected $leader;
44
45
    /**
46
     * The number of matches won
47
     *
48
     * @var int
49
     */
50
    protected $matches_won;
51
52
    /**
53
     * The number of matches lost
54
     *
55
     * @var int
56
     */
57
    protected $matches_lost;
58
59
    /**
60
     * The number of matches tied
61
     *
62
     * @var int
63
     */
64
    protected $matches_draw;
65
66
    /**
67
     * The total number of matches
68
     *
69
     * @var int
70
     */
71
    protected $matches_total;
72
73
    /**
74
     * The number of members
75
     *
76
     * @var int
77
     */
78
    protected $members;
79
80
    /**
81
     * The team's status
82
     *
83
     * @var string
84
     */
85
    protected $status;
86
87
    /**
88
     * A list of cached matches to calculate team activity
89
     *
90
     * @var Match[]|null
91
     */
92
    public static $cachedMatches = null;
93
94
    /**
95
     * The name of the database table used for queries
96
     */
97
    const TABLE = "teams";
98
99
    /**
100
     * The location where avatars will be stored
101
     */
102
    const AVATAR_LOCATION = "/web/assets/imgs/avatars/teams/";
103
104
    const CREATE_PERMISSION = Permission::CREATE_TEAM;
105
    const EDIT_PERMISSION = Permission::EDIT_TEAM;
106
    const SOFT_DELETE_PERMISSION = Permission::SOFT_DELETE_TEAM;
107
    const HARD_DELETE_PERMISSION = Permission::HARD_DELETE_TEAM;
108
109
    /**
110
     * {@inheritdoc}
111
     */
112 23
    protected function assignResult($team)
113
    {
114 23
        $this->name = $team['name'];
115 23
        $this->alias = $team['alias'];
116 23
        $this->description = $team['description'];
117 23
        $this->avatar = $team['avatar'];
118 23
        $this->created = TimeDate::fromMysql($team['created']);
119 23
        $this->elo = $team['elo'];
120 23
        $this->activity = null;
121 23
        $this->leader = $team['leader'];
122 23
        $this->matches_won = $team['matches_won'];
123 23
        $this->matches_lost = $team['matches_lost'];
124 23
        $this->matches_draw = $team['matches_draw'];
125 23
        $this->members = $team['members'];
126 23
        $this->status = $team['status'];
127
128 23
        $this->matches_total = $this->matches_won + $this->matches_lost + $this->matches_draw;
129 23
    }
130
131
    /**
132
     * Adds a new member to the team
133
     *
134
     * @param int $id The id of the player to add to the team
135
     *
136
     * @return bool|null True if both the player was added to the team AND the team member count was incremented
137
     */
138 23 View Code Duplication
    public function addMember($id)
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...
139
    {
140 23
        $player = Player::get($id);
141
142 23
        if (!$player->isTeamless()) {
143
            throw new Exception("The player already belongs in a team");
144
        }
145
146 23
        $player->setTeam($this->getId());
147 23
        $this->update('members', ++$this->members);
148 23
    }
149
150
    /**
151
     * Increment the team's match count
152
     *
153
     * @param int    $adjust The number to add to the current matches number (negative to substract)
154
     * @param string $type   The match count that should be changed. Can be 'win', 'draw' or 'loss'
155
     */
156 12
    public function changeMatchCount($adjust, $type)
157
    {
158 12
        $this->matches_total += $adjust;
159
160
        switch ($type) {
161 12
            case "win":
162 12
            case "won":
163 8
                $this->update("matches_won", $this->matches_won += $adjust);
164
165 8
                return;
166 12
            case "loss":
167 4
            case "lost":
168 9
                $this->update("matches_lost", $this->matches_lost += $adjust);
169
170 9
                return;
171
            default:
172 4
                $this->update("matches_draw", $this->matches_draw += $adjust);
173
174 4
                return;
175
        }
176
    }
177
178
    /**
179
     * Decrement the team's match count by one
180
     *
181
     * @param string $type The type of the match. Can be 'win', 'draw' or 'loss'
182
     */
183
    public function decrementMatchCount($type)
184
    {
185
        $this->changeMatchCount(-1, $type);
186
    }
187
188
    /**
189
     * Get the activity of the team
190
     *
191
     * @return float The team's activity
192
     */
193 1
    public function getActivity()
194
    {
195 1
        if ($this->activity === null) {
196
            // We don't have a cached activity value
197 1
            if (self::$cachedMatches === null) {
198
                self::$cachedMatches = Match::getQueryBuilder()
199
                    ->active()
200
                    ->with($this)
201
                    ->where('time')->isAfter(TimeDate::from('45 days ago'))
202
                    ->getModels($fast = true);
203
            }
204
205 1
            $this->activity = 0.0;
206 1
            foreach (self::$cachedMatches as $match) {
207 1
                if ($match->involvesTeam($this)) {
208 1
                    $this->activity += $match->getActivity();
209
                }
210
            }
211
        }
212
213 1
        return $this->activity;
214
    }
215
216
    /**
217
     * Get the creation date of the team
218
     *
219
     * @return TimeDate The creation date of the team
220
     */
221 2
    public function getCreationDate()
222
    {
223 2
        return $this->created->copy();
224
    }
225
226
    /**
227
     * Get the description of the team
228
     *
229
     * @return string  The description of the team
230
     */
231 1
    public function getDescription()
232
    {
233 1
        return $this->description;
234
    }
235
236
    /**
237
     * Get the leader of the team
238
     *
239
     * @return Player The object representing the team leader
240
     */
241 2
    public function getLeader()
242
    {
243 2
        return Player::get($this->leader);
244
    }
245
246
    /**
247
     * Get the matches this team has participated in
248
     *
249
     * @param string $matchType The filter for match types: "all", "wins", "losses", or "draws"
250
     * @param int    $count     The amount of matches to be retrieved
251
     * @param int    $page      The number of the page to return
252
     *
253
     * @return Match[] The array of match IDs this team has participated in
254
     */
255 2
    public function getMatches($matchType = "all", $count = 5, $page = 1)
256
    {
257 2
        return Match::getQueryBuilder()
258 2
             ->active()
259 2
             ->with($this, $matchType)
260 2
             ->sortBy('time')->reverse()
261 2
             ->limit($count)->fromPage($page)
262 2
             ->getModels($fast = true);
263
    }
264
265
    /**
266
     * Get the number of matches that resulted as a draw
267
     *
268
     * @return int The number of matches, respectively
269
     */
270 1
    public function getMatchesDraw()
271
    {
272 1
        return $this->matches_draw;
273
    }
274
275
    /**
276
     * Get the number of matches that the team has lost
277
     *
278
     * @return int The number of matches, respectively
279
     */
280 1
    public function getMatchesLost()
281
    {
282 1
        return $this->matches_lost;
283
    }
284
285
    /**
286
     * Get the URL that points to the team's list of matches
287
     *
288
     * @return string The team's list of matches
289
     */
290
    public function getMatchesURL()
291
    {
292
        return Service::getGenerator()->generate("match_by_team_list", array("team" => $this->getAlias()));
293
    }
294
295
    /**
296
     * Get the number of matches that the team has won
297
     *
298
     * @return int The number of matches, respectively
299
     */
300 1
    public function getMatchesWon()
301
    {
302 1
        return $this->matches_won;
303
    }
304
305
    /**
306
     * Get the members on the team
307
     *
308
     * @return Player[] The members on the team
309
     */
310 2
    public function getMembers()
311
    {
312 2
        $leader = $this->leader;
313 2
        $members = Player::getTeamMembers($this->id);
314
315
        usort($members, function ($a, $b) use ($leader) {
316
            // Leader always goes first
317 2
            if ($a->getId() == $leader) {
318 2
                return -1;
319
            }
320 1
            if ($b->getId() == $leader) {
321 1
                return 1;
322
            }
323
324
            // Sort the rest of the players alphabetically
325
            $sort = Player::getAlphabeticalSort();
326
327
            return $sort($a, $b);
328 2
        });
329
330 2
        return $members;
331
    }
332
333
    /**
334
     * Get the name of the team
335
     *
336
     * @return string The name of the team
337
     */
338 23
    public function getName()
339
    {
340 23
        if ($this->name === null) {
341
            return "None";
342
        }
343 23
        return $this->name;
344
    }
345
346
    /**
347
     * Get the name of the team, safe for use in your HTML
348
     *
349
     * @return string The name of the team
350
     */
351 1
    public function getEscapedName()
352
    {
353 1
        if (!$this->valid) {
354
            return "<em>None</em>";
355
        }
356 1
        return $this->escape($this->name);
357
    }
358
359
    /**
360
     * Get the number of members on the team
361
     *
362
     * @return int The number of members on the team
363
     */
364 2
    public function getNumMembers()
365
    {
366 2
        return $this->members;
367
    }
368
369
    /**
370
     * Get the total number of matches this team has played
371
     *
372
     * @return int The total number of matches this team has played
373
     */
374 2
    public function getNumTotalMatches()
375
    {
376 2
        return $this->matches_total;
377
    }
378
379
    /**
380
     * Get the rank category a team belongs too based on their ELO
381
     *
382
     * This value is always a multiple of 100 and less than or equal to 2000
383
     *
384
     * @return int The rank category a team belongs to
385
     */
386 1
    public function getRankValue()
387
    {
388 1
        return min(2000, floor($this->getElo() / 100) * 100);
389
    }
390
391
    /**
392
     * Get the HTML for an image with the rank symbol
393
     *
394
     * @return string The HTML for a rank image
395
     */
396 1
    public function getRankImageLiteral()
397
    {
398 1
        return '<div class="c-rank c-rank--' . $this->getRankValue() . '" aria-hidden="true"></div>';
399
    }
400
401
    /**
402
     * Increment the team's match count by one
403
     *
404
     * @param string $type The type of the match. Can be 'win', 'draw' or 'loss'
405
     */
406
    public function incrementMatchCount($type)
407
    {
408
        $this->changeMatchCount(1, $type);
409
    }
410
411
    /**
412
     * Check if a player is part of this team
413
     *
414
     * @param int $playerID The player to check
415
     *
416
     * @return bool True if the player belongs to this team
417
     */
418 2
    public function isMember($playerID)
419
    {
420 2
        $player = Player::get($playerID);
421
422 2
        return $player->getTeam()->isSameAs($this);
423
    }
424
425
    /**
426
     * Removes a member from the team
427
     *
428
     * @param  int  $id The id of the player to remove
429
     * @return void
430
     */
431 2 View Code Duplication
    public function removeMember($id)
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...
432
    {
433 2
        if (!$this->isMember($id)) {
434
            throw new Exception("The player is not a member of that team");
435
        }
436
437 2
        $player = Player::get($id);
438
439 2
        $player->update("team", null);
440 2
        $this->update('members', --$this->members);
441 2
    }
442
443
    /**
444
     * Update the description of the team
445
     *
446
     * @param  string $description The description of the team written as markdown
447
     * @return void
448
     */
449
    public function setDescription($description)
450
    {
451
        $this->update("description", $description);
452
    }
453
454
    /**
455
     * Change the status of the team
456
     *
457
     * @param  string $newStatus The new status of the team (open, closed, disabled or deleted)
458
     * @return self
459
     */
460
    public function setStatus($newStatus)
461
    {
462
        return $this->updateProperty($this->status, 'status', $newStatus);
463
    }
464
465
    /**
466
     * Change the leader of the team
467
     *
468
     * @param  int  $leader The ID of the new leader of the team
469
     * @return self
470
     */
471
    public function setLeader($leader)
472
    {
473
        return $this->updateProperty($this->leader, 'leader', $leader);
474
    }
475
476
    /**
477
     * Find if a specific match is the team's last one
478
     *
479
     * @param  int|Match $match The match
480
     * @return bool
481
     */
482 1
    public function isLastMatch($match)
483
    {
484
        // Find if this team participated in any matches after the current match
485 1
        return !Match::getQueryBuilder()
486 1
            ->with($this)
487 1
            ->where('status')->notEquals('deleted')
488 1
            ->where('time')->isAfter(Match::get($match)->getTimestamp())
489 1
            ->any();
490
    }
491
492
    /**
493
     * {@inheritdoc}
494
     */
495 1
    public function delete()
496
    {
497 1
        parent::delete();
498
499
        // Remove all the members of a deleted team
500 1
        $this->updateProperty($this->members, 'members', 0);
501 1
        $this->db->execute("UPDATE `players` SET `team` = NULL WHERE `team` = ?",
502 1
            $this->id);
503 1
    }
504
505
    /**
506
     * {@inheritdoc}
507
     */
508 12
    public function supportsMatchCount()
509
    {
510 12
        return true;
511
    }
512
513
    /**
514
     * Create a new team
515
     *
516
     * @param  string           $name        The name of the team
517
     * @param  int              $leader      The ID of the person creating the team, also the leader
518
     * @param  string           $avatar      The URL to the team's avatar
519
     * @param  string           $description The team's description
520
     * @param  string           $status      The team's status (open, closed, disabled or deleted)
521
     * @param  string|\TimeDate $created     The date the team was created
522
     *
523
     * @return Team   An object that represents the newly created team
524
     */
525 24
    public static function createTeam($name, $leader, $avatar, $description, $status = 'closed', $created = "now")
526
    {
527 24
        $created = TimeDate::from($created);
528
529 24
        $team = self::create(array(
530 24
            'name'         => $name,
531 24
            'alias'        => self::generateAlias($name),
532 24
            'description'  => $description,
533 24
            'elo'          => 1200,
534 24
            'activity'     => 0.00,
535 24
            'matches_won'  => 0,
536 24
            'matches_draw' => 0,
537 24
            'matches_lost' => 0,
538 24
            'members'      => 0,
539 24
            'avatar'       => $avatar,
540 24
            'leader'       => $leader,
541 24
            'status'       => $status,
542 24
            'created'      => $created->toMysql(),
543
        ));
544
545 23
        $team->addMember($leader);
546 23
        $team->getIdenticon($team->getId());
547
548 23
        return $team;
549
    }
550
551
    /**
552
     * Get all the teams in the database that are not disabled or deleted
553
     *
554
     * @return Team[] An array of Team IDs
555
     */
556
    public static function getTeams()
557
    {
558
        return self::arrayIdToModel(
559
            self::fetchIdsFrom(
560
                "status", array("disabled", "deleted"),
561
                true, "ORDER BY elo DESC"
562
            )
563
        );
564
    }
565
566
    /**
567
     * Get a single team by its name
568
     *
569
     * @param  string $name The team name to look for
570
     * @return Team
571
     */
572 1
    public static function getFromName($name)
573
    {
574 1
        $team = static::get(self::fetchIdFrom($name, 'name'));
575
576 1
        return $team->inject('name', $name);
577
    }
578
579
    /**
580
     * Alphabetical order function for use in usort (case-insensitive)
581
     * @return Closure The sort function
582
     */
583
    public static function getAlphabeticalSort()
584
    {
585 1
        return function (Team $a, Team $b) {
586
            return strcasecmp($a->getName(), $b->getName());
587 1
        };
588
    }
589
590
    /**
591
     * {@inheritdoc}
592
     */
593 1
    public static function getActiveStatuses()
594
    {
595 1
        return array('open', 'closed');
596
    }
597
598
    /**
599
     * Get a query builder for teams
600
     * @return QueryBuilder
601
     */
602 1 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...
603
    {
604 1
        return new QueryBuilder('Team', array(
605 1
            'columns' => array(
606
                'name'    => 'name',
607
                'elo'     => 'elo',
608
                'members' => 'members',
609
                'status'  => 'status'
610
            ),
611
            'name' => 'name',
612
        ));
613
    }
614
615
    /**
616
     * {@inheritdoc}
617
     */
618 1
    protected function isEditor($player)
619
    {
620 1
        return $player->isSameAs($this->getLeader());
621
    }
622
}
623