Completed
Push — develop-3.0 ( 4fe777...24fc5d )
by Mohamed
09:15
created

Updater::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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\Contracts\Model\UserInterface;
16
use Tinyissue\Model\Activity;
17
use Tinyissue\Model\Permission;
18
use Tinyissue\Model\Project;
19
use Tinyissue\Model\Tag;
20
use Tinyissue\Model\User;
21
use Tinyissue\Repository\RepositoryUpdater;
22
23
class Updater extends RepositoryUpdater
24
{
25
    public function __construct(Project\Issue $model)
26
    {
27
        $this->model = $model;
28
    }
29
30
    /**
31
     * Set the issue is updated by a user.
32
     *
33
     * @param int $userId
34
     *
35
     * @return Eloquent\Model
36
     */
37
    public function changeUpdatedBy($userId)
38
    {
39
        $this->model->updated_by = $userId;
40
        $this->model->touch();
41
42
        return $this->save();
43
    }
44
45
    /**
46
     * Reassign the issue to a new user.
47
     *
48
     * @param int|UserInterface $assignTo
49
     * @param UserInterface     $user
50
     *
51
     * @return Eloquent\Model
52
     */
53
    public function reassign($assignTo, UserInterface $user)
54
    {
55
        $assignToId = !$assignTo instanceof UserInterface ? $assignTo : $assignTo->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Tinyissue\Contracts\Model\UserInterface 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...
56
        $this->model->assigned_to = $assignToId;
57
58
        // Add event on successful save
59
//        static::saved(function (Project\Issue $issue) use ($user) {
60
////            $this->queueAssign($issue, $user);// TODO
61
//        });
62
63
        $this->save();
64
65
        return $this->saveToActivities([
66
            'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
67
            'parent_id' => $this->model->project->id,
68
            'user_id'   => $user->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Tinyissue\Contracts\Model\UserInterface 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...
69
            'action_id' => $this->model->assigned_to,
70
        ]);
71
    }
72
73
    /**
74
     * Update the given issue.
75
     *
76
     * @param array $input
77
     *
78
     * @return Eloquent\Model
79
     */
80
    public function update(array $input = [])
81
    {
82
        $fill = array_only($input, [
83
            'title', 'body', 'assigned_to',
84
        ]);
85
        $fill['updated_by'] = $this->model->updatedBy->id;
86
87 View Code Duplication
        if (isset($input['time_quote']['lock'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
88
            $fill['lock_quote'] = $input['time_quote']['lock'];
89
        }
90
91
        // Only save quote if not locked or locked & user allowed to modify it
92
        if (array_key_exists('time_quote', $input) &&
93
            (!$this->model->isQuoteLocked() || $this->getLoggedUser()->can('lockQuote', $this->model))
94
        ) {
95
            $fill['time_quote'] = $input['time_quote'];
96
        }
97
98
        /* Add to activity log for assignment if changed */
99
        $assignToId = (int)array_get($input, 'assigned_to');
100
        if ($assignToId > 0 && $assignToId !== (int)$this->model->assigned_to) {
101
            $this->saveToActivities([
102
                'type_id'   => Activity::TYPE_REASSIGN_ISSUE,
103
                'parent_id' => $this->model->project->id,
104
                'user_id'   => $this->model->updatedBy->id,
105
                'action_id' => $this->model->assigned_to,
106
            ]);
107
        }
108
109
        $this->model->fill($fill);
110
111
        $this->syncTags($input, $this->model->tags()->with('parent')->get());
112
113
        // Add event on successful save
114
//        static::saved(function (Project\Issue $issue) {
115
////            $this->queueUpdate($issue, $issue->updatedBy);// TODO
116
//        });
117
118
        return $this->save();
119
    }
120
121
    /**
122
     * Create a new issue.
123
     *
124
     * @param array $input
125
     *
126
     * @return static
127
     */
128
    public function create(array $input)
129
    {
130
        // TODO better setRelations??
131
        if (array_key_exists('project_id', $input)) {
132
            $this->model->setRelation('project', Project::find((int)$input['project_id']));
133
        }
134
135
        $fill = [
136
            'created_by'  => $this->model->user->id,
137
            'project_id'  => $this->model->project->id,
138
            'title'       => $input['title'],
139
            'body'        => $input['body'],
140
            'assigned_to' => (int)$this->model->project->default_assignee,
141
        ];
142
143
        // TODO thsi is crayp fix
144
        if ($this->model->user->isDeveloperOrMore()) {
145
            if (array_key_exists('assigned_to', $input)) {
146
                $fill['assigned_to'] = $input['assigned_to'];
147
            }
148
            if (array_key_exists('time_quote', $input)) {
149
                $fill['time_quote'] = $input['time_quote'];
150
            }
151
        }
152
153
        $this->model->fill($fill)->save();
154
155
        // Add issue to messages queue
156
//        $this->queueAdd($this, $this->user);// TODO
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()->updateIssueToken($input['upload_token'], $this->model->user->id, $this->model->id);
0 ignored issues
show
Documentation Bug introduced by
The method updateIssueToken does not exist on object<Tinyissue\Model\Project\Issue\Attachment>? 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...
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;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (Tinyissue\Repository\Project\Issue\Updater) is incompatible with the return type of the parent method Tinyissue\Repository\RepositoryUpdater::create of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
177
    }
178
179
    /**
180
     * Move the issue (comments & activities) to another project.
181
     *
182
     * @param int $projectId
183
     *
184
     * @return $this
185
     */
186 View Code Duplication
    public function changeProject($projectId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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;
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
        $id = $this->model->id;
220
        $projectId = $this->model->project_id;
221
        $comments = $this->model->comments;
222
        $attachments = $this->model->attachments;
223
224
        $status = $this->model->delete();
225
226
        if ($status) {
227
            $attachments->each(function (Attachment $attachment) use ($projectId) {
228
                $path = $this->getUploadStorage($projectId, $attachment->upload_token);
229
                $attachment->deleteFile($path, $attachment->filename);
230
                $attachment->delete();
231
            });
232
            $comments->each(function (Project\Issue\Comment $comment) {
233
                $comment->deleteComment($this->getLoggedUser()->user());
0 ignored issues
show
Bug introduced by
The method user() does not seem to exist on object<Tinyissue\Contracts\Model\UserInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Documentation Bug introduced by
The method deleteComment does not exist on object<Tinyissue\Model\Project\Issue\Comment>? 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...
234
            });
235
            User\Activity::where('parent_id', '=', $projectId)->where('item_id', '=', $id)->delete();
236
            \DB::table('projects_issues_tags')->where('issue_id', '=', $id)->delete();
237
        }
238
239
        return $status;
240
    }
241
242
    /**
243
     * Change the status of an issue.
244
     *
245
     * @param int           $status
246
     * @param UserInterface $user
247
     *
248
     * @return Eloquent\Model
249
     */
250
    public function changeStatus($status, UserInterface $user)
251
    {
252
        if ($status == 0) {
253
            $this->model->closed_by = $user->id;
0 ignored issues
show
Bug introduced by
Accessing id on the interface Tinyissue\Contracts\Model\UserInterface 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...
254
            $this->model->closed_at = (new \DateTime())->format('Y-m-d H:i:s');
255
            $activityType = Activity::TYPE_CLOSE_ISSUE;
256
        } else {
257
            $this->model->closed_by = 0;
258
            $this->model->closed_at = null;
259
            $activityType = Activity::TYPE_REOPEN_ISSUE;
260
        }
261
262
        /* Add to activity log */
263
        $this->saveToActivities([
264
            'type_id'   => $activityType,
265
            'parent_id' => $this->model->project->id,
266
            'user_id'   => $user->id,
0 ignored issues
show
Bug introduced by
Accessing id on the interface Tinyissue\Contracts\Model\UserInterface 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...
267
        ]);
268
269
        $this->model->status = $status;
270
271
        // Add event on successful save
272
//        static::saved(function (Project\Issue $issue) use ($user) {
273
////            $this->queueUpdate($issue, $user);// TODO
274
//        });
275
276
        return $this->save();
277
    }
278
279
    /**
280
     * Sync the issue tags.
281
     *
282
     * @param array      $input
283
     * @param Collection $currentTags
284
     *
285
     * @return bool
286
     */
287
    public function syncTags(array $input, Collection $currentTags = null)
288
    {
289
        $tagIds = array_only($input, [
290
            'tag_type', 'tag_status', 'tag_resolution',
291
        ]);
292
293
        // User can edit their own role and can only change issue type
294
        if ($this->model->updatedBy instanceof UserInterface && $this->model->updatedBy->isUser()) {
295
            $currentTagIds = $currentTags->pluck('id', 'parent.name')->toArray();
0 ignored issues
show
Bug introduced by
It seems like $currentTags is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
296
            $tagIds['tag_status'] = array_key_exists('status', $currentTagIds) ? $currentTagIds['status'] : 0;
297
            $tagIds['tag_resolution'] = array_key_exists('resolution', $currentTagIds) ? $currentTagIds['resolution'] : 0;
298
        }
299
300
        $tags = (new Tag())->whereIn('id', $tagIds)->get();
0 ignored issues
show
Documentation Bug introduced by
The method whereIn does not exist on object<Tinyissue\Model\Tag>? 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...
301
302
        $removedTags = [];
303 View Code Duplication
        if (null === $currentTags) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
304
            // Add the following tags except for open status
305
            $addedTags = $tags
306
                ->map(function (Tag $tag) {
307
                    return $tag->toShortArray();
308
                })
309
                ->toArray();
310
        } else {
311
            // Tags remove from the issue
312
            $removedTags = $currentTags
313
                ->diff($tags)
314
                ->map(function (Tag $tag) {
315
                    return $tag->toShortArray();
316
                })
317
                ->toArray();
318
319
            // Check if we are adding new tags
320
            $addedTags = $tags
321
                ->filter(function (Tag $tag) use ($currentTags) {
322
                    return $currentTags->where('id', $tag->id)->count() === 0;
323
                })
324
                ->map(function (Tag $tag) {
325
                    return $tag->toShortArray();
326
                })
327
                ->toArray();
328
329
            // No new tags to add or remove
330
            if (empty($removedTags) && empty($addedTags)) {
331
                return true;
332
            }
333
        }
334
335
        // Save relation
336
        $this->model->tags()->sync($tags->pluck('id')->all());
337
338
        // Activity is added when new issue create with tags or updated with tags excluding the open status tag
339 View Code Duplication
        if (!empty($removedTags) || !empty($addedTags)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
340
            // Add this change to messages queue
341
//            $this->queueChangeTags($this, $addedTags, $removedTags, $this->user);// TODO
342
343
            // Add to activity log for tags if changed
344
            $this->saveToActivities([
345
                'type_id'   => Activity::TYPE_ISSUE_TAG,
346
                'parent_id' => $this->model->project->id,
347
                'user_id'   => $this->model->user->id,
348
                'data'      => ['added_tags' => $addedTags, 'removed_tags' => $removedTags],
349
            ]);
350
        }
351
352
        return true;
353
    }
354
355
    /**
356
     * Add tag to the issue & close issue if added tag is Closed.
357
     *
358
     * @param Tag $newTag
359
     * @param Tag $oldTag
360
     *
361
     * @return $this
362
     */
363
    public function changeKanbanTag(Tag $newTag, Tag $oldTag)
364
    {
365
        //  skip if there is no change in status tags
366
        if ($oldTag->name === $newTag->name) {
367
            return $this;
368
        }
369
370
        // Open issue
371
        $data = ['added_tags' => [], 'removed_tags' => []];
372
373
        // Remove previous status tag
374
        $this->model->tags()->detach($oldTag);
375
        $data['removed_tags'][] = $oldTag->toShortArray();
376
377
        // Add new tag
378 View Code Duplication
        if (!$this->model->tags->contains($newTag)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
379
            $this->model->tags()->attach($newTag);
380
381
            $data['added_tags'][] = $newTag->toShortArray();
382
        }
383
384 View Code Duplication
        if (!empty($data)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
            // Add this change to messages queue
386
//            $this->queueChangeTags($this, $data['added_tags'], $data['removed_tags'], $this->user);// TODO
387
388
            // Add to activity log for tags if changed
389
            $this->saveToActivities([
390
                'type_id'   => Activity::TYPE_ISSUE_TAG,
391
                'parent_id' => $this->model->project->id,
392
                'user_id'   => $this->model->user->id,
393
                'data'      => $data,
394
            ]);
395
        }
396
397
        return $this;
398
    }
399
}
400