Completed
Push — master ( 84cd1f...258c28 )
by ARCANEDEV
08:50
created

Discussion::removeParticipant()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php namespace Arcanedev\LaravelMessenger\Models;
2
3
use Arcanedev\LaravelMessenger\Bases\Model;
4
use Arcanedev\LaravelMessenger\Contracts\Discussion as DiscussionContract;
5
use Arcanedev\LaravelMessenger\Contracts\Message as MessageContract;
6
use Arcanedev\LaravelMessenger\Contracts\Participant as ParticipantContract;
7
use Carbon\Carbon;
8
use Illuminate\Database\Eloquent\Builder;
9
use Illuminate\Database\Eloquent\SoftDeletes;
10
use Illuminate\Database\Query\JoinClause;
11
12
/**
13
 * Class     Discussion
14
 *
15
 * @package  Arcanedev\LaravelMessenger\Models
16
 * @author   ARCANEDEV <[email protected]>
17
 *
18
 * @property  int             id
19
 * @property  string          subject
20
 * @property  \Carbon\Carbon  created_at
21
 * @property  \Carbon\Carbon  updated_at
22
 * @property  \Carbon\Carbon  deleted_at
23
 *
24
 * @property  \Illuminate\Database\Eloquent\Model         creator
25
 * @property  \Illuminate\Database\Eloquent\Collection    messages
26
 * @property  \Illuminate\Database\Eloquent\Collection    participants
27
 * @property  \Arcanedev\LaravelMessenger\Models\Message  latest_message
28
 *
29
 * @method static \Illuminate\Database\Eloquent\Builder  subject(string $subject, bool $strict)
30
 * @method static \Illuminate\Database\Eloquent\Builder  between(array $usersIds)
31
 * @method static \Illuminate\Database\Eloquent\Builder  forUser(int $userId)
32
 * @method static \Illuminate\Database\Eloquent\Builder  forUserWithNewMessages(int $userId)
33
 */
34
class Discussion extends Model implements DiscussionContract
35
{
36
    /* ------------------------------------------------------------------------------------------------
37
     |  Traits
38
     | ------------------------------------------------------------------------------------------------
39
     */
40
    use SoftDeletes;
41
42
    /* ------------------------------------------------------------------------------------------------
43
     |  Properties
44
     | ------------------------------------------------------------------------------------------------
45
     */
46
    /**
47
     * The attributes that can be set with Mass Assignment.
48
     *
49
     * @var array
50
     */
51
    protected $fillable = ['subject'];
52
53
    /**
54
     * The attributes that should be mutated to dates.
55
     *
56
     * @var array
57
     */
58
    protected $dates = ['deleted_at'];
59
60
    /**
61
     * The attributes that should be cast to native types.
62
     *
63
     * @var array
64
     */
65
    protected $casts = [
66
        'id' => 'integer',
67
    ];
68
69
    /* ------------------------------------------------------------------------------------------------
70
     |  Constructor
71
     | ------------------------------------------------------------------------------------------------
72
     */
73
    /**
74
     * Create a new Eloquent model instance.
75
     *
76
     * @param  array  $attributes
77
     */
78 304
    public function __construct(array $attributes = [])
79
    {
80 304
        $this->setTable(
81 304
            $this->getTableFromConfig('discussions', 'discussions')
82 228
        );
83
84 304
        parent::__construct($attributes);
85 304
    }
86
87
    /* ------------------------------------------------------------------------------------------------
88
     |  Relationships
89
     | ------------------------------------------------------------------------------------------------
90
     */
91
    /**
92
     * Participants relationship.
93
     *
94
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
95
     */
96 216
    public function participants()
97
    {
98 216
        return $this->hasMany(
99 216
            $this->getModelFromConfig('participants', Participant::class)
100 162
        );
101
    }
102
103
    /**
104
     * Messages relationship.
105
     *
106
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
107
     */
108 88
    public function messages()
109
    {
110 88
        return $this->hasMany(
111 88
            $this->getModelFromConfig('messages', Participant::class)
112 66
        );
113
    }
114
115
    /**
116
     * Get the user that created the first message.
117
     *
118
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
119
     */
120 8
    public function creator()
121
    {
122 8
        return $this->messages()->oldest()->first()->user();
123
    }
124
125
    /* ------------------------------------------------------------------------------------------------
126
     |  Scopes
127
     | ------------------------------------------------------------------------------------------------
128
     */
129
    /**
130
     * Scope discussions that the user is associated with.
131
     *
132
     * @param  \Illuminate\Database\Eloquent\Builder  $query
133
     * @param  int                                    $userId
134
     *
135
     * @return \Illuminate\Database\Eloquent\Builder
136
     */
137 16
    public function scopeForUser(Builder $query, $userId)
138
    {
139 16
        return $query->join(
140 16
            $participants = $this->getParticipantsTable(),
141
            function (JoinClause $join) use ($participants, $userId) {
142 16
                $join->on($this->getQualifiedKeyName(), '=', "{$participants}.discussion_id")
143 16
                    ->where("{$participants}.user_id", '=', $userId)
144 16
                    ->whereNull("{$participants}.deleted_at");
145 16
            }
146 12
        );
147
    }
148
149
    /**
150
     * Scope discussions with new messages that the user is associated with.
151
     *
152
     * @param  \Illuminate\Database\Eloquent\Builder  $query
153
     * @param  int                                    $userId
154
     *
155
     * @return \Illuminate\Database\Eloquent\Builder
156
     */
157 8
    public function scopeForUserWithNewMessages(Builder $query, $userId)
158
    {
159 8
        $participants = $this->getParticipantsTable();
160 8
        $discussions  = $this->getTable();
161 8
        $prefix       = $this->getConnection()->getTablePrefix();
162
163 8
        return $this->scopeForUser($query, $userId)
164
            ->where(function (Builder $query) use ($participants, $discussions, $prefix) {
165 8
                $expression = $this->getConnection()->raw("{$prefix}{$participants}.last_read");
166
167 8
                $query->where("{$discussions}.updated_at", '>', $expression)
168 8
                      ->orWhereNull("{$participants}.last_read");
169 8
            });
170
    }
171
172
    /**
173
     * Scope discussions between given user ids.
174
     *
175
     * @param  \Illuminate\Database\Eloquent\Builder  $query
176
     * @param  array                                  $userIds
177
     *
178
     * @return \Illuminate\Database\Eloquent\Builder
179
     */
180 8
    public function scopeBetween(Builder $query, array $userIds)
181
    {
182 8
        $participants = $this->getParticipantsTable();
183
184
        return $query->whereHas($participants, function (Builder $query) use ($userIds) {
185 8
            $query->whereIn('user_id', $userIds)
186 8
                ->groupBy('discussion_id')
187 8
                ->havingRaw('COUNT(discussion_id)=' . count($userIds));
188 8
        });
189
    }
190
191
    /**
192
     * Get the participants table name.
193
     *
194
     * @return string
195
     */
196 24
    protected function getParticipantsTable()
197
    {
198 24
        return $this->getTableFromConfig('participants', 'participants');
199
    }
200
201
    /**
202
     * Scope the query by the subject.
203
     *
204
     * @param  \Illuminate\Database\Eloquent\Builder  $query
205
     * @param  string                                 $subject
206
     * @param  bool                                   $strict
207
     *
208
     * @return \Illuminate\Database\Eloquent\Builder
209
     */
210 16
    public function scopeSubject(Builder $query, $subject, $strict = false)
211
    {
212 16
        $subject = $strict ? $subject : "%{$subject}%";
213
214 16
        return $query->where('subject', 'like', $subject);
215
    }
216
217
    /* ------------------------------------------------------------------------------------------------
218
     |  Getters & Setters
219
     | ------------------------------------------------------------------------------------------------
220
     */
221
    /**
222
     * Get the latest_message attribute.
223
     *
224
     * @return \Arcanedev\LaravelMessenger\Models\Message
225
     */
226 8
    public function getLatestMessageAttribute()
227
    {
228 8
        return $this->messages->sortByDesc('created_at')->first();
229
    }
230
231
    /* ------------------------------------------------------------------------------------------------
232
     |  Main Functions
233
     | ------------------------------------------------------------------------------------------------
234
     */
235
    /**
236
     * Returns all of the latest discussions by `updated_at` date.
237
     *
238
     * @return \Illuminate\Database\Eloquent\Collection
239
     */
240 8
    public static function getLatest()
241
    {
242 8
        return self::latest('updated_at')->get();
243
    }
244
245
    /**
246
     * Returns all discussions by subject.
247
     *
248
     * @param  string  $subject
249
     * @param  bool    $strict
250
     *
251
     * @return \Illuminate\Database\Eloquent\Collection
252
     */
253 16
    public static function getBySubject($subject, $strict = false)
254
    {
255 16
        return self::subject($subject, $strict)->get();
256
    }
257
258
    /**
259
     * Returns an array of user ids that are associated with the discussion.
260
     *
261
     * @param  int|null  $userId
262
     *
263
     * @return \Illuminate\Support\Collection
264
     */
265 8
    public function participantsUserIds($userId = null)
266
    {
267
        /** @var \Illuminate\Support\Collection $usersIds */
268 8
        $usersIds = $this->participants()->withTrashed()->lists('user_id');
269
270 8
        return $usersIds->push($userId)->filter()->unique();
271
    }
272
273
    /**
274
     * Add a user to discussion as a participant.
275
     *
276
     * @param  int   $userId
277
     *
278
     * @return \Arcanedev\LaravelMessenger\Models\Participant
279
     */
280 112
    public function addParticipant($userId)
281
    {
282
        /** @var \Arcanedev\LaravelMessenger\Models\Participant $participant */
283 112
        $participant = $this->participants()->firstOrCreate([
284 112
            'user_id'       => $userId,
285 112
            'discussion_id' => $this->id,
286 84
        ]);
287
288 112
        return $participant;
289
    }
290
291
    /**
292
     * Add users to discussion as participants.
293
     *
294
     * @param  array  $userIds
295
     *
296
     * @return \Illuminate\Database\Eloquent\Collection
297
     */
298 64
    public function addParticipants(array $userIds)
299
    {
300 64
        foreach ($userIds as $userId) {
301 64
            $this->addParticipant($userId);
302 48
        }
303
304 64
        return $this->participants;
305
    }
306
307
    /**
308
     * Remove a participant from discussion.
309
     *
310
     * @param  int   $userId
311
     * @param  bool  $reload
312
     *
313
     * @return int
314
     */
315 16
    public function removeParticipant($userId, $reload = true)
316
    {
317 16
        return $this->removeParticipants([$userId], $reload);
318
    }
319
320
    /**
321
     * Remove participants from discussion.
322
     *
323
     * @param  array  $userIds
324
     * @param  bool   $reload
325
     *
326
     * @return int
327
     */
328 48
    public function removeParticipants(array $userIds, $reload = true)
329
    {
330 48
        $deleted = $this->participants()
331 48
            ->whereIn('user_id', $userIds)
332 48
            ->where('discussion_id', $this->id)
333 48
            ->delete();
334
335 48
        if ($reload) $this->load(['participants']);
336
337 48
        return $deleted;
338
    }
339
340
    /**
341
     * Mark a discussion as read for a user.
342
     *
343
     * @param  int  $userId
344
     *
345
     * @return bool|int
346
     */
347 40
    public function markAsRead($userId)
348
    {
349 40
        if ($participant = $this->getParticipantByUserId($userId)) {
350 32
            return $participant->update([
351 32
                'last_read' => Carbon::now()
352 24
            ]);
353
        }
354
355 8
        return false;
356
    }
357
358
    /**
359
     * See if the current thread is unread by the user.
360
     *
361
     * @param  int  $userId
362
     *
363
     * @return bool
364
     */
365 16
    public function isUnread($userId)
366
    {
367 16
        return ($participant = $this->getParticipantByUserId($userId))
368 16
            ? $participant->last_read < $this->updated_at
369 16
            : false;
370
    }
371
372
    /**
373
     * Finds the participant record from a user id.
374
     *
375
     * @param  int  $userId
376
     *
377
     * @return \Arcanedev\LaravelMessenger\Models\Participant
378
     */
379 56
    public function getParticipantByUserId($userId)
380
    {
381 56
        return $this->participants()
382 56
            ->where('user_id', $userId)
383 56
            ->first();
384
    }
385
386
    /**
387
     * Get the trashed participants.
388
     *
389
     * @return \Illuminate\Database\Eloquent\Collection
390
     */
391 16
    public function getTrashedParticipants()
392
    {
393 16
        return $this->participants()
394 16
            ->onlyTrashed()
395 16
            ->get();
396
    }
397
398
    /**
399
     * Restores all participants within a discussion.
400
     *
401
     * @param  bool  $reload
402
     *
403
     * @return int
404
     */
405 8
    public function restoreAllParticipants($reload = true)
406
    {
407 8
        $participants = $this->getTrashedParticipants();
408
        $restored     = $participants->filter(function (ParticipantContract $participant) {
409 8
            return $participant->restore();
410 8
        })->count();
411
412 8
        if ($reload) $this->load(['participants']);
413
414 8
        return $restored;
415
    }
416
417
    /**
418
     * Generates a participant information as a string.
419
     *
420
     * @param  int|null       $ignoredUserId
421
     * @param  \Closure|null  $callback
422
     * @param  string         $glue
423
     *
424
     * @return string
425
     */
426 16
    public function participantsString($ignoredUserId = null, $callback = null, $glue = ', ')
427
    {
428
        /** @var \Illuminate\Database\Eloquent\Collection $participants */
429 16
        $participants = $this->participants->load(['user']);
430
431 16
        if (is_null($callback)) {
432
            // By default: the participant name
433
            $callback = function (ParticipantContract $participant) {
434 8
                return $participant->stringInfo();
435 8
            };
436 6
        }
437
438
        return $participants->filter(function (ParticipantContract $participant) use ($ignoredUserId) {
439 16
            return $participant->user_id !== $ignoredUserId;
0 ignored issues
show
Bug introduced by
Accessing user_id on the interface Arcanedev\LaravelMessenger\Contracts\Participant suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
440 16
        })->map($callback)->implode($glue);
441
    }
442
443
    /**
444
     * Checks to see if a user is a current participant of the discussion.
445
     *
446
     * @param  int  $userId
447
     *
448
     * @return bool
449
     */
450 16
    public function hasParticipant($userId)
451
    {
452 16
        return $this->participants()
453 16
            ->where('user_id', '=', $userId)
454 16
            ->count() > 0;
455
    }
456
457
    /**
458
     * Returns array of unread messages in discussion for given user.
459
     *
460
     * @param  int  $userId
461
     *
462
     * @return \Illuminate\Support\Collection
463
     */
464 16
    public function userUnreadMessages($userId)
465
    {
466 16
        $participant = $this->getParticipantByUserId($userId);
467
468 16
        if (is_null($participant)) return collect();
469
470 16
        return is_null($participant->last_read)
471 16
            ? $this->messages->toBase()
472 16
            : $this->messages->filter(function (MessageContract $message) use ($participant) {
473 16
                return $message->updated_at->gt($participant->last_read);
0 ignored issues
show
Bug introduced by
Accessing updated_at on the interface Arcanedev\LaravelMessenger\Contracts\Message suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
474 16
            })->toBase();
475
    }
476
}
477