Completed
Push — master ( 1ab9a2...14874e )
by ARCANEDEV
9s
created

Discussion   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 464
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 36
lcom 2
cbo 5
dl 0
loc 464
ccs 128
cts 128
cp 1
rs 8.8
c 0
b 0
f 0

26 Methods

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