Completed
Push — feature/player-list ( ee00f1...3478af )
by Vladimir
04:05
created

Conversation::getCreator()   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
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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
    public function isCreator($id)
84
    {
85
        return $this->creator == $id;
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function isEditor($player)
92
    {
93
        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
    public function getLastActivity()
102
    {
103
        return $this->last_activity->copy();
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());
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);
126
    }
127
128
    /**
129
     * Get the last message of the conversation
130
     *
131
     * @return Message
132
     */
133
    public function getLastMessage()
134
    {
135
        $ids = self::fetchIdsFrom('conversation_to', array($this->id), false, 'AND event_type IS null ORDER BY id DESC LIMIT 0,1', 'messages');
136
137
        if (!isset($ids[0])) {
138
            return Message::invalid();
139
        }
140
141
        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
    public function isReadBy($playerId)
151
    {
152
        $query = $this->db->query("SELECT `read` FROM `player_conversations` WHERE `player` = ? AND `conversation` = ?",
153
            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->execute(
167 1
            "UPDATE `player_conversations` SET `read` = 1 WHERE `player` = ? AND `conversation` = ? AND `read` = 0",
168 1
            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
    public function markUnread($except)
179
    {
180
        $this->db->execute(
181
            "UPDATE `player_conversations` SET `read` = 0 WHERE `conversation` = ? AND `player` != ?",
182
            array($this->id, $except)
183
        );
184
    }
185
186
    /**
187
     * {@inheritdoc}
188
     */
189 1
    public static function getRouteName($action = 'show')
190
    {
191 1
        return "message_conversation_$action";
192
    }
193
194
    /**
195
     * {@inheritdoc}
196
     */
197 1
    public static function getActiveStatuses()
198
    {
199 1
        return array('active', 'reported');
200
    }
201
202
    /**
203
     * {@inheritdoc}
204
     */
205
    public function getName()
206
    {
207
        return $this->getSubject();
208
    }
209
210
    /**
211
     * Get a list containing each member of the conversation
212
     * @param  int|null $hide The ID of a player to ignore
213
     * @return Model[]  An array of players and teams
214
     */
215
    public function getMembers($hide = null)
216
    {
217
        $members = Player::arrayIdToModel($this->getPlayerIds($hide, true));
218
        usort($members, Player::getAlphabeticalSort());
219
220
        $teams = Team::arrayIdToModel($this->getTeamIds());
221
        usort($teams, Team::getAlphabeticalSort());
222
223
        return array_merge($members, $teams);
224
    }
225
226
    /**
227
     * Get the members of one of the conversation's teams that don't belong in
228
     * the conversation
229
     *
230
     * @todo   Use Model::createFromDatabaseResults()
231
     * @param  Team $team The team to check
232
     * @return Player[]
233
     */
234
    public function getMissingTeamMembers(Team $team)
235
    {
236
        $query = "SELECT players.id AS id FROM players
237
            WHERE players.team = ?
238
            AND players.id NOT IN (
239
              SELECT player_conversations.player FROM player_conversations
240
              WHERE player_conversations.conversation = ?
241
            )";
242
243
        $results = $this->db->query($query, array($team->getId(), $this->id));
244
245
        return Player::arrayIdToModel(array_column($results, 'id'));
246
    }
247
248
    /**
249
     * Get a list containing the IDs of each member player of the conversation
250
     * @param  int|null  $hide     The ID of a player to ignore
251
     * @param  bool   $distinct Whether to only return players who were
252
     *                             specifically invited to the conversation, and
253
     *                             are not participating only as members of a team
254
     * @return int[] An array of player IDs
255
     */
256
    public function getPlayerIds($hide = null, $distinct = false)
257
    {
258
        $additional_query = "WHERE `conversation` = ?";
259
        $params = array($this->id);
260
261
        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...
262
            $additional_query .= " AND `player` != ?";
263
            $params[] = $hide;
264
        }
265
266
        if ($distinct) {
267
            $additional_query .= " AND `distinct` = 1";
268
        }
269
270
        return self::fetchIds($additional_query, $params, "player_conversations", "player");
271
    }
272
273
    /**
274
     * Get a list containing the IDs of each member team of the conversation
275
     *
276
     * @return int[] An array of team IDs
277
     */
278
    public function getTeamIds()
279
    {
280
        return self::fetchIds("WHERE `conversation` = ?", $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...
281
    }
282
283
    /**
284
     * Create a new message conversation
285
     **
286
     * @param  string $subject   The subject of the conversation
287
     * @param  int    $creatorId The ID of the player who created the conversation
288
     * @param  array  $members   A list of Models representing the conversation's members
289
     * @return Conversation  An object that represents the created conversation
290
     */
291 1
    public static function createConversation($subject, $creatorId, $members = array())
292
    {
293 1
        $conversation = self::create(array(
294 1
            'subject' => $subject,
295 1
            'creator' => $creatorId,
296 1
            'status'  => "active",
297 1
        ), 'last_activity');
298
299 1
        Database::getInstance()->startTransaction();
300 1
        foreach ($members as $member) {
301 1
            $conversation->addMember($member);
302
        }
303 1
        Database::getInstance()->finishTransaction();
304
305 1
        return $conversation;
306
    }
307
308
    /**
309
     * Send a new message to the conversation's members
310
     * @param  Player  $from    The sender
311
     * @param  string  $message The body of the message
312
     * @param  string  $status  The status of the message - can be 'visible', 'hidden', 'deleted' or 'reported'
313
     * @return Message An object that represents the sent message
314
     */
315 1
    public function sendMessage($from, $message, $status = 'visible')
316
    {
317 1
        $message = Message::sendMessage($this->getId(), $from->getId(), $message, $status);
318
319 1
        $this->updateLastActivity();
320
321 1
        return $message;
322
    }
323
324
    /**
325
     * Checks if a player or team belongs in the conversation
326
     * @param  Player|Team $member The player or team to check
327
     * @param  bool $distinct Whether to only return true if a player is
328
     *                        specifically a member of the conversation, not
329
     *                        just a member of one of the conversation's teams (ignored if $member is a Team)
330
     * @return bool True if the given object belongs in the conversation, false if they don't
331
     */
332 1
    public function isMember($member, $distinct = false)
333
    {
334 1
        $type = ($member instanceof Player) ? 'player' : 'team';
335
336 1
        if ($type === 'player' and $distinct) {
337
            $distinctQuery = 'AND `distinct` = 1';
338
        } else {
339 1
            $distinctQuery = '';
340
        }
341
342 1
        $result = $this->db->query(
343 1
            "SELECT 1 FROM `{$type}_conversations` WHERE `conversation` = ?
344 1
              AND `$type` = ? $distinctQuery",
345 1
            array($this->id, $member->getId()));
346
347 1
        return count($result) > 0;
348
    }
349
350
    /**
351
     * Add a member to the discussion
352
     *
353
     * @param  Player|Team $member   The member to add
354
     * @param  bool        $distinct Whether to add the member as a distinct
355
     *                               player (ignored for teams)
356
     * @return void
357
     */
358 1
    public function addMember($member, $distinct = true)
359
    {
360 1
        if ($member instanceof Player) {
361
            // Mark individual players as distinct by creating or updating the
362
            // entry on the table
363 1
            if ($distinct) {
364 1
                $query = "INSERT INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 1)
365
                  ON DUPLICATE KEY UPDATE `distinct` = 1";
366
            } else {
367
                $query = "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`, `read`) VALUES (?, ?, 0, 1)";
368
            }
369
370 1
            $this->db->execute($query, array($this->getId(), $member->getId()));
371
        } elseif ($member instanceof Team) {
372
            // Add the team to the team_conversations table...
373
            $this->db->execute(
374
                "INSERT IGNORE INTO `team_conversations` (`conversation`, `team`) VALUES (?, ?)",
375
                array($this->getId(), $member->getId())
376
            );
377
378
            // ...and each of its members in the player_conversations table as
379
            // non-distinct (unless they were already there)
380
            foreach ($member->getMembers() as $player) {
381
                $this->db->execute(
382
                    "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 0)",
383
                    array($this->getId(), $player->getId())
384
                );
385
            }
386
        }
387 1
    }
388
389
    /**
390
     * Find out if a player belongs to any of the conversation's teams
391
     *
392
     * This does not take into account whether the player is a distinct member
393
     * of the conversation (i.e. they have been invited separately)
394
     *
395
     * @param  Player $member The player to check
396
     * @return bool
397
     */
398
    public function isTeamMember($member)
399
    {
400
        $query = $this->db->query(
401
            "SELECT COUNT(*) as c FROM players
402
                INNER JOIN teams ON teams.id = players.team
403
                INNER JOIN team_conversations ON team_conversations.team = teams.id
404
                WHERE team_conversations.conversation = ?
405
                AND players.id = ?
406
                LIMIT 1", array($this->getId(), $member->getId())
407
        );
408
409
        return $query[0]['c'] > 0;
410
    }
411
412
    /**
413
     * Remove a member from the discussion
414
     *
415
     * @param  Player|Team $member The member to remove
416
     * @return void
417
     */
418
    public function removeMember($member)
419
    {
420
        if ($member instanceof Player) {
421
            if ($this->isTeamMember($member) && $member->getTeam()->getLeader()->isSameAs($member)) {
422
                // The player is the leader of a team in the conversation, don't
423
                // remove them entirely
424
                $this->db->execute(
425
                    "UPDATE `player_conversations` SET `distinct` = 0 WHERE `conversation` = ? AND `player` = ?", array($this->getId(), $member->getId())
426
                );
427
            } else {
428
                $this->db->execute(
429
                    "DELETE FROM `player_conversations` WHERE `conversation` = ? AND `player` = ?", array($this->getId(), $member->getId())
430
                );
431
            }
432
        } else {
433
            $this->db->execute(
434
                "DELETE `player_conversations` FROM `player_conversations`
435
                LEFT JOIN `players` ON players.id = player_conversations.player
436
                WHERE player_conversations.conversation = ?
437
                AND players.team = ?
438
                AND player_conversations.distinct = 0", array($this->getId(), $member->getId())
439
            );
440
441
            $this->db->execute(
442
                "DELETE FROM `team_conversations`
443
                WHERE conversation = ?
444
                AND team = ?", array($this->getId(), $member->getId())
445
            );
446
        }
447
    }
448
449
    /**
450
     * Find out which members of the conversation should receive an e-mail after a new
451
     * message has been sent
452
     *
453
     * @param  int   $except The ID of a player who won't receive an e-mail
454
     *                       (e.g. message author)
455
     * @param  bool  $read   Whether to only send e-mails to players who have
456
     *                       read all the previous messages in the conversation
457
     * @return int[] A player ID list
458
     */
459
    public function getWaitingForEmailIDs($except, $read = true)
460
    {
461
        $readQuery = ($read) ? 'AND pg.read = 1' : '';
462
463
        return $this->fetchIds(
464
            "LEFT JOIN players ON pg.player = players.id
465
                WHERE pg.conversation = ?
466
                $readQuery
467
                AND pg.player != ?
468
                AND players.verified = 1
469
                AND players.receives != \"nothing\"",
470
            array($this->id, $except),
471
            'player_conversations AS pg',
472
            'pg.player');
473
    }
474
475
    /**
476
     * Get a query builder for conversations
477
     * @return ConversationQueryBuilder
478
     */
479 1
    public static function getQueryBuilder()
480
    {
481 1
        return new ConversationQueryBuilder('Conversation', array(
482 1
            'columns' => array(
483
                'last_activity' => 'last_activity',
484
                'status'        => 'status'
485
            ),
486
            'name' => 'subject',
487
        ));
488
    }
489
}
490