Completed
Push — master ( 9f28c7...55eb39 )
by Konstantinos
06:26
created

Conversation::getConversations()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1
Metric Value
dl 0
loc 9
ccs 4
cts 4
cp 1
rs 9.6666
cc 1
eloc 4
nc 1
nop 1
crap 1
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
    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 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
     * @return bool True if the given object belongs in the conversation, false if they don't
307
     */
308
    public function isMember($member)
309 1
    {
310
        $type = ($member instanceof Player) ? 'player' : 'team';
311
312
        $result = $this->db->query("SELECT 1 FROM `{$type}_conversations` WHERE `conversation` = ?
313 1
                                    AND `$type` = ?", "ii", array($this->id, $member->getId()));
314 1
315
        return count($result) > 0;
316 1
    }
317
318
    /**
319
     * Add a member to the discussion
320
     *
321
     * @param  Player|Team $member The member  to add
322
     * @return void
323
     */
324 1
    public function addMember($member)
325
    {
326 1
        if ($member instanceof Player) {
327
            // Mark individual players as distinct by creating or updating the
328 1
            // entry on the table
329 1
            $this->db->query(
330
                "INSERT INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 1)
331 1
                    ON DUPLICATE KEY UPDATE `distinct` = 1",
332
                "ii",
333
                array($this->getId(), $member->getId())
334
            );
335
        } elseif ($member instanceof Team) {
336
            // Add the team to the team_conversations table...
337
            $this->db->query(
338
                "INSERT INTO `team_conversations` (`conversation`, `team`) VALUES (?, ?)",
339
                "ii",
340 1
                array($this->getId(), $member->getId())
341
            );
342 1
343
            // ...and each of its members in the player_conversations table as
344
            // non-distinct (unless they were already there)
345 1
            foreach ($member->getMembers() as $player) {
346
                $this->db->query(
347 1
                    "INSERT IGNORE INTO `player_conversations` (`conversation`, `player`, `distinct`) VALUES (?, ?, 0)",
348 1
                    "ii",
349 1
                    array($this->getId(), $player->getId())
350
                );
351
            }
352
        }
353
    }
354
355
    /**
356
     * Remove a member from the discussion
357
     *
358
     * @todo
359
     *
360
     * @param  Player|Team $member The member to remove
361
     * @return void
362
     */
363
    public function removeMember($member)
364
    {
365
        if ($member instanceof Player) {
366
            $this->db->query("DELETE FROM `player_conversations` WHERE `conversation` = ? AND `player` = ?", "ii", array($this->getId(), $member->getId()));
367
        } else {
368
            throw new Exception("Not implemented yet");
369 1
        }
370
    }
371
372
    /**
373
     * Find out which members of the conversation should receive an e-mail after a new
374
     * message has been sent
375
     *
376
     * @param  int   $except The ID of a player who won't receive an e-mail (e.g. message author)
377
     * @return int[] A player ID list
378
     */
379
    public function getWaitingForEmailIDs($except)
380
    {
381
        return $this->fetchIds(
382
            '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"',
383
            'ii',
384
            array($this->id, $except),
385
            'player_conversations AS pg',
386
            'pg.player');
387
    }
388
389
    /**
390
     * Get a query builder for conversations
391
     * @return ConversationQueryBuilder
392
     */
393
    public static function getQueryBuilder()
394
    {
395 1
        return new ConversationQueryBuilder('Conversation', array(
396
            'columns' => array(
397 1
                'last_activity' => 'last_activity',
398 1
                'status' => 'status'
399 1
            ),
400 1
            'name' => 'subject',
401 1
        ));
402 1
    }
403
}
404