Completed
Branch develop-3.0 (63d375)
by Mohamed
02:58
created

Updater::update()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 3
eloc 12
nc 2
nop 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\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
    use MessageQueuer;
25
26
    /**
27
     * @var Project\Issue
28
     */
29
    protected $model;
30
31
    public function __construct(Project\Issue $model)
32
    {
33
        $this->model = $model;
34
    }
35
36
    /**
37
     * Set the issue is updated by a user.
38
     *
39
     * @return Project\Issue
40
     */
41
    public function changeUpdatedBy()
42
    {
43
        $this->model->updated_by = $this->user->id;
44
        $this->model->touch();
45
46
        return $this->save();
47
    }
48
49
    /**
50
     * Reassign the issue to a new user.
51
     *
52
     * @param int|User $assignTo
53
     * @param User     $user
54
     *
55
     * @return Project\Issue
56
     */
57
    public function reassign($assignTo, User $user)
58
    {
59
        $this->setUser($user);
60
        $assignToId               = !$assignTo instanceof User ? $assignTo : $assignTo->id;
61
        $this->model->assigned_to = $assignToId;
62
63
        // Add event on successful save
64
        Project\Issue::saved(function (Project\Issue $issue) {
65
            $this->queueAssign($issue, $this->user);
66
        });
67
68
        $this->save();
69
70
        $this->saveToActivities([
71
            'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
72
            'parent_id' => $this->model->project->id,
73
            'user_id'   => $user->id,
74
            'action_id' => $this->model->assigned_to,
75
        ]);
76
77
        return $this->model;
78
    }
79
80
    protected function filterUpdateAttributes(array $input)
81
    {
82
        $fill               = array_only($input, ['title', 'body', 'assigned_to']);
83
        $fill['updated_by'] = $this->model->updatedBy->id;
84
        $fill['lock_quote'] = (bool) isset($input['time_quote']['lock']);
85
86
        // Only save quote if not locked or locked & user allowed to modify it
87
        if (array_key_exists('time_quote', $input) &&
88
            (!$this->model->isQuoteLocked() || $this->getLoggedUser()->can('lockQuote', $this->model))
89
        ) {
90
            $fill['time_quote'] = $input['time_quote'];
91
        }
92
93
        return $fill;
94
    }
95
96
    /**
97
     * Update the given issue.
98
     *
99
     * @param array $input
100
     *
101
     * @return Project\Issue
102
     */
103
    public function update(array $input = [])
104
    {
105
        $this->model->fill($this->filterUpdateAttributes($input));
106
107
        /* Add to activity log for assignment if changed */
108
        if ($this->model->isDirty('assigned_to') && $this->model->assigned_to > 0) {
109
            $this->saveToActivities([
110
                'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
111
                'parent_id' => $this->model->project->id,
112
                'user_id'   => $this->model->updatedBy->id,
113
                'action_id' => $this->model->assigned_to,
114
            ]);
115
        }
116
117
        $this->syncTags($input, $this->model->tags()->with('parent')->get());
118
119
        // Add event on successful save
120
        Project\Issue::saved(function (Project\Issue $issue) {
121
            $this->queueUpdate($issue, $this->user);
122
        });
123
124
        return $this->save();
125
    }
126
127
    /**
128
     * Create a new issue.
129
     *
130
     * @param array $input
131
     *
132
     * @return Project\Issue
133
     */
134
    public function create(array $input)
135
    {
136
        if (array_key_exists('project_id', $input)) {
137
            $this->model->setRelation('project', Project::find((int) $input['project_id']));
138
        }
139
140
        $fill = [
141
            'created_by'  => $this->model->user->id,
142
            'project_id'  => $this->model->project->id,
143
            'title'       => $input['title'],
144
            'body'        => $input['body'],
145
            'assigned_to' => (int) $this->model->project->default_assignee,
146
        ];
147
148
        if ($this->model->user->isDeveloperOrMore()) {
149
            $fill['assigned_to'] = array_get($input, 'assigned_to', $fill['assigned_to']);
150
            $fill['time_quote']  = array_get($input, 'time_quote');
151
        }
152
153
        $this->model->fill($fill)->save();
154
155
        // Add issue to messages queue
156
        $this->queueAdd($this->model, $this->user);
157
158
        /* Add to user's activity log */
159
        $this->saveToActivities([
160
            'type_id'   => Activity::TYPE_CREATE_ISSUE,
161
            'parent_id' => $this->model->project->id,
162
            'user_id'   => $this->model->user->id,
163
        ]);
164
165
        /* Add attachments to issue */
166
        Project\Issue\Attachment::instance()->updater()->updateIssueToken($input['upload_token'], $this->model->user->id, $this->model->id);
167
168
        // Add default tag to newly created issue
169
        $defaultTag = app('tinyissue.settings')->getFirstStatusTagId();
170
        if ($defaultTag > 0 && empty($input['tag_status'])) {
171
            $input['tag_status'] = $defaultTag;
172
        }
173
174
        $this->syncTags($input);
175
176
        return $this->model;
177
    }
178
179
    /**
180
     * Move the issue (comments & activities) to another project.
181
     *
182
     * @param int $projectId
183
     *
184
     * @return Project\Issue
185
     */
186
    public function changeProject($projectId)
187
    {
188
        $this->model->project_id = $projectId;
189
        $this->save();
190
        $comments = $this->model->comments()->get();
191
        foreach ($comments as $comment) {
192
            $comment->project_id = $projectId;
193
            $comment->save();
194
        }
195
196
        $activities = $this->model->activities()->get();
197
        foreach ($activities as $activity) {
198
            $activity->parent_id = $projectId;
199
            $activity->save();
200
        }
201
202
        return $this->model;
203
    }
204
205
    /**
206
     * Delete an issue.
207
     *
208
     * @return bool
209
     *
210
     * @throws \Exception
211
     */
212
    public function delete()
213
    {
214
        return $this->transaction('deleteIssue');
215
    }
216
217
    protected function deleteIssue()
218
    {
219
        // Delete issue related data
220
        $this->deleteComments();
221
        $this->deleteAttachments();
222
        $this->deleteUserActivities();
223
        $this->deleteIssueTags();
224
225
        // Delete the issue
226
        return $this->model->delete();
227
    }
228
229
    /**
230
     * @return void
231
     */
232
    protected function deleteComments()
233
    {
234
        $this->model->comments->each(function (Project\Issue\Comment $comment) {
235
            $comment->updater($this->getLoggedUser())->delete();
236
        });
237
    }
238
239
    /**
240
     * @return void
241
     */
242
    protected function deleteAttachments()
243
    {
244
        $this->model->attachments->each(function (Project\Issue\Attachment $attachment) {
245
            $attachment->updater()->delete();
246
        });
247
    }
248
249
    /**
250
     * @return void
251
     */
252
    protected function deleteIssueTags()
253
    {
254
        \DB::table('projects_issues_tags')->where('issue_id', '=', $this->model->id)->delete();
255
    }
256
257
    /**
258
     * @return void
259
     */
260
    protected function deleteUserActivities()
261
    {
262
        User\Activity::where('parent_id', '=', $this->model->project_id)
263
            ->where('item_id', '=', $this->model->id)
264
            ->delete();
265
    }
266
267
    /**
268
     * Change the status of an issue.
269
     *
270
     * @param int  $status
271
     * @param User $user
272
     *
273
     * @return Project\Issue
274
     */
275
    public function changeStatus($status, User $user)
276
    {
277
        if ($status == 0) {
278
            $this->model->closed_by = $user->id;
279
            $this->model->closed_at = (new \DateTime())->format('Y-m-d H:i:s');
280
            $activityType           = Activity::TYPE_CLOSE_ISSUE;
281
        } else {
282
            $this->model->closed_by = 0;
283
            $this->model->closed_at = null;
284
            $activityType           = Activity::TYPE_REOPEN_ISSUE;
285
        }
286
287
        /* Add to activity log */
288
        $this->saveToActivities([
289
            'type_id'   => $activityType,
290
            'parent_id' => $this->model->project->id,
291
            'user_id'   => $user->id,
292
        ]);
293
294
        $this->model->status = $status;
295
296
        // Add event on successful save
297
        Project\Issue::saved(function (Project\Issue $issue) {
298
            $this->queueUpdate($issue, $this->user);
299
        });
300
301
        return $this->save();
302
    }
303
304
    /**
305
     * Sync the issue tags.
306
     *
307
     * @param array      $input
308
     * @param Collection $currentTags
309
     *
310
     * @return bool
311
     */
312
    public function syncTags(array $input, Collection $currentTags = null)
313
    {
314
        $tagIds = array_only($input, [
315
            'tag_type', 'tag_status', 'tag_resolution',
316
        ]);
317
        $currentTags = is_null($currentTags) ? Collection::make([]) : $currentTags;
318
319
        // User can edit their own role and can only change issue type
320
        if ($this->model->updatedBy instanceof User && $this->model->updatedBy->isUser()) {
321
            $currentTagIds            = $currentTags->pluck('id', 'parent.name')->toArray();
322
            $tagIds['tag_status']     = array_key_exists('status', $currentTagIds) ? $currentTagIds['status'] : 0;
323
            $tagIds['tag_resolution'] = array_key_exists('resolution', $currentTagIds) ? $currentTagIds['resolution'] : 0;
324
        }
325
326
        $tags = (new Tag())->whereIn('id', $tagIds)->get();
327
328
        $removedTags = [];
329
        if (null === $currentTags) {
330
            // Add the following tags except for open status
331
            $addedTags = $tags
332
                ->map(function (Tag $tag) {
333
                    return $tag->toShortArray();
334
                })
335
                ->toArray();
336
        } else {
337
            // Tags remove from the issue
338
            $removedTags = $currentTags
339
                ->diff($tags)
340
                ->map(function (Tag $tag) {
341
                    return $tag->toShortArray();
342
                })
343
                ->toArray();
344
345
            // Check if we are adding new tags
346
            $addedTags = $tags
347
                ->filter(function (Tag $tag) use ($currentTags) {
348
                    return $currentTags->where('id', $tag->id)->count() === 0;
349
                })
350
                ->map(function (Tag $tag) {
351
                    return $tag->toShortArray();
352
                })
353
                ->toArray();
354
355
            // No new tags to add or remove
356
            if (empty($removedTags) && empty($addedTags)) {
357
                return true;
358
            }
359
        }
360
361
        // Save relation
362
        $this->model->tags()->sync($tags->pluck('id')->all());
363
364
        // Activity is added when new issue create with tags or updated with tags excluding the open status tag
365
        if (!empty($removedTags) || !empty($addedTags)) {
366
            // Add this change to messages queue
367
            $this->queueChangeTags($this->model, $addedTags, $removedTags, $this->user);
368
369
            // Add to activity log for tags if changed
370
            $this->saveToActivities([
371
                'type_id'   => Activity::TYPE_ISSUE_TAG,
372
                'parent_id' => $this->model->project->id,
373
                'user_id'   => $this->model->user->id,
374
                'data'      => ['added_tags' => $addedTags, 'removed_tags' => $removedTags],
375
            ]);
376
        }
377
378
        return true;
379
    }
380
381
    /**
382
     * Add tag to the issue & close issue if added tag is Closed.
383
     *
384
     * @param Tag $newTag
385
     * @param Tag $oldTag
386
     *
387
     * @return Project\Issue
388
     */
389
    public function changeKanbanTag(Tag $newTag, Tag $oldTag)
390
    {
391
        //  skip if there is no change in status tags
392
        if ($oldTag->name === $newTag->name) {
393
            return $this->model;
394
        }
395
396
        // Open issue
397
        $data = ['added_tags' => [], 'removed_tags' => []];
398
399
        // Remove previous status tag
400
        $this->model->tags()->detach($oldTag);
401
        $data['removed_tags'][] = $oldTag->toShortArray();
402
403
        // Add new tag
404
        if (!$this->model->tags->contains($newTag)) {
405
            $this->model->tags()->attach($newTag);
406
407
            $data['added_tags'][] = $newTag->toShortArray();
408
        }
409
410
        if (!empty($data)) {
411
            // Add this change to messages queue
412
            $this->queueChangeTags($this->model, $data['added_tags'], $data['removed_tags'], $this->user);
413
414
            // Add to activity log for tags if changed
415
            $this->saveToActivities([
416
                'type_id'   => Activity::TYPE_ISSUE_TAG,
417
                'parent_id' => $this->model->project->id,
418
                'user_id'   => $this->model->user->id,
419
                'data'      => $data,
420
            ]);
421
        }
422
423
        return $this->model;
424
    }
425
}
426