Completed
Push — develop-3.0 ( 5ab583...f20237 )
by Mohamed
06:33
created

Updater::create()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 47
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 47
rs 8.5125
cc 5
eloc 25
nc 8
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
    /**
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
        // Project internal issue number
152
        $this->model->issue_no = $this->model->forProject($this->model->project->id)->max('issue_no') + 1;
0 ignored issues
show
Documentation Bug introduced by
The method max does not exist on object<Tinyissue\Model\Project\Issue>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
153
154
        $this->model->fill($fill)->save();
155
156
        // Add issue to messages queue
157
        $this->queueAdd($this->model, $this->user);
158
159
        /* Add to user's activity log */
160
        $this->saveToActivities([
161
            'type_id'   => Activity::TYPE_CREATE_ISSUE,
162
            'parent_id' => $this->model->project->id,
163
            'user_id'   => $this->model->user->id,
164
        ]);
165
166
        /* Add attachments to issue */
167
        Project\Issue\Attachment::instance()->updater()->updateIssueToken($input['upload_token'], $this->model->user->id, $this->model->id);
168
169
        // Add default tag to newly created issue
170
        $defaultTag = app('tinyissue.settings')->getFirstStatusTagId();
171
        if ($defaultTag > 0 && empty($input['tag_status'])) {
172
            $input['tag_status'] = $defaultTag;
173
        }
174
175
        $this->syncTags($input);
176
177
        return $this->model;
178
    }
179
180
    /**
181
     * Move the issue (comments & activities) to another project.
182
     *
183
     * @param int $projectId
184
     *
185
     * @return Project\Issue
186
     */
187
    public function changeProject($projectId)
188
    {
189
        $this->model->project_id = $projectId;
190
        $this->save();
191
        $comments = $this->model->comments()->get();
192
        foreach ($comments as $comment) {
193
            $comment->project_id = $projectId;
194
            $comment->save();
195
        }
196
197
        $activities = $this->model->activities()->get();
198
        foreach ($activities as $activity) {
199
            $activity->parent_id = $projectId;
200
            $activity->save();
201
        }
202
203
        return $this->model;
204
    }
205
206
    /**
207
     * Delete an issue.
208
     *
209
     * @return bool
210
     *
211
     * @throws \Exception
212
     */
213
    public function delete()
214
    {
215
        return $this->transaction('deleteIssue');
216
    }
217
218
    protected function deleteIssue()
219
    {
220
        // Delete issue related data
221
        $this->deleteComments();
222
        $this->deleteAttachments();
223
        $this->deleteUserActivities();
224
        $this->deleteIssueTags();
225
226
        // Delete the issue
227
        return $this->model->delete();
228
    }
229
230
    /**
231
     * @return void
232
     */
233
    protected function deleteComments()
234
    {
235
        $this->model->comments->each(function (Project\Issue\Comment $comment) {
236
            $comment->updater($this->getLoggedUser())->delete();
237
        });
238
    }
239
240
    /**
241
     * @return void
242
     */
243
    protected function deleteAttachments()
244
    {
245
        $this->model->attachments->each(function (Project\Issue\Attachment $attachment) {
246
            $attachment->updater()->delete();
247
        });
248
    }
249
250
    /**
251
     * @return void
252
     */
253
    protected function deleteIssueTags()
254
    {
255
        \DB::table('projects_issues_tags')->where('issue_id', '=', $this->model->id)->delete();
256
    }
257
258
    /**
259
     * @return void
260
     */
261
    protected function deleteUserActivities()
262
    {
263
        User\Activity::where('parent_id', '=', $this->model->project_id)
264
            ->where('item_id', '=', $this->model->id)
265
            ->delete();
266
    }
267
268
    /**
269
     * Change the status of an issue.
270
     *
271
     * @param int  $status
272
     * @param User $user
273
     *
274
     * @return Project\Issue
275
     */
276
    public function changeStatus($status, User $user)
277
    {
278
        if ($status == 0) {
279
            $this->model->closed_by = $user->id;
280
            $this->model->closed_at = (new \DateTime())->format('Y-m-d H:i:s');
281
            $activityType = Activity::TYPE_CLOSE_ISSUE;
282
        } else {
283
            $this->model->closed_by = 0;
284
            $this->model->closed_at = null;
285
            $activityType = Activity::TYPE_REOPEN_ISSUE;
286
        }
287
288
        /* Add to activity log */
289
        $this->saveToActivities([
290
            'type_id'   => $activityType,
291
            'parent_id' => $this->model->project->id,
292
            'user_id'   => $user->id,
293
        ]);
294
295
        $this->model->status = $status;
296
297
        // Add event on successful save
298
        Project\Issue::saved(function (Project\Issue $issue) {
299
            $this->queueUpdate($issue, $this->user);
300
        });
301
302
        return $this->save();
303
    }
304
305
    /**
306
     * Sync the issue tags.
307
     *
308
     * @param array      $input
309
     * @param Collection $currentTags
310
     *
311
     * @return bool
312
     */
313
    public function syncTags(array $input, Collection $currentTags = null)
314
    {
315
        $tagIds = array_only($input, [
316
            'tag_type', 'tag_status', 'tag_resolution',
317
        ]);
318
        $currentTags = is_null($currentTags) ? Collection::make([]) : $currentTags;
319
320
        // User can edit their own role and can only change issue type
321
        if ($this->model->updatedBy instanceof User && $this->model->updatedBy->isUser()) {
322
            $currentTagIds = $currentTags->pluck('id', 'parent.name')->toArray();
323
            $tagIds['tag_status'] = array_key_exists('status', $currentTagIds) ? $currentTagIds['status'] : 0;
324
            $tagIds['tag_resolution'] = array_key_exists('resolution', $currentTagIds) ? $currentTagIds['resolution'] : 0;
325
        }
326
327
        $tags = (new Tag())->whereIn('id', $tagIds)->get();
328
329
        $removedTags = [];
330
        if (null === $currentTags) {
331
            // Add the following tags except for open status
332
            $addedTags = $tags
333
                ->map(function (Tag $tag) {
334
                    return $tag->toShortArray();
335
                })
336
                ->toArray();
337
        } else {
338
            // Tags remove from the issue
339
            $removedTags = $currentTags
340
                ->diff($tags)
341
                ->map(function (Tag $tag) {
342
                    return $tag->toShortArray();
343
                })
344
                ->toArray();
345
346
            // Check if we are adding new tags
347
            $addedTags = $tags
348
                ->filter(function (Tag $tag) use ($currentTags) {
349
                    return $currentTags->where('id', $tag->id)->count() === 0;
350
                })
351
                ->map(function (Tag $tag) {
352
                    return $tag->toShortArray();
353
                })
354
                ->toArray();
355
356
            // No new tags to add or remove
357
            if (empty($removedTags) && empty($addedTags)) {
358
                return true;
359
            }
360
        }
361
362
        // Save relation
363
        $this->model->tags()->sync($tags->pluck('id')->all());
364
365
        // Activity is added when new issue create with tags or updated with tags excluding the open status tag
366
        if (!empty($removedTags) || !empty($addedTags)) {
367
            // Add this change to messages queue
368
            $this->queueChangeTags($this->model, $addedTags, $removedTags, $this->user);
369
370
            // Add to activity log for tags if changed
371
            $this->saveToActivities([
372
                'type_id'   => Activity::TYPE_ISSUE_TAG,
373
                'parent_id' => $this->model->project->id,
374
                'user_id'   => $this->model->user->id,
375
                'data'      => ['added_tags' => $addedTags, 'removed_tags' => $removedTags],
376
            ]);
377
        }
378
379
        return true;
380
    }
381
382
    /**
383
     * Add tag to the issue & close issue if added tag is Closed.
384
     *
385
     * @param Tag $newTag
386
     * @param Tag $oldTag
387
     *
388
     * @return Project\Issue
389
     */
390
    public function changeKanbanTag(Tag $newTag, Tag $oldTag)
391
    {
392
        //  skip if there is no change in status tags
393
        if ($oldTag->name === $newTag->name) {
394
            return $this->model;
395
        }
396
397
        // Open issue
398
        $data = ['added_tags' => [], 'removed_tags' => []];
399
400
        // Remove previous status tag
401
        $this->model->tags()->detach($oldTag);
402
        $data['removed_tags'][] = $oldTag->toShortArray();
403
404
        // Add new tag
405
        if (!$this->model->tags->contains($newTag)) {
406
            $this->model->tags()->attach($newTag);
407
408
            $data['added_tags'][] = $newTag->toShortArray();
409
        }
410
411
        if (!empty($data)) {
412
            // Add this change to messages queue
413
            $this->queueChangeTags($this->model, $data['added_tags'], $data['removed_tags'], $this->user);
414
415
            // Add to activity log for tags if changed
416
            $this->saveToActivities([
417
                'type_id'   => Activity::TYPE_ISSUE_TAG,
418
                'parent_id' => $this->model->project->id,
419
                'user_id'   => $this->model->user->id,
420
                'data'      => $data,
421
            ]);
422
        }
423
424
        return $this->model;
425
    }
426
427
    /**
428
     * Insert update issue to message queue.
429
     *
430
     * @param Project\Issue $issue
431
     * @param User          $changeBy
432
     *
433
     * @return void
434
     */
435
    public function queueUpdate(Project\Issue $issue, User $changeBy)
436
    {
437
        // is Closed?
438
        $this->queueClosedIssue($issue, $changeBy);
439
440
        // is Reopened?
441
        $this->queueReopenedIssue($issue, $changeBy);
442
443
        // If the assignee has changed and it is not the logged in user who made the action
444
        $noMessageForMe = $this->queueAssign($issue, $changeBy);
445
446
        // If the update was just for assigning user, then skip update issue
447
        $this->queueUpdateIssue($issue, $changeBy, $noMessageForMe);
448
    }
449
450
    /**
451
     * @param Project\Issue $issue
452
     * @param User          $changeBy
453
     * @param bool|int      $noMessageForMe
454
     */
455
    protected function queueUpdateIssue(Project\Issue $issue, User $changeBy, $noMessageForMe = false)
456
    {
457
        // Number of changed attributes
458
        $countChanges = count($issue->getDirty());
459
460
        if (!($countChanges === 1 && $noMessageForMe !== false)) {
461
            return (new Queue())->updater($changeBy)->queue(Queue::UPDATE_ISSUE, $issue, $changeBy);
462
        }
463
    }
464
465
    /**
466
     * @param Project\Issue $issue
467
     * @param User          $changeBy
468
     */
469
    protected function queueClosedIssue(Project\Issue $issue, User $changeBy)
470
    {
471
        if (!$issue->isOpen()) {
472
            (new Queue())->updater($changeBy)->queue(Queue::CLOSE_ISSUE, $issue, $changeBy);
473
        }
474
    }
475
476
    /**
477
     * @param Project\Issue $issue
478
     * @param User          $changeBy
479
     */
480
    protected function queueReopenedIssue(Project\Issue $issue, User $changeBy)
481
    {
482
        if ((int)$issue->getOriginal('status') === Project\Issue::STATUS_CLOSED) {
483
            (new Queue())->updater($changeBy)->queue(Queue::REOPEN_ISSUE, $issue, $changeBy);
484
        }
485
    }
486
487
    /**
488
     * Insert add issue to message queue.
489
     *
490
     * @param Project\Issue $issue
491
     * @param User          $changeBy
492
     *
493
     * @return void
494
     */
495
    public function queueAdd(Project\Issue $issue, User $changeBy)
496
    {
497
        return (new Queue())->updater($changeBy)->queue(Queue::ADD_ISSUE, $issue, $changeBy);
498
    }
499
500
    /**
501
     * Insert assign issue to message queue.
502
     *
503
     * @param Project\Issue $issue
504
     * @param User          $changeBy
505
     *
506
     * @return bool|int
507
     */
508
    public function queueAssign(Project\Issue $issue, User $changeBy)
509
    {
510
        // Whether or not the assignee has changed
511
        $return = false;
512
513
        // If the assignee has changed and it is not the logged in user who made the action
514
        if ($issue->assigned_to > 0 && $changeBy->id !== $issue->assigned_to && $issue->assigned_to !== $issue->getOriginal('assigned_to', $issue->assigned_to)) {
515
            (new Queue())->updater($changeBy)->queue(Queue::ASSIGN_ISSUE, $issue, $changeBy);
516
517
            $return = $issue->assigned_to;
518
        }
519
520
        return $return;
521
    }
522
523
    /**
524
     * Insert issue tag changes to message queue.
525
     *
526
     * @param Project\Issue $issue
527
     * @param array         $addedTags
528
     * @param array         $removedTags
529
     * @param User          $changeBy
530
     *
531
     * @return mixed
532
     */
533
    public function queueChangeTags(Project\Issue $issue, array $addedTags, array $removedTags, User $changeBy)
534
    {
535
        return (new Queue())->updater($changeBy)->queueIssueTagChanges($issue, $addedTags, $removedTags, $changeBy);
536
    }
537
}
538