Completed
Push — master ( 01615d...a88e90 )
by Konstantinos
04:19
created

Conversation   B

Complexity

Total Complexity 39

Size/Duplication

Total Lines 451
Duplicated Lines 10.2 %

Coupling/Cohesion

Components 1
Dependencies 7

Test Coverage

Coverage 76.92%
Metric Value
wmc 39
lcom 1
cbo 7
dl 46
loc 451
ccs 90
cts 117
cp 0.7692
rs 8.2857

26 Methods

Rating   Name   Duplication   Size   Complexity  
A getSubject() 0 4 1
A getCreator() 0 4 1
A getRouteName() 0 4 1
A getActiveStatuses() 0 4 1
A assignResult() 0 7 1
A isCreator() 0 4 1
A isEditor() 0 4 1
A getLastActivity() 0 4 1
A updateLastActivity() 0 5 1
A setSubject() 0 4 1
A getLastMessage() 0 10 2
A markReadBy() 0 7 1
A markUnread() 0 8 1
A getName() 0 4 1
A getMembers() 0 10 1
A getPlayerIds() 0 18 3
A getTeamIds() 0 4 1
A createConversation() 0 14 2
A sendMessage() 0 8 1
A isReadBy() 7 7 1
A isMember() 0 17 4
B addMember() 0 32 5
A isTeamMember() 13 13 1
B removeMember() 26 30 3
A getWaitingForEmailIDs() 0 9 1
A getQueryBuilder() 0 10 1

How to fix   Duplicated Code   

Duplicated Code

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

Common duplication problems, and corresponding solutions are:

1
<?php
2
/**
3
 * This file contains functionality relating to the participants of a conversation message
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 discussion (group of messages)
11
 * @package    BZiON\Models
12
 */
13
class Conversation extends UrlModel implements NamedModel
14
{
15
    /**
16
     * The subject of the conversation
17
     * @var string
18
     */
19
    protected $subject;
20
21
    /**
22
     * The time of the last message to the conversation
23
     * @var TimeDate
24
     */
25
    protected $last_activity;
26
27
    /**
28
     * The id of the creator of the conversation
29
     * @var int
30
     */
31
    protected $creator;
32
33
    /**
34
     * The status of the conversation
35
     *
36
     * Can be 'active', 'disabled', 'deleted' or 'reported'
37
     * @var string
38
     */
39
    protected $status;
40
41
    /**
42
     * The name of the database table used for queries
43
     */
44
    const TABLE = "conversations";
45
46
    /**
47
     * {@inheritdoc}
48
     */
49 1
    protected function assignResult($conversation)
50
    {
51 1
        $this->subject = $conversation['subject'];
52 1
        $this->last_activity = TimeDate::fromMysql($conversation['last_activity']);
53 1
        $this->creator = $conversation['creator'];
54 1
        $this->status = $conversation['status'];
55 1
    }
56
57
    /**
58
     * Get the subject of the discussion
59
     *
60
     * @return string
61
     **/
62 1
    public function getSubject()
63
    {
64 1
        return $this->subject;
65
    }
66
67
    /**
68
     * Get the creator of the discussion
69
     *
70
     * @return Player
71
     */
72
    public function getCreator()
73
    {
74
        return Player::get($this->creator);
75
    }
76
77
    /**
78
     * Determine whether a player is the one who created the message conversation
79
     *
80
     * @param  int  $id The ID of the player to test for
81
     * @return bool
82
     */
83 1
    public function isCreator($id)
84
    {
85 1
        return ($this->creator == $id);
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91 1
    public function isEditor ($player)
92
    {
93 1
        return $this->isCreator($player->getId());
94
    }
95
96
    /**
97
     * Get the time when the conversation was most recently active
98
     *
99
     * @return TimeDate
100
     */
101 1
    public function getLastActivity()
102
    {
103 1
        return $this->last_activity;
104
    }
105
106
    /**
107
     * Update the conversation's last activity timestamp
108
     *
109
     * @return void
110
     */
111 1
    public function updateLastActivity()
112
    {
113 1
        $this->last_activity = TimeDate::now();
114 1
        $this->update('last_activity', $this->last_activity->toMysql(), 's');
115 1
    }
116
117
    /**
118
     * Update the conversation's subject
119
     *
120
     * @param  string $subject The new subject
121
     * @return self
122
     */
123
    public function setSubject($subject)
124
    {
125
        return $this->updateProperty($this->subject, 'subject', $subject, 's');
126
    }
127
128
    /**
129
     * Get the last message of the conversation
130
     *
131
     * @return Message
132
     */
133 1
    public function getLastMessage()
134
    {
135 1
        $ids = self::fetchIdsFrom('conversation_to', array($this->id), 'i', false, 'AND event_type IS null ORDER BY id DESC LIMIT 0,1', 'messages');
136
137 1
        if (!isset($ids[0])) {
138
            return Message::invalid();
139
        }
140
141 1
        return Message::get($ids[0]);
142
    }
143
144
    /**
145
     * Find whether the last message in the conversation has been read by a player
146
     *
147
     * @param  int     $playerId The ID of the player
148
     * @return bool
149
     */
150 View Code Duplication
    public function isReadBy($playerId)
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...
151
    {
152
        $query = $this->db->query("SELECT `read` FROM `player_conversations` WHERE `player` = ? AND `conversation` = ?",
153
            'ii', array($playerId, $this->id));
154
155
        return ($query[0]['read'] == 1);
156
    }
157
158
    /**
159
     * Mark the last message in the conversation as having been read by a player
160
     *
161
     * @param  int  $playerId The ID of the player
162
     * @return void
163
     */
164 1
    public function markReadBy($playerId)
165
    {
166 1
        $this->db->query(
167 1
            "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `conversation` = ? AND `read` = 0",
168 1
            'ii', array($playerId, $this->id)
169
        );
170 1
    }
171
172
    /**
173
     * Mark the last message in the conversation as unread by the conversation's members
174
     *
175
     * @param  int  $except The ID of a player to exclude
176
     * @return void
177
     */
178 1
    public function markUnread($except)
179
    {
180 1
        $this->db->query(
181 1
            "UPDATE `player_conversations` SET `read` = 0 WHERE `conversation` = ? AND `player` != ?",
182 1
            'ii',
183 1
            array($this->id, $except)
184
        );
185 1
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 1
    public static function getRouteName($action = 'show')
191
    {
192 1
        return "message_conversation_$action";
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198 1
    public static function getActiveStatuses()
199
    {
200 1
        return array('active', 'reported');
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206 1
    public function getName()
207
    {
208 1
        return $this->getSubject();
209
    }
210
211
    /**
212
     * Get a list containing each member of the conversation
213
     * @param  int|null $hide The ID of a player to ignore
214
     * @return Model[]  An array of players and teams
215
     */
216 1
    public function getMembers($hide = null)
217
    {
218 1
        $members = Player::arrayIdToModel($this->getPlayerIds($hide, true));
219 1
        usort($members, Player::getAlphabeticalSort());
220
221 1
        $teams = Team::arrayIdToModel($this->getTeamIds());
222 1
        usort($teams, Team::getAlphabeticalSort());
223
224 1
        return array_merge($members, $teams);
225
    }
226
227
    /**
228
     * Get a list containing the IDs of each member player of the conversation
229
     * @param  int|null  $hide     The ID of a player to ignore
230
     * @param  bool   $distinct Whether to only return players who were
231
     *                             specifically invited to the conversation, and
232
     *                             are not participating only as members of a team
233
     * @return integer[] An array of player IDs
234
     */
235 1
    public function getPlayerIds($hide = null, $distinct = false)
236
    {
237 1
        $additional_query = "WHERE `conversation` = ?";
238 1
        $types = "i";
239 1
        $params = array($this->id);
240
241 1
        if ($hide) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hide of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
242
            $additional_query .= " AND `player` != ?";
243
            $types .= "i";
244
            $params[] = $hide;
245
        }
246
247 1
        if ($distinct) {
248 1
            $additional_query .= " AND `distinct` = 1";
249
        }
250
251 1
        return parent::fetchIds($additional_query, $types, $params, "player_conversations", "player");
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (fetchIds() instead of getPlayerIds()). Are you sure this is correct? If so, you might want to change this to $this->fetchIds().

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

Consider the following code:

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

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

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

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

Loading history...
252
    }
253
254
    /**
255
     * Get a list containing the IDs of each member team of the conversation
256
     *
257
     * @return integer[] An array of team IDs
258
     */
259 1
    public function getTeamIds()
260
    {
261 1
        return parent::fetchIds("WHERE `conversation` = ?", "i", $this->id, "team_conversations", "team");
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...
Comprehensibility Bug introduced by
It seems like you call parent on a different method (fetchIds() instead of getTeamIds()). Are you sure this is correct? If so, you might want to change this to $this->fetchIds().

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

Consider the following code:

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

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

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

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

Loading history...
262
    }
263
264
    /**
265
     * Create a new message conversation
266
     **
267
     * @param  string $subject   The subject of the conversation
268
     * @param  int    $creatorId The ID of the player who created the conversation
269
     * @param  array  $members   A list of Models representing the conversation's members
270
     * @return Conversation  An object that represents the created conversation
271
     */
272 1
    public static function createConversation($subject, $creatorId, $members = array())
273
    {
274 1
        $conversation = self::create(array(
275 1
            'subject' => $subject,
276 1
            'creator' => $creatorId,
277 1
            'status'  => "active",
278 1
        ), 'sis', 'last_activity');
279
280 1
        foreach ($members as $member) {
281 1
            $conversation->addMember($member);
282
        }
283
284 1
        return $conversation;
285
    }
286
287
    /**
288
     * Send a new message to the conversation's members
289
     * @param  Player  $from    The sender
290
     * @param  string  $message The body of the message
291
     * @param  string  $status  The status of the message - can be 'visible', 'hidden', 'deleted' or 'reported'
292
     * @return Message An object that represents the sent message
293
     */
294 1
    public function sendMessage($from, $message, $status = 'visible')
295
    {
296 1
        $message = Message::sendMessage($this->getId(), $from->getId(), $message, $status);
297
298 1
        $this->updateLastActivity();
299
300 1
        return $message;
301
    }
302
303
    /**
304
     * Checks if a player or team belongs in the conversation
305
     * @param  Player|Team $member The player or team to check
306
     * @param  bool Whether to only return true if a player is specifically a
307
     *              member of the conversation, not just a member of one of the
308 1
     *              conversation's teams (ignored if $member is a Team)
309
     * @return bool True if the given object belongs in the conversation, false if they don't
310 1
     */
311
    public function isMember($member, $distinct = false)
312 1
    {
313 1
        $type = ($member instanceof Player) ? 'player' : 'team';
314
315 1
        if ($type === 'player' and $distinct) {
316
            $distinctQuery = 'AND `distinct` = 1';
317
        } else {
318
            $distinctQuery = '';
319
        }
320
321
        $result = $this->db->query(
322
            "SELECT 1 FROM `{$type}_conversations` WHERE `conversation` = ?
323
              AND `$type` = ? $distinctQuery",
324 1
            "ii", array($this->id, $member->getId()));
325
326 1
        return count($result) > 0;
327
    }
328
329 1
    /**
330
     * Add a member to the discussion
331 1
     *
332 1
     * @param  Player|Team $member   The member to add
333 1
     * @param  bool        $distinct Whether to add the member as a distinct
334
     *                               player (ignored for teams)
335
     * @return void
336
     */
337
    public function addMember($member, $distinct = true)
338
    {
339
        if ($member instanceof Player) {
340
            // Mark individual players as distinct by creating or updating the
341
            // entry on the table
342
            if ($distinct) {
343
                $query = "INSERT INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 1)
344
                  ON DUPLICATE KEY UPDATE `distinct` = 1";
345
            } else {
346
                $query = "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`, `read`) VALUES (?, ?, 0, 1)";
347
            }
348
349
            $this->db->query($query, "ii", array($this->getId(), $member->getId()));
350
        } elseif ($member instanceof Team) {
351
            // Add the team to the team_conversations table...
352
            $this->db->query(
353 1
                "INSERT INTO `team_conversations` (`conversation`, `team`) VALUES (?, ?)",
354
                "ii",
355
                array($this->getId(), $member->getId())
356
            );
357
358
            // ...and each of its members in the player_conversations table as
359
            // non-distinct (unless they were already there)
360
            foreach ($member->getMembers() as $player) {
361
                $this->db->query(
362
                    "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 0)",
363
                    "ii",
364
                    array($this->getId(), $player->getId())
365
                );
366
            }
367
        }
368
    }
369
370
    /**
371
     * Find out if a player belongs to any of the conversation's teams
372
     *
373
     * This does not take into account whether the player is a distinct member
374
     * of the conversation (i.e. they have been invited separately)
375
     *
376
     * @param  Player $member The player to check
377
     * @return bool
378
     */
379 1 View Code Duplication
    public function isTeamMember($member)
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...
380
    {
381 1
        $query = $this->db->query(
382 1
            "SELECT COUNT(*) as c FROM players
383 1
                INNER JOIN teams ON teams.id = players.team
384 1
                INNER JOIN team_conversations ON team_conversations.team = teams.id
385 1
                WHERE team_conversations.conversation = ?
386 1
                AND players.id = ?
387
                LIMIT 1", "ii", array($this->getId(), $member->getId())
388
        );
389
390
        return $query[0]['c'] > 0;
391
    }
392
393 1
    /**
394
     * Remove a member from the discussion
395 1
     *
396
     * @todo Properly leave the conversation even when belonging to a team?
397
     *
398
     * @param  Player|Team $member The member to remove
399 1
     * @return void
400
     */
401
    public function removeMember($member)
402
    {
403
        if ($member instanceof Player) {
404 View Code Duplication
            if ($this->isTeamMember($member)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
405
                // The player is already member of a team in the conversation,
406
                // don't remove them entirely
407
                $this->db->query(
408
                    "UPDATE `player_conversations` SET `distinct` = 0 WHERE `conversation` = ? AND `player` = ?", "ii", array($this->getId(), $member->getId())
409
                );
410
            } else {
411
                $this->db->query(
412
                    "DELETE FROM `player_conversations` WHERE `conversation` = ? AND `player` = ?", "ii", array($this->getId(), $member->getId())
413
                );
414
            }
415 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
416
            $this->db->query(
417
                "DELETE `player_conversations` FROM `player_conversations`
418
                LEFT JOIN `players` ON players.id = player_conversations.player
419
                WHERE player_conversations.conversation = ?
420
                AND players.team = ?
421
                AND player_conversations.distinct = 0", "ii", array($this->getId(), $member->getId())
422
            );
423
424
            $this->db->query(
425
                "DELETE FROM `team_conversations`
426
                WHERE conversation = ?
427
                AND team = ?", "ii", array($this->getId(), $member->getId())
428
            );
429
        }
430
    }
431
432
    /**
433
     * Find out which members of the conversation should receive an e-mail after a new
434
     * message has been sent
435
     *
436
     * @param  int   $except The ID of a player who won't receive an e-mail (e.g. message author)
437
     * @return int[] A player ID list
438
     */
439
    public function getWaitingForEmailIDs($except)
440
    {
441
        return $this->fetchIds(
442
            'LEFT JOIN players ON pg.player = players.id WHERE pg.conversation = ? AND pg.read = 1 AND pg.player != ?  AND players.verified = 1 AND players.receives != "nothing"',
443
            'ii',
444
            array($this->id, $except),
445
            'player_conversations AS pg',
446
            'pg.player');
447
    }
448
449
    /**
450
     * Get a query builder for conversations
451
     * @return ConversationQueryBuilder
452
     */
453
    public static function getQueryBuilder()
454
    {
455
        return new ConversationQueryBuilder('Conversation', array(
456
            'columns' => array(
457
                'last_activity' => 'last_activity',
458
                'status' => 'status'
459
            ),
460
            'name' => 'subject',
461
        ));
462
    }
463
}
464