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
|
1 |
|
public function getCreator() |
73
|
|
|
{ |
74
|
1 |
|
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
|
|
|
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(), '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
|
|
|
public function getLastMessage() |
134
|
|
|
{ |
135
|
|
|
$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
|
|
|
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
|
|
View Code Duplication |
public function isReadBy($playerId) |
|
|
|
|
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 the members of one of the conversation's teams that don't belong in |
229
|
|
|
* the conversation |
230
|
|
|
* |
231
|
|
|
* @todo Use Model::createFromDatabaseResults() |
232
|
|
|
* @param Team $team The team to check |
233
|
|
|
* @return Player[] |
234
|
|
|
*/ |
235
|
|
|
public function getMissingTeamMembers(Team $team) |
236
|
|
|
{ |
237
|
|
|
$query = "SELECT players.id AS id FROM players |
238
|
|
|
WHERE players.team = ? |
239
|
|
|
AND players.id NOT IN ( |
240
|
|
|
SELECT player_conversations.player FROM player_conversations |
241
|
|
|
WHERE player_conversations.conversation = ? |
242
|
|
|
)"; |
243
|
|
|
|
244
|
|
|
$results = $this->db->query($query, "ii", array($team->getId(), $this->id)); |
245
|
|
|
|
246
|
|
|
return Player::arrayIdToModel(array_column($results, 'id')); |
|
|
|
|
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Get a list containing the IDs of each member player of the conversation |
251
|
|
|
* @param int|null $hide The ID of a player to ignore |
252
|
|
|
* @param bool $distinct Whether to only return players who were |
253
|
|
|
* specifically invited to the conversation, and |
254
|
|
|
* are not participating only as members of a team |
255
|
|
|
* @return int[] An array of player IDs |
256
|
|
|
*/ |
257
|
1 |
|
public function getPlayerIds($hide = null, $distinct = false) |
258
|
|
|
{ |
259
|
1 |
|
$additional_query = "WHERE `conversation` = ?"; |
260
|
1 |
|
$types = "i"; |
261
|
1 |
|
$params = array($this->id); |
262
|
|
|
|
263
|
1 |
|
if ($hide) { |
|
|
|
|
264
|
|
|
$additional_query .= " AND `player` != ?"; |
265
|
|
|
$types .= "i"; |
266
|
|
|
$params[] = $hide; |
267
|
|
|
} |
268
|
|
|
|
269
|
1 |
|
if ($distinct) { |
270
|
1 |
|
$additional_query .= " AND `distinct` = 1"; |
271
|
|
|
} |
272
|
|
|
|
273
|
1 |
|
return parent::fetchIds($additional_query, $types, $params, "player_conversations", "player"); |
|
|
|
|
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Get a list containing the IDs of each member team of the conversation |
278
|
|
|
* |
279
|
|
|
* @return int[] An array of team IDs |
280
|
|
|
*/ |
281
|
1 |
|
public function getTeamIds() |
282
|
|
|
{ |
283
|
1 |
|
return parent::fetchIds("WHERE `conversation` = ?", "i", $this->id, "team_conversations", "team"); |
|
|
|
|
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
/** |
287
|
|
|
* Create a new message conversation |
288
|
|
|
** |
289
|
|
|
* @param string $subject The subject of the conversation |
290
|
|
|
* @param int $creatorId The ID of the player who created the conversation |
291
|
|
|
* @param array $members A list of Models representing the conversation's members |
292
|
|
|
* @return Conversation An object that represents the created conversation |
293
|
|
|
*/ |
294
|
1 |
|
public static function createConversation($subject, $creatorId, $members = array()) |
295
|
|
|
{ |
296
|
1 |
|
$conversation = self::create(array( |
297
|
1 |
|
'subject' => $subject, |
298
|
1 |
|
'creator' => $creatorId, |
299
|
1 |
|
'status' => "active", |
300
|
1 |
|
), 'sis', 'last_activity'); |
301
|
|
|
|
302
|
1 |
|
Database::getInstance()->startTransaction(); |
303
|
1 |
|
foreach ($members as $member) { |
304
|
1 |
|
$conversation->addMember($member); |
305
|
|
|
} |
306
|
1 |
|
Database::getInstance()->finishTransaction(); |
307
|
|
|
|
308
|
1 |
|
return $conversation; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Send a new message to the conversation's members |
313
|
|
|
* @param Player $from The sender |
314
|
|
|
* @param string $message The body of the message |
315
|
|
|
* @param string $status The status of the message - can be 'visible', 'hidden', 'deleted' or 'reported' |
316
|
|
|
* @return Message An object that represents the sent message |
317
|
|
|
*/ |
318
|
1 |
|
public function sendMessage($from, $message, $status = 'visible') |
319
|
|
|
{ |
320
|
1 |
|
$message = Message::sendMessage($this->getId(), $from->getId(), $message, $status); |
321
|
|
|
|
322
|
1 |
|
$this->updateLastActivity(); |
323
|
|
|
|
324
|
1 |
|
return $message; |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Checks if a player or team belongs in the conversation |
329
|
|
|
* @param Player|Team $member The player or team to check |
330
|
|
|
* @param bool Whether to only return true if a player is specifically a |
331
|
|
|
* member of the conversation, not just a member of one of the |
332
|
|
|
* conversation's teams (ignored if $member is a Team) |
333
|
|
|
* @return bool True if the given object belongs in the conversation, false if they don't |
334
|
|
|
*/ |
335
|
1 |
|
public function isMember($member, $distinct = false) |
336
|
|
|
{ |
337
|
1 |
|
$type = ($member instanceof Player) ? 'player' : 'team'; |
338
|
|
|
|
339
|
1 |
|
if ($type === 'player' and $distinct) { |
340
|
|
|
$distinctQuery = 'AND `distinct` = 1'; |
341
|
|
|
} else { |
342
|
1 |
|
$distinctQuery = ''; |
343
|
|
|
} |
344
|
|
|
|
345
|
1 |
|
$result = $this->db->query( |
346
|
1 |
|
"SELECT 1 FROM `{$type}_conversations` WHERE `conversation` = ? |
347
|
1 |
|
AND `$type` = ? $distinctQuery", |
348
|
1 |
|
"ii", array($this->id, $member->getId())); |
349
|
|
|
|
350
|
1 |
|
return count($result) > 0; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Add a member to the discussion |
355
|
|
|
* |
356
|
|
|
* @param Player|Team $member The member to add |
357
|
|
|
* @param bool $distinct Whether to add the member as a distinct |
358
|
|
|
* player (ignored for teams) |
359
|
|
|
* @return void |
360
|
|
|
*/ |
361
|
1 |
|
public function addMember($member, $distinct = true) |
362
|
|
|
{ |
363
|
1 |
|
if ($member instanceof Player) { |
364
|
|
|
// Mark individual players as distinct by creating or updating the |
365
|
|
|
// entry on the table |
366
|
1 |
|
if ($distinct) { |
367
|
|
|
$query = "INSERT INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 1) |
368
|
1 |
|
ON DUPLICATE KEY UPDATE `distinct` = 1"; |
369
|
|
|
} else { |
370
|
|
|
$query = "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`, `read`) VALUES (?, ?, 0, 1)"; |
371
|
|
|
} |
372
|
|
|
|
373
|
1 |
|
$this->db->query($query, "ii", array($this->getId(), $member->getId())); |
374
|
|
|
} elseif ($member instanceof Team) { |
375
|
|
|
// Add the team to the team_conversations table... |
376
|
|
|
$this->db->query( |
377
|
|
|
"INSERT IGNORE INTO `team_conversations` (`conversation`, `team`) VALUES (?, ?)", |
378
|
|
|
"ii", |
379
|
|
|
array($this->getId(), $member->getId()) |
380
|
|
|
); |
381
|
|
|
|
382
|
|
|
// ...and each of its members in the player_conversations table as |
383
|
|
|
// non-distinct (unless they were already there) |
384
|
|
|
foreach ($member->getMembers() as $player) { |
385
|
|
|
$this->db->query( |
386
|
|
|
"INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 0)", |
387
|
|
|
"ii", |
388
|
|
|
array($this->getId(), $player->getId()) |
389
|
|
|
); |
390
|
|
|
} |
391
|
|
|
} |
392
|
1 |
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Find out if a player belongs to any of the conversation's teams |
396
|
|
|
* |
397
|
|
|
* This does not take into account whether the player is a distinct member |
398
|
|
|
* of the conversation (i.e. they have been invited separately) |
399
|
|
|
* |
400
|
|
|
* @param Player $member The player to check |
401
|
|
|
* @return bool |
402
|
|
|
*/ |
403
|
1 |
View Code Duplication |
public function isTeamMember($member) |
|
|
|
|
404
|
|
|
{ |
405
|
1 |
|
$query = $this->db->query( |
406
|
|
|
"SELECT COUNT(*) as c FROM players |
407
|
|
|
INNER JOIN teams ON teams.id = players.team |
408
|
|
|
INNER JOIN team_conversations ON team_conversations.team = teams.id |
409
|
|
|
WHERE team_conversations.conversation = ? |
410
|
|
|
AND players.id = ? |
411
|
1 |
|
LIMIT 1", "ii", array($this->getId(), $member->getId()) |
412
|
|
|
); |
413
|
|
|
|
414
|
1 |
|
return $query[0]['c'] > 0; |
415
|
|
|
} |
416
|
|
|
|
417
|
|
|
/** |
418
|
|
|
* Remove a member from the discussion |
419
|
|
|
* |
420
|
|
|
* @param Player|Team $member The member to remove |
421
|
|
|
* @return void |
422
|
|
|
*/ |
423
|
|
|
public function removeMember($member) |
424
|
|
|
{ |
425
|
|
|
if ($member instanceof Player) { |
426
|
|
|
if ($this->isTeamMember($member) && $member->getTeam()->getLeader()->isSameAs($member)) { |
427
|
|
|
// The player is the leader of a team in the conversation, don't |
428
|
|
|
// remove them entirely |
429
|
|
|
$this->db->query( |
430
|
|
|
"UPDATE `player_conversations` SET `distinct` = 0 WHERE `conversation` = ? AND `player` = ?", "ii", array($this->getId(), $member->getId()) |
431
|
|
|
); |
432
|
|
|
} else { |
433
|
|
|
$this->db->query( |
434
|
|
|
"DELETE FROM `player_conversations` WHERE `conversation` = ? AND `player` = ?", "ii", array($this->getId(), $member->getId()) |
435
|
|
|
); |
436
|
|
|
} |
437
|
|
|
} else { |
438
|
|
|
$this->db->query( |
439
|
|
|
"DELETE `player_conversations` FROM `player_conversations` |
440
|
|
|
LEFT JOIN `players` ON players.id = player_conversations.player |
441
|
|
|
WHERE player_conversations.conversation = ? |
442
|
|
|
AND players.team = ? |
443
|
|
|
AND player_conversations.distinct = 0", "ii", array($this->getId(), $member->getId()) |
444
|
|
|
); |
445
|
|
|
|
446
|
|
|
$this->db->query( |
447
|
|
|
"DELETE FROM `team_conversations` |
448
|
|
|
WHERE conversation = ? |
449
|
|
|
AND team = ?", "ii", array($this->getId(), $member->getId()) |
450
|
|
|
); |
451
|
|
|
} |
452
|
|
|
} |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* Find out which members of the conversation should receive an e-mail after a new |
456
|
|
|
* message has been sent |
457
|
|
|
* |
458
|
|
|
* @param int $except The ID of a player who won't receive an e-mail |
459
|
|
|
* (e.g. message author) |
460
|
|
|
* @param bool $read Whether to only send e-mails to players who have |
461
|
|
|
* read all the previous messages in the conversation |
462
|
|
|
* @return int[] A player ID list |
463
|
|
|
*/ |
464
|
1 |
|
public function getWaitingForEmailIDs($except, $read = true) |
465
|
|
|
{ |
466
|
1 |
|
$readQuery = ($read) ? 'AND pg.read = 1' : ''; |
467
|
|
|
|
468
|
1 |
|
return $this->fetchIds( |
469
|
|
|
"LEFT JOIN players ON pg.player = players.id |
470
|
|
|
WHERE pg.conversation = ? |
471
|
1 |
|
$readQuery |
472
|
|
|
AND pg.player != ? |
473
|
|
|
AND players.verified = 1 |
474
|
1 |
|
AND players.receives != \"nothing\"", |
475
|
1 |
|
'ii', |
476
|
1 |
|
array($this->id, $except), |
477
|
1 |
|
'player_conversations AS pg', |
478
|
1 |
|
'pg.player'); |
479
|
|
|
} |
480
|
|
|
|
481
|
|
|
/** |
482
|
|
|
* Get a query builder for conversations |
483
|
|
|
* @return ConversationQueryBuilder |
484
|
|
|
*/ |
485
|
1 |
|
public static function getQueryBuilder() |
486
|
|
|
{ |
487
|
1 |
|
return new ConversationQueryBuilder('Conversation', array( |
488
|
|
|
'columns' => array( |
489
|
|
|
'last_activity' => 'last_activity', |
490
|
|
|
'status' => 'status' |
491
|
1 |
|
), |
492
|
|
|
'name' => 'subject', |
493
|
|
|
)); |
494
|
|
|
} |
495
|
|
|
} |
496
|
|
|
|
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.