Completed
Branch develop-3.0 (f1fbdb)
by Mohamed
03:16
created

Updater::deleteIssue()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
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\Repository\Project\Issue;
13
14
use Illuminate\Support\Collection;
15
use Tinyissue\Model\Activity;
16
use Tinyissue\Model\Message\Queue;
17
use Tinyissue\Model\Project;
18
use Tinyissue\Model\Tag;
19
use Tinyissue\Model\User;
20
use Tinyissue\Repository\RepositoryUpdater;
21
22
class Updater extends RepositoryUpdater
23
{
24
    /**
25
     * @var Project\Issue
26
     */
27
    protected $model;
28
29
    public function __construct(Project\Issue $model)
30
    {
31
        $this->model = $model;
32
    }
33
34
    /**
35
     * Set the issue is updated by a user.
36
     *
37
     * @return Project\Issue
38
     */
39
    public function changeUpdatedBy()
40
    {
41
        $this->model->updated_by = $this->user->id;
42
        $this->model->touch();
43
44
        return $this->save();
45
    }
46
47
    /**
48
     * Reassign the issue to a new user.
49
     *
50
     * @param int|User $assignTo
51
     * @param User     $user
52
     *
53
     * @return Project\Issue
54
     */
55
    public function reassign($assignTo, User $user)
56
    {
57
        $this->setUser($user);
58
        $assignToId = !$assignTo instanceof User ? $assignTo : $assignTo->id;
59
        $this->model->assigned_to = $assignToId;
60
61
        // Add event on successful save
62
        Project\Issue::saved(function (Project\Issue $issue) {
63
            $this->queueAssign($issue, $this->user);
64
        });
65
66
        $this->save();
67
68
        $this->saveToActivities([
69
            'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
70
            'parent_id' => $this->model->project->id,
71
            'user_id'   => $user->id,
72
            'action_id' => $this->model->assigned_to,
73
        ]);
74
75
        return $this->model;
76
    }
77
78
    protected function filterUpdateAttributes(array $input)
79
    {
80
        $fill = array_only($input, ['title', 'body', 'assigned_to']);
81
        $fill['updated_by'] = $this->model->updatedBy->id;
82
        $fill['lock_quote'] = (bool)isset($input['time_quote']['lock']);
83
84
        // Only save quote if not locked or locked & user allowed to modify it
85
        if (array_key_exists('time_quote', $input) &&
86
            (!$this->model->isQuoteLocked() || $this->getLoggedUser()->can('lockQuote', $this->model))
87
        ) {
88
            $fill['time_quote'] = $input['time_quote'];
89
        }
90
91
        return $fill;
92
    }
93
94
    /**
95
     * Update the given issue.
96
     *
97
     * @param array $input
98
     *
99
     * @return Project\Issue
100
     */
101
    public function update(array $input = [])
102
    {
103
        $this->model->fill($this->filterUpdateAttributes($input));
104
105
        /* Add to activity log for assignment if changed */
106
        if ($this->model->isDirty('assigned_to') && $this->model->assigned_to > 0) {
107
            $this->saveToActivities([
108
                'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
109
                'parent_id' => $this->model->project->id,
110
                'user_id'   => $this->model->updatedBy->id,
111
                'action_id' => $this->model->assigned_to,
112
            ]);
113
        }
114
115
        $this->syncTags($input, $this->model->tags()->with('parent')->get());
116
117
        // Add event on successful save
118
        Project\Issue::saved(function (Project\Issue $issue) {
119
            $this->queueUpdate($issue, $this->user);
120
        });
121
122
        return $this->save();
123
    }
124
125
    /**
126
     * Create a new issue.
127
     *
128
     * @param array $input
129
     *
130
     * @return Project\Issue
131
     */
132
    public function create(array $input)
133
    {
134
        if (array_key_exists('project_id', $input)) {
135
            $this->model->setRelation('project', Project::find((int)$input['project_id']));
136
        }
137
138
        $fill = [
139
            'created_by'  => $this->model->user->id,
140
            'project_id'  => $this->model->project->id,
141
            'title'       => $input['title'],
142
            'body'        => $input['body'],
143
            'assigned_to' => (int)$this->model->project->default_assignee,
144
        ];
145
146
        if ($this->model->user->isDeveloperOrMore()) {
147
            $fill['assigned_to'] = array_get($input, 'assigned_to', $fill['assigned_to']);
148
            $fill['time_quote'] = array_get($input, 'time_quote');
149
        }
150
151
        $this->model->fill($fill)->save();
152
153
        // Add issue to messages queue
154
        $this->queueAdd($this->model, $this->user);
155
156
        /* Add to user's activity log */
157
        $this->saveToActivities([
158
            'type_id'   => Activity::TYPE_CREATE_ISSUE,
159
            'parent_id' => $this->model->project->id,
160
            'user_id'   => $this->model->user->id,
161
        ]);
162
163
        /* Add attachments to issue */
164
        Project\Issue\Attachment::instance()->updater()->updateIssueToken($input['upload_token'], $this->model->user->id, $this->model->id);
165
166
        // Add default tag to newly created issue
167
        $defaultTag = app('tinyissue.settings')->getFirstStatusTagId();
168
        if ($defaultTag > 0 && empty($input['tag_status'])) {
169
            $input['tag_status'] = $defaultTag;
170
        }
171
172
        $this->syncTags($input);
173
174
        return $this->model;
175
    }
176
177
    /**
178
     * Move the issue (comments & activities) to another project.
179
     *
180
     * @param int $projectId
181
     *
182
     * @return Project\Issue
183
     */
184
    public function changeProject($projectId)
185
    {
186
        $this->model->project_id = $projectId;
187
        $this->save();
188
        $comments = $this->model->comments()->get();
189
        foreach ($comments as $comment) {
190
            $comment->project_id = $projectId;
191
            $comment->save();
192
        }
193
194
        $activities = $this->model->activities()->get();
195
        foreach ($activities as $activity) {
196
            $activity->parent_id = $projectId;
197
            $activity->save();
198
        }
199
200
        return $this->model;
201
    }
202
203
    /**
204
     * Delete an issue.
205
     *
206
     * @return bool
207
     *
208
     * @throws \Exception
209
     */
210
    public function delete()
211
    {
212
        return $this->transaction('deleteIssue');
213
    }
214
215
    protected function deleteIssue()
216
    {
217
        // Delete issue related data
218
        $this->deleteComments();
219
        $this->deleteAttachments();
220
        $this->deleteUserActivities();
221
        $this->deleteIssueTags();
222
223
        // Delete the issue
224
        return $this->model->delete();
225
    }
226
227
    /**
228
     * @return void
229
     */
230
    protected function deleteComments()
231
    {
232
        $this->model->comments->each(function (Project\Issue\Comment $comment) {
233
            $comment->updater($this->getLoggedUser())->delete();
234
        });
235
    }
236
237
    /**
238
     * @return void
239
     */
240
    protected function deleteAttachments()
241
    {
242
        $this->model->attachments->each(function (Project\Issue\Attachment $attachment) {
243
            $attachment->updater()->delete();
244
        });
245
    }
246
247
    /**
248
     * @return void
249
     */
250
    protected function deleteIssueTags()
251
    {
252
        \DB::table('projects_issues_tags')->where('issue_id', '=', $this->model->id)->delete();
253
    }
254
255
    /**
256
     * @return void
257
     */
258
    protected function deleteUserActivities()
259
    {
260
        User\Activity::where('parent_id', '=', $this->model->project_id)
261
            ->where('item_id', '=', $this->model->id)
262
            ->delete();
263
    }
264
265
    /**
266
     * Change the status of an issue.
267
     *
268
     * @param int  $status
269
     * @param User $user
270
     *
271
     * @return Project\Issue
272
     */
273
    public function changeStatus($status, User $user)
274
    {
275
        if ($status == 0) {
276
            $this->model->closed_by = $user->id;
277
            $this->model->closed_at = (new \DateTime())->format('Y-m-d H:i:s');
278
            $activityType = Activity::TYPE_CLOSE_ISSUE;
279
        } else {
280
            $this->model->closed_by = 0;
281
            $this->model->closed_at = null;
282
            $activityType = Activity::TYPE_REOPEN_ISSUE;
283
        }
284
285
        /* Add to activity log */
286
        $this->saveToActivities([
287
            'type_id'   => $activityType,
288
            'parent_id' => $this->model->project->id,
289
            'user_id'   => $user->id,
290
        ]);
291
292
        $this->model->status = $status;
293
294
        // Add event on successful save
295
        Project\Issue::saved(function (Project\Issue $issue) {
296
            $this->queueUpdate($issue, $this->user);
297
        });
298
299
        return $this->save();
300
    }
301
302
    /**
303
     * Sync the issue tags.
304
     *
305
     * @param array      $input
306
     * @param Collection $currentTags
307
     *
308
     * @return bool
309
     */
310
    public function syncTags(array $input, Collection $currentTags = null)
311
    {
312
        $tagIds = array_only($input, [
313
            'tag_type', 'tag_status', 'tag_resolution',
314
        ]);
315
        $currentTags = is_null($currentTags) ? Collection::make([]) : $currentTags;
316
317
        // User can edit their own role and can only change issue type
318
        if ($this->model->updatedBy instanceof User && $this->model->updatedBy->isUser()) {
319
            $currentTagIds = $currentTags->pluck('id', 'parent.name')->toArray();
320
            $tagIds['tag_status'] = array_key_exists('status', $currentTagIds) ? $currentTagIds['status'] : 0;
321
            $tagIds['tag_resolution'] = array_key_exists('resolution', $currentTagIds) ? $currentTagIds['resolution'] : 0;
322
        }
323
324
        $tags = (new Tag())->whereIn('id', $tagIds)->get();
325
326
        $removedTags = [];
327
        if (null === $currentTags) {
328
            // Add the following tags except for open status
329
            $addedTags = $tags
330
                ->map(function (Tag $tag) {
331
                    return $tag->toShortArray();
332
                })
333
                ->toArray();
334
        } else {
335
            // Tags remove from the issue
336
            $removedTags = $currentTags
337
                ->diff($tags)
338
                ->map(function (Tag $tag) {
339
                    return $tag->toShortArray();
340
                })
341
                ->toArray();
342
343
            // Check if we are adding new tags
344
            $addedTags = $tags
345
                ->filter(function (Tag $tag) use ($currentTags) {
346
                    return $currentTags->where('id', $tag->id)->count() === 0;
347
                })
348
                ->map(function (Tag $tag) {
349
                    return $tag->toShortArray();
350
                })
351
                ->toArray();
352
353
            // No new tags to add or remove
354
            if (empty($removedTags) && empty($addedTags)) {
355
                return true;
356
            }
357
        }
358
359
        // Save relation
360
        $this->model->tags()->sync($tags->pluck('id')->all());
361
362
        // Activity is added when new issue create with tags or updated with tags excluding the open status tag
363
        if (!empty($removedTags) || !empty($addedTags)) {
364
            // Add this change to messages queue
365
            $this->queueChangeTags($this->model, $addedTags, $removedTags, $this->user);
366
367
            // Add to activity log for tags if changed
368
            $this->saveToActivities([
369
                'type_id'   => Activity::TYPE_ISSUE_TAG,
370
                'parent_id' => $this->model->project->id,
371
                'user_id'   => $this->model->user->id,
372
                'data'      => ['added_tags' => $addedTags, 'removed_tags' => $removedTags],
373
            ]);
374
        }
375
376
        return true;
377
    }
378
379
    /**
380
     * Add tag to the issue & close issue if added tag is Closed.
381
     *
382
     * @param Tag $newTag
383
     * @param Tag $oldTag
384
     *
385
     * @return Project\Issue
386
     */
387
    public function changeKanbanTag(Tag $newTag, Tag $oldTag)
388
    {
389
        //  skip if there is no change in status tags
390
        if ($oldTag->name === $newTag->name) {
391
            return $this->model;
392
        }
393
394
        // Open issue
395
        $data = ['added_tags' => [], 'removed_tags' => []];
396
397
        // Remove previous status tag
398
        $this->model->tags()->detach($oldTag);
399
        $data['removed_tags'][] = $oldTag->toShortArray();
400
401
        // Add new tag
402
        if (!$this->model->tags->contains($newTag)) {
403
            $this->model->tags()->attach($newTag);
404
405
            $data['added_tags'][] = $newTag->toShortArray();
406
        }
407
408
        if (!empty($data)) {
409
            // Add this change to messages queue
410
            $this->queueChangeTags($this->model, $data['added_tags'], $data['removed_tags'], $this->user);
411
412
            // Add to activity log for tags if changed
413
            $this->saveToActivities([
414
                'type_id'   => Activity::TYPE_ISSUE_TAG,
415
                'parent_id' => $this->model->project->id,
416
                'user_id'   => $this->model->user->id,
417
                'data'      => $data,
418
            ]);
419
        }
420
421
        return $this->model;
422
    }
423
424
    /**
425
     * Insert update issue to message queue.
426
     *
427
     * @param Project\Issue $issue
428
     * @param User          $changeBy
429
     *
430
     * @return void
431
     */
432
    public function queueUpdate(Project\Issue $issue, User $changeBy)
433
    {
434
        // is Closed?
435
        $this->queueClosedIssue($issue, $changeBy);
436
437
        // is Reopened?
438
        $this->queueReopenedIssue($issue, $changeBy);
439
440
        // If the assignee has changed and it is not the logged in user who made the action
441
        $noMessageForMe = $this->queueAssign($issue, $changeBy);
442
443
        // If the update was just for assigning user, then skip update issue
444
        $this->queueUpdateIssue($issue, $changeBy, $noMessageForMe);
445
    }
446
447
    /**
448
     * @param Project\Issue $issue
449
     * @param User          $changeBy
450
     * @param bool|int      $noMessageForMe
451
     */
452
    protected function queueUpdateIssue(Project\Issue $issue, User $changeBy, $noMessageForMe = false)
453
    {
454
        // Number of changed attributes
455
        $countChanges = count($issue->getDirty());
456
457
        if (!($countChanges === 1 && $noMessageForMe !== false)) {
458
            return (new Queue())->updater($changeBy)->queue(Queue::UPDATE_ISSUE, $issue, $changeBy);
459
        }
460
    }
461
462
    /**
463
     * @param Project\Issue $issue
464
     * @param User          $changeBy
465
     */
466
    protected function queueClosedIssue(Project\Issue $issue, User $changeBy)
467
    {
468
        if (!$issue->isOpen()) {
469
            (new Queue())->updater($changeBy)->queue(Queue::CLOSE_ISSUE, $issue, $changeBy);
470
        }
471
    }
472
473
    /**
474
     * @param Project\Issue $issue
475
     * @param User          $changeBy
476
     */
477
    protected function queueReopenedIssue(Project\Issue $issue, User $changeBy)
478
    {
479
        if ((int)$issue->getOriginal('status') === Project\Issue::STATUS_CLOSED) {
480
            (new Queue())->updater($changeBy)->queue(Queue::REOPEN_ISSUE, $issue, $changeBy);
481
        }
482
    }
483
484
    /**
485
     * Insert add issue to message queue.
486
     *
487
     * @param Project\Issue $issue
488
     * @param User          $changeBy
489
     *
490
     * @return void
491
     */
492
    public function queueAdd(Project\Issue $issue, User $changeBy)
493
    {
494
        return (new Queue())->updater($changeBy)->queue(Queue::ADD_ISSUE, $issue, $changeBy);
495
    }
496
497
    /**
498
     * Insert assign issue to message queue.
499
     *
500
     * @param Project\Issue $issue
501
     * @param User          $changeBy
502
     *
503
     * @return bool|int
504
     */
505
    public function queueAssign(Project\Issue $issue, User $changeBy)
506
    {
507
        // Whether or not the assignee has changed
508
        $return = false;
509
510
        // If the assignee has changed and it is not the logged in user who made the action
511
        if ($issue->assigned_to > 0 && $changeBy->id !== $issue->assigned_to && $issue->assigned_to !== $issue->getOriginal('assigned_to', $issue->assigned_to)) {
512
            (new Queue())->updater($changeBy)->queue(Queue::ASSIGN_ISSUE, $issue, $changeBy);
513
514
            $return = $issue->assigned_to;
515
        }
516
517
        return $return;
518
    }
519
520
    /**
521
     * Insert issue tag changes to message queue.
522
     *
523
     * @param Project\Issue $issue
524
     * @param array         $addedTags
525
     * @param array         $removedTags
526
     * @param User          $changeBy
527
     *
528
     * @return mixed
529
     */
530
    public function queueChangeTags(Project\Issue $issue, array $addedTags, array $removedTags, User $changeBy)
531
    {
532
        return (new Queue())->updater($changeBy)->queueIssueTagChanges($issue, $addedTags, $removedTags, $changeBy);
533
    }
534
}
535