Completed
Branch develop-3.0 (f52fa2)
by Mohamed
04:18
created

SendMessagesAbstract::validateData()

Size

Total Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 1
c 0
b 0
f 0
nc 1
1
<?php
2
3
/*
4
 * This file is part of the Tinyissue package.
5
 *
6
 * (c) Mohamed Alsharaf <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Tinyissue\Services;
13
14
use Illuminate\Contracts\Mail\Mailer;
15
use Illuminate\Mail\Message as MailMessage;
16
use Illuminate\Support\Collection;
17
use Tinyissue\Http\Requests\FormRequest\Note;
18
use Tinyissue\Model\Message;
19
use Tinyissue\Model\Project;
20
use Tinyissue\Model\Project\Issue;
21
use Tinyissue\Model\User;
22
23
/**
24
 * SendMessagesAbstract is an abstract class with for objects that requires sending messages.
25
 *
26
 * @author Mohamed Alsharaf <[email protected]>
27
 */
28
abstract class SendMessagesAbstract
29
{
30
    /**
31
     * Instance of issue that this message belong to.
32
     *
33
     * @var Issue
34
     */
35
    protected $issue;
36
37
    /**
38
     * Instance of project that this message belong to.
39
     *
40
     * @var Issue
41
     */
42
    protected $project;
43
44
    /**
45
     * The latest message queued.
46
     *
47
     * @var Message\Queue
48
     */
49
    protected $latestMessage;
50
51
    /**
52
     * Collection of all of the queued messages.
53
     *
54
     * @var Collection
55
     */
56
    protected $allMessages;
57
58
    /**
59
     * Instance of a queued message that is for adding a record (ie. adding issue).
60
     *
61
     * @var Message\Queue
62
     */
63
    protected $addMessage;
64
    /**
65
     * Collection of users that must not receive messages.
66
     *
67
     * @var Collection
68
     */
69
    protected $excludeUsers;
70
    /**
71
     * Collection of all of the project users that should receive messages.
72
     *
73
     * @var Collection
74
     */
75
    protected $projectUsers;
76
    /**
77
     * Collection of full subscribers that will always receive messages.
78
     *
79
     * @var Collection
80
     */
81
    protected $fullSubscribers;
82
    /**
83
     * Name of message template.
84
     *
85
     * @var string
86
     */
87
    protected $template;
88
89
    /**
90
     * @var Mailer
91
     */
92
    protected $mailer;
93
94
    /**
95
     * Collection of all messages.
96
     *
97
     * @var Collection
98
     */
99
    protected $messages;
100
101
    /**
102
     * Set instance of Mailer.
103
     *
104
     * @param Mailer $mailer
105
     *
106
     * @return $this
107
     */
108
    public function setMailer(Mailer $mailer)
109
    {
110
        $this->mailer = $mailer;
111
112
        return $this;
113
    }
114
115
    /**
116
     * The main method to process the massages queue and send them.
117
     *
118
     * @param Message\Queue $latestMessage
119
     * @param Collection    $changes
120
     *
121
     * @return void
122
     */
123
    public function process(Message\Queue $latestMessage, Collection $changes)
124
    {
125
        $this->setup($latestMessage, $changes);
126
127
        // Is model deleted? or in valid
128
        if (!$this->getModel() || !$this->validateData()) {
129
            return;
130
        }
131
132
        $this->processDirectMessages();
133
134
        // Skip if no users found
135
        if ($this->getProjectUsers()->isEmpty()) {
136
            return;
137
        }
138
139
        $this->populateData();
140
141
        // Send the latest message if it is about status (ie. closed issue)
142
        if ($this->isStatusMessage()) {
143
            return $this->sendMessageToAll($this->latestMessage);
144
        }
145
146
        // Get message data for all of the messages combined & for add message queue if exists
147
        $addMessageData = [];
148
        if ($this->addMessage) {
149
            $addMessageData = $this->getMessageData($this->addMessage);
150
        }
151
        $everythingMessageData = $this->getCombineMessageData($this->allMessages);
152
153
        // Send messages to project users
154
        $this->sendMessages($this->getProjectUsers(), [
155
            'addMessage' => $addMessageData,
156
            'everything' => $everythingMessageData,
157
        ]);
158
    }
159
160
    /**
161
     * Setup properties needed for the process.
162
     *
163
     * @param Message\Queue $latestMessage
164
     * @param Collection    $allMessages
165
     *
166
     * @return void
167
     */
168
    protected function setup(Message\Queue $latestMessage, Collection $allMessages)
169
    {
170
        // Set queue messages
171
        $this->latestMessage = $latestMessage;
172
        $this->allMessages   = $allMessages;
173
174
        // Exclude the user who made the change from receiving messages
175
        $this->addToExcludeUsers($this->latestMessage->changeBy);
176
177
        // Extract add model message if exists
178
        if ($this->getModel()) {
179
            $addMessageIdentifier = Message\Queue::getAddEventNameFromModel($this->getModel());
180
            $this->addMessage     = $this->allMessages->where('event', $addMessageIdentifier)->first();
181
        }
182
183
        // Make sure to load issue
184
        $this->getIssue();
185
    }
186
187
    /**
188
     * Whether or not we have all the needed properties.
189
     *
190
     * @return bool
191
     */
192
    abstract protected function validateData();
193
194
    /**
195
     * Process any messages queue that is to send messages to specific users.
196
     * For example, assign issue to user to message the user about the issue.
197
     *
198
     * @return void
199
     */
200
    protected function processDirectMessages()
201
    {
202
    }
203
204
    /**
205
     * Populate any data or properties.
206
     *
207
     * @return void
208
     */
209
    protected function populateData()
210
    {
211
    }
212
213
    /**
214
     * Whether or not the latest message is about status change such as closed issue.
215
     *
216
     * @return bool
217
     */
218
    abstract public function isStatusMessage();
219
220
    /**
221
     * Returns the message subject.
222
     *
223
     * @return string
224
     */
225
    protected function getSubject()
226
    {
227
        return '#' . $this->issue->id . ' / ' . $this->issue->title;
228
    }
229
230
    /**
231
     * Returns an array of data needed for the message.
232
     *
233
     * @param Message\Queue $queue
234
     * @param array         $extraData
235
     *
236
     * @return array
237
     */
238
    protected function getMessageData(Message\Queue $queue, array $extraData = [])
239
    {
240
        // Generic info for all messages emails
241
        $messageData                         = [];
242
        $messageData['issue']                = $this->getIssue();
243
        $messageData['project']              = $this->getProject();
244
        $messageData['changes']              = [];
245
        $messageData['changes']['change_by'] = [
246
            'now' => $queue->changeBy->fullname,
247
        ];
248
        if ($this->getIssue()) {
249
            $messageData['changes']['change_by']['url'] = $this->getIssue()->to();
250
        } else {
251
            $messageData['changes']['change_by']['url'] = $this->getProject()->to();
252
        }
253
        $messageData['changeByImage']   = $queue->changeBy->image;
254
        $messageData['changeByHeading'] = $this->getMessageHeading($queue);
255
        $messageData['event']           = $queue->event;
256
257
        // Info specific to a message type
258
        $method = 'getMessageDataFor' . ucfirst(camel_case($queue->event));
259
        if (method_exists($this, $method)) {
260
            $messageData = array_replace_recursive($messageData, $this->{$method}($queue, $extraData));
261
        }
262
263
        return $messageData;
264
    }
265
266
    /**
267
     * Loop through all of the messages and combine its message data.
268
     *
269
     * @param Collection $changes
270
     *
271
     * @return array
272
     */
273
    protected function getCombineMessageData(Collection $changes)
274
    {
275
        $everything = [];
276
        $changes->reverse()->each(function (Message\Queue $queue) use (&$everything) {
277
            if (empty($everything)) {
278
                $everything = $this->getMessageData($queue);
279
            } else {
280
                $messageData = $this->getMessageData($queue);
281
                $everything['changes'] = array_merge($everything['changes'], $messageData['changes']);
282
            }
283
        });
284
        $latestMessage                 = $changes->first();
285
        $everything['changeByHeading'] = $this->getMessageHeading($latestMessage, $changes);
286
        $everything['event']           = $latestMessage->event;
287
        $messageData                   = $this->getMessageData($latestMessage);
288
        $everything                    = array_replace_recursive($everything, $messageData);
289
290
        return $everything;
291
    }
292
293
    /**
294
     * Return text to be used for the message heading.
295
     *
296
     * @param Message\Queue   $queue
297
     * @param Collection|null $changes
298
     *
299
     * @return string
300
     */
301
    protected function getMessageHeading(Message\Queue $queue, Collection $changes = null)
302
    {
303
        $heading = $queue->changeBy->fullname . ' ';
304
305
        // If other users have made changes too
306
        if (!is_null($changes) && $changes->unique('change_by_id')->count() > 1) {
307
            $heading .= '& others ';
308
        }
309
310
        return $heading;
311
    }
312
313
    /**
314
     * Returns collection of all users in a project that should receive the messages.
315
     *
316
     * @return Collection
317
     */
318
    protected function getProjectUsers()
319
    {
320
        if (null === $this->projectUsers) {
321
            $this->projectUsers = (new Project\User())
322
                ->with('message', 'user', 'user.role')
323
                ->whereNotIn('user_id', $this->getExcludeUsers()->pluck('id'))
324
                ->where('project_id', '=', $this->getProjectId())
325
                ->get();
326
        }
327
328
        return $this->projectUsers;
329
    }
330
331
    /**
332
     * Returns the model that is belong to the queue message.
333
     *
334
     * @return Issue|Issue\Comment|Note
335
     */
336
    protected function getModel()
337
    {
338
        return $this->latestMessage->model;
339
    }
340
341
    /**
342
     * Returns an instance of project issue.
343
     *
344
     * @return Issue|bool
345
     */
346
    abstract protected function getIssue();
347
348
    /**
349
     * Returns an instance of project.
350
     *
351
     * @return Project
352
     */
353
    abstract protected function getProject();
354
355
    /**
356
     * Returns the id of a project.
357
     *
358
     * @return int
359
     */
360
    abstract protected function getProjectId();
361
362
    /**
363
     * Returns collection of all of the users that must not receive messages.
364
     *
365
     * @return Collection
366
     */
367
    protected function getExcludeUsers()
368
    {
369
        if (null === $this->excludeUsers) {
370
            $this->excludeUsers = collect([]);
371
        }
372
373
        return $this->excludeUsers;
374
    }
375
376
    /**
377
     * Exclude a user from receiving messages.
378
     *
379
     * @param User $user
380
     *
381
     * @return $this
382
     */
383
    protected function addToExcludeUsers(User $user)
384
    {
385
        $this->getExcludeUsers()->push($user);
386
387
        return $this;
388
    }
389
390
    /**
391
     * Find user by id. This search the project users and fallback to excluded list of users.
392
     *
393
     * @param int $userId
394
     *
395
     * @return User
396
     */
397
    protected function getUserById($userId)
398
    {
399
        $projectUser = $this->getProjectUsers()->where('user_id', $userId)->first();
400
401
        if (!$projectUser) {
402
            return $this->getExcludeUsers()->where('id', $userId)->first();
403
        }
404
405
        return $projectUser->user;
406
    }
407
408
    /**
409
     * Returns collection of all messages.
410
     *
411
     * @return Collection
412
     */
413
    protected function getMessages()
414
    {
415
        if (null === $this->messages) {
416
            $this->messages = Message::instance()->getAll();
417
        }
418
419
        return $this->messages;
420
    }
421
422
    /**
423
     * Send a message to a user.
424
     *
425
     * @param User  $user
426
     * @param array $data
427
     *
428
     * @return mixed
429
     */
430
    private function sendMessage(User $user, array $data)
431
    {
432
        // Make sure the data contains changes
433
        if (!array_key_exists('changes', $data) && count($data['changes']) > 1) {
434
            return;
435
        }
436
437
        return $this->mailer->send('email.' . $this->template, $data, function (MailMessage $message) use ($user) {
438
            $message->to($user->email, $user->fullname)->subject($this->getSubject());
439
        });
440
    }
441
442
    /**
443
     * Send a message to a collection of users, or send customised message per use logic.
444
     *
445
     * @param Collection $users
446
     * @param array      $data
447
     *
448
     * @return void
449
     */
450
    protected function sendMessages(Collection $users, array $data)
451
    {
452
        foreach ($users as $user) {
453
            $userMessageData = $this->getUserMessageData($user->user_id, $data);
454
            if (!$this->wantToReceiveMessage($user, $userMessageData)) {
455
                continue;
456
            }
457
458
            $this->sendMessage($user->user, $userMessageData);
459
        }
460
    }
461
462
    /**
463
     * Get customised message per user logic.
464
     *
465
     * @param int   $userId
466
     * @param array $messagesData
467
     *
468
     * @return array
469
     */
470
    protected function getUserMessageData($userId, array $messagesData)
471
    {
472
        if (array_key_exists('event', $messagesData)) {
473
            return $messagesData;
474
        }
475
476
        // Possible message data
477
        $addMessageData        = $messagesData['addMessage'];
478
        $everythingMessageData = $messagesData['everything'];
479
480
        // Check if the user has seen the model data and made a change
481
        $changeMadeByUser = $this->allMessages->where('change_by_id', $userId);
482
483
        // This user has never seen this model data
484
        if (!$changeMadeByUser->count()) {
485
            if ($this->addMessage) {
486
                return $addMessageData;
487
            }
488
489
            return $everythingMessageData;
490
        }
491
492
        // This user has seen this model data
493
        // Get all of the changes that may happened later.
494
        // Combine them and send message to the user about these changes.
495
        $everythingMessageData = $this->getCombineMessageData(
496
            $this->allMessages->forget($changeMadeByUser->keys()->toArray())
497
        );
498
499
        return $everythingMessageData;
500
    }
501
502
    /**
503
     * Whether or not the user wants to receive the message.
504
     *
505
     * @param Project\User $user
506
     * @param array        $data
507
     *
508
     * @return bool
509
     */
510
    protected function wantToReceiveMessage(Project\User $user, array $data)
511
    {
512
        /** @var Message $message */
513
        $message = $this->getMessageForUser($user);
514
515
        // No message to send,
516
        // - if we can't find message object or
517
        // - messages are disabled or
518
        // - event is inactive for the user message setting
519
        if (!$message || $message->isDisabled() || !$message->isActiveEvent($data['event'])) {
520
            return false;
521
        }
522
523
        // Wants to see all updates in project
524
        if ((bool) $message->in_all_issues === true) {
0 ignored issues
show
Documentation introduced by
The property in_all_issues does not exist on object<Tinyissue\Model\Message>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
525
            return true;
526
        }
527
528
        if (!$this->getIssue()) {
529
            return false;
530
        }
531
532
        // For issue only send messages if user is assignee or creator
533
        $creator  = $this->getIssue()->user;
534
        $assignee = $this->getIssue()->assigned;
535
        if ((int) $user->user_id === (int) $creator->id || ($assignee && (int) $user->user_id === (int) $assignee->id)) {
536
            return true;
537
        }
538
539
        return false;
540
    }
541
542
    /**
543
     * @param Project\User $user
544
     *
545
     * @return Message
546
     */
547
    protected function getMessageForUser(Project\User $user)
548
    {
549
        $message = $user->message;
550
        if (!$message) {
551
            $roleName = $user->user->role->role;
552
            $message  = $this->getMessages()->where('name', Message::$defaultMessageToRole[$roleName])->first();
553
        }
554
555
        return $message;
556
    }
557
558
    /**
559
     * Send a message to al users in project and full subscribes.
560
     *
561
     * @param Message\Queue $queue
562
     *
563
     * @return void
564
     */
565
    protected function sendMessageToAll(Message\Queue $queue)
566
    {
567
        $messageData = $this->getMessageData($queue);
568
569
        $this->sendMessages($this->getProjectUsers(), $messageData);
570
    }
571
572
    /**
573
     * Load the creator of an issue to the collection of project users. So we can send message to creator if needed.
574
     *
575
     * @return void
576
     */
577
    protected function loadIssueCreatorToProjectUsers()
578
    {
579
        // Stop if we can't get the issue
580
        if (!$this->getIssue()) {
581
            return;
582
        }
583
584
        // Get issue creator
585
        $creator = $this->getIssue()->user;
586
587
        // Stop if creator excluded from messages
588
        $excluded = $this->getExcludeUsers()->where('id', $creator->id)->first();
589
        if ($excluded) {
590
            return;
591
        }
592
593
        // Stop if the creator already part of the project users
594
        $existInProject = $this->getProjectUsers()->where('user_id', $creator->id)->first();
595
        if ($existInProject) {
596
            return;
597
        }
598
599
        // Create virtual project user object & add to collection
600
        $userProject = new Project\User([
601
            'user_id'    => $creator->id,
602
            'project_id' => $this->getProjectId(),
603
        ]);
604
        $userProject->setRelation('user', $creator);
605
        $userProject->setRelation('project', $this->getProject());
606
        $this->getProjectUsers()->push($userProject);
607
    }
608
}
609