Completed
Branch develop-3.0 (39c5d3)
by Mohamed
03:05
created

IssueController   B

Complexity

Total Complexity 27

Size/Duplication

Total Lines 415
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 27
lcom 1
cbo 16
dl 0
loc 415
rs 8.4614
c 2
b 0
f 1

18 Methods

Rating   Name   Duplication   Size   Complexity  
A postAddComment() 0 11 1
B getIndex() 0 22 4
A postAssign() 0 8 1
A getDeleteComment() 0 6 1
A getNew() 0 8 1
A postNew() 0 10 1
A getEdit() 0 15 2
A postEdit() 0 18 2
A postEditComment() 0 12 2
A getClose() 0 14 2
B postUploadAttachment() 0 27 2
A getDeleteAttachment() 0 8 1
B getDisplayAttachment() 0 33 2
A getDownloadAttachment() 0 9 1
A postChangeProject() 0 6 1
A postChangeKanbanTag() 0 9 1
A getIssueComments() 0 12 1
A getIssueActivity() 0 12 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\Http\Controllers\Project;
13
14
use Illuminate\Http\Request;
15
use Illuminate\Http\Response;
16
use Tinyissue\Form\Comment as CommentForm;
17
use Tinyissue\Form\Issue as IssueForm;
18
use Tinyissue\Http\Controllers\Controller;
19
use Tinyissue\Http\Requests\FormRequest;
20
use Tinyissue\Model\Project;
21
use Tinyissue\Model\Project\Issue;
22
use Tinyissue\Model\Project\Issue\Attachment;
23
use Tinyissue\Model\Project\Issue\Comment;
24
use Tinyissue\Model\Tag;
25
26
/**
27
 * IssueController is the controller class for managing request related to projects issues.
28
 *
29
 * @author Mohamed Alsharaf <[email protected]>
30
 */
31
class IssueController extends Controller
32
{
33
    /**
34
     * Project issue index page (List project issues).
35
     *
36
     * @param Project     $project
37
     * @param Issue       $issue
38
     * @param CommentForm $form
39
     *
40
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
41
     */
42
    public function getIndex(Project $project, Issue $issue, CommentForm $form)
43
    {
44
        $canEdit           = $this->allows('update', $issue);
45
        $usersCanFixIssues = $canEdit && $issue->status == Issue::STATUS_OPEN ? $project->getUsersCanFixIssue() : [];
46
47
        // Projects should be limited to issue-modify
48
        $projects = null;
49
        if ($canEdit) {
50
            $projects = $this->getLoggedUser()->getProjects();
51
        }
52
53
        return view('project.issue.index', [
54
            'issue'               => $issue,
55
            'usersCanFixIssues'   => $usersCanFixIssues,
56
            'project'             => $project,
57
            'closed_issues_count' => $project->countClosedIssues($this->getLoggedUser()),
58
            'open_issues_count'   => $project->countOpenIssues($this->getLoggedUser()),
59
            'commentForm'         => $form,
60
            'sidebar'             => 'project',
61
            'projects'            => $projects,
62
        ]);
63
    }
64
65
    /**
66
     * Ajax: Assign new user to an issue.
67
     *
68
     * @param Issue   $issue
69
     * @param Request $request
70
     *
71
     * @return \Symfony\Component\HttpFoundation\Response
72
     */
73
    public function postAssign(Issue $issue, Request $request)
74
    {
75
        $response = [
76
            'status' => $issue->updater($this->getLoggedUser())->reassign((int) $request->input('user_id'), $this->getLoggedUser()),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Tinyissue\Repository\RepositoryUpdater as the method reassign() does only exist in the following sub-classes of Tinyissue\Repository\RepositoryUpdater: Tinyissue\Repository\Project\Issue\Updater. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
77
        ];
78
79
        return response()->json($response);
80
    }
81
82
    /**
83
     * Ajax: save comment.
84
     *
85
     * @param Comment $comment
86
     * @param Request $request
87
     *
88
     * @return \Symfony\Component\HttpFoundation\Response
89
     */
90
    public function postEditComment(Comment $comment, Request $request)
91
    {
92
        $body = '';
93
        if ($request->has('body')) {
94
            $comment
0 ignored issues
show
Bug introduced by
The method updateBody() does not exist on Tinyissue\Repository\RepositoryUpdater. Did you maybe mean update()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
95
                ->updater($this->getLoggedUser())
96
                ->updateBody((string) $request->input('body'), $this->getLoggedUser());
97
            $body = \Html::format($comment->comment);
98
        }
99
100
        return response()->json(['text' => $body]);
101
    }
102
103
    /**
104
     * To add new comment to an issue.
105
     *
106
     * @param Project             $project
107
     * @param Issue               $issue
108
     * @param Comment             $comment
109
     * @param FormRequest\Comment $request
110
     *
111
     * @return \Illuminate\Http\RedirectResponse
112
     */
113
    public function postAddComment(Project $project, Issue $issue, Comment $comment, FormRequest\Comment $request)
114
    {
115
        $comment->setRelations([
116
            'project' => $project,
117
            'issue'   => $issue,
118
            'user'    => $this->getLoggedUser(),
119
        ]);
120
        $comment->updater($this->getLoggedUser())->create($request->all());
121
122
        return redirect($issue->to() . '#comment' . $comment->id)->with('notice', trans('tinyissue.your_comment_added'));
123
    }
124
125
    /**
126
     * Ajax: to delete a comment.
127
     *
128
     * @param Comment $comment
129
     *
130
     * @return \Symfony\Component\HttpFoundation\Response
131
     */
132
    public function getDeleteComment(Comment $comment)
133
    {
134
        $comment->updater($this->getLoggedUser())->delete();
135
136
        return response()->json(['status' => true]);
137
    }
138
139
    /**
140
     * New issue form.
141
     *
142
     * @param Project   $project
143
     * @param IssueForm $form
144
     *
145
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
146
     */
147
    public function getNew(Project $project, IssueForm $form)
148
    {
149
        return view('project.issue.new', [
150
            'project' => $project,
151
            'form'    => $form,
152
            'sidebar' => 'project',
153
        ]);
154
    }
155
156
    /**
157
     * To create a new issue.
158
     *
159
     * @param Project           $project
160
     * @param Issue             $issue
161
     * @param FormRequest\Issue $request
162
     *
163
     * @return \Illuminate\Http\RedirectResponse
164
     */
165
    public function postNew(Project $project, Issue $issue, FormRequest\Issue $request)
166
    {
167
        $issue->setRelations([
168
            'project' => $project,
169
            'user'    => $this->getLoggedUser(),
170
        ]);
171
        $issue->updater($this->getLoggedUser())->create($request->all());
172
173
        return redirect($issue->to())->with('notice', trans('tinyissue.issue_has_been_created'));
174
    }
175
176
    /**
177
     * Edit an existing issue form.
178
     *
179
     * @param Project   $project
180
     * @param Issue     $issue
181
     * @param IssueForm $form
182
     *
183
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
184
     */
185
    public function getEdit(Project $project, Issue $issue, IssueForm $form)
186
    {
187
        // Cannot edit closed issue
188
        if ($issue->status == Issue::STATUS_CLOSED) {
189
            return redirect($issue->to())
190
                ->with('notice', trans('tinyissue.cant_edit_closed_issue'));
191
        }
192
193
        return view('project.issue.edit', [
194
            'issue'   => $issue,
195
            'project' => $project,
196
            'form'    => $form,
197
            'sidebar' => 'project',
198
        ]);
199
    }
200
201
    /**
202
     * To update an existing issue details.
203
     *
204
     * @param Project           $project
205
     * @param Issue             $issue
206
     * @param FormRequest\Issue $request
207
     *
208
     * @return \Illuminate\Http\RedirectResponse
209
     */
210
    public function postEdit(Project $project, Issue $issue, FormRequest\Issue $request)
211
    {
212
        // Delete the issue
213
        if ($request->has('delete-issue')) {
214
            $issue->updater($this->getLoggedUser())->delete();
215
216
            return redirect($project->to())->with('notice', trans('tinyissue.issue_has_been_deleted'));
217
        }
218
219
        $issue->setRelations([
220
            'project'   => $project,
221
            'updatedBy' => $this->getLoggedUser(),
222
        ]);
223
        $issue->updater($this->getLoggedUser())->update($request->all());
224
225
        return redirect($issue->to())
226
            ->with('notice', trans('tinyissue.issue_has_been_updated'));
227
    }
228
229
    /**
230
     * To close or reopen an issue.
231
     *
232
     * @param Project $project
233
     * @param Issue   $issue
234
     * @param int     $status
235
     *
236
     * @return \Illuminate\Http\RedirectResponse
237
     */
238
    public function getClose(Project $project, Issue $issue, $status = 0)
239
    {
240
        if ($status == 0) {
241
            $message = trans('tinyissue.issue_has_been_closed');
242
        } else {
243
            $message = trans('tinyissue.issue_has_been_reopened');
244
        }
245
246
        $issue->setRelation('project', $project);
247
        $issue->updater($this->getLoggedUser())->changeStatus($status, $this->getLoggedUser());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Tinyissue\Repository\RepositoryUpdater as the method changeStatus() does only exist in the following sub-classes of Tinyissue\Repository\RepositoryUpdater: Tinyissue\Repository\Project\Issue\Updater. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
248
249
        return redirect($issue->to())
250
            ->with('notice', $message);
251
    }
252
253
    /**
254
     * To upload an attachment file.
255
     *
256
     * @param Project    $project
257
     * @param Attachment $attachment
258
     * @param Request    $request
259
     *
260
     * @return \Symfony\Component\HttpFoundation\Response
261
     */
262
    public function postUploadAttachment(Project $project, Attachment $attachment, Request $request)
263
    {
264
        try {
265
            $attachment->updater($this->getLoggedUser())->upload($request->all(), $project, $this->getLoggedUser());
0 ignored issues
show
Bug introduced by
The method upload() does not exist on Tinyissue\Repository\RepositoryUpdater. Did you maybe mean getUploadStorage()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
266
267
            $response = [
268
                'upload' => [
269
                    [
270
                        'name'   => $attachment->filename,
271
                        'size'   => $attachment->filesize,
272
                        'fileId' => $attachment->id,
273
                    ],
274
                ],
275
            ];
276
        } catch (\Exception $exception) {
277
            $file = $request->file('upload');
278
279
            $response = [
280
                'status' => false,
281
                'name'   => $file->getClientOriginalName(),
282
                'error'  => $exception->getMessage(),
283
                'trace'  => $exception->getTraceAsString(),
284
            ];
285
        }
286
287
        return response()->json($response);
288
    }
289
290
    /**
291
     * Delete attachment.
292
     *
293
     * @param Project    $project
294
     * @param Issue      $issue
295
     * @param Attachment $attachment
296
     *
297
     * @return \Illuminate\Http\RedirectResponse
298
     */
299
    public function getDeleteAttachment(Project $project, Issue $issue, Attachment $attachment)
300
    {
301
        $issue->setRelation('project', $project);
302
        $attachment->setRelation('issue', $issue);
303
        $attachment->updater($this->getLoggedUser())->delete();
304
305
        return redirect($issue->to())->with('notice', trans('tinyissue.attachment_has_been_deleted'));
306
    }
307
308
    /**
309
     * Display an attachment file such as image.
310
     *
311
     * @param Project    $project
312
     * @param Issue      $issue
313
     * @param Attachment $attachment
314
     * @param Request    $request
315
     *
316
     * @return Response
317
     */
318
    public function getDisplayAttachment(Project $project, Issue $issue, Attachment $attachment, Request $request)
319
    {
320
        $issue->setRelation('project', $project);
321
        $attachment->setRelation('issue', $issue);
322
323
        $path    = config('tinyissue.uploads_dir') . '/' . $issue->project_id . '/' . $attachment->upload_token . '/' . $attachment->filename;
324
        $storage = \Storage::disk('local');
325
        $length  = $storage->size($path);
326
        $time    = $storage->lastModified($path);
327
        $type    = $storage->getDriver()->getMimetype($path);
328
329
        $response = new Response();
330
        $response->setEtag(md5($time . $path));
331
        $response->setExpires(new \DateTime('@' . ($time + 60)));
332
        $response->setLastModified(new \DateTime('@' . $time));
333
        $response->setPublic();
334
        $response->setStatusCode(200);
335
336
        $response->header('Content-Type', $type);
337
        $response->header('Content-Length', $length);
338
        $response->header('Content-Disposition', 'inline; filename="' . $attachment->filename . '"');
339
        $response->header('Cache-Control', 'must-revalidate');
340
341
        if ($response->isNotModified($request)) {
342
            // Return empty response if not modified
343
            return $response;
344
        }
345
346
        // Return file if first request / modified
347
        $response->setContent($storage->get($path));
348
349
        return $response;
350
    }
351
352
    /**
353
     * Download an attachment file.
354
     *
355
     * @param Project    $project
356
     * @param Issue      $issue
357
     * @param Attachment $attachment
358
     *
359
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
360
     */
361
    public function getDownloadAttachment(Project $project, Issue $issue, Attachment $attachment)
362
    {
363
        $issue->setRelation('project', $project);
364
        $attachment->setRelation('issue', $issue);
365
366
        $path = config('filesystems.disks.local.root') . '/' . config('tinyissue.uploads_dir') . '/' . $issue->project_id . '/' . $attachment->upload_token . '/' . $attachment->filename;
367
368
        return response()->download($path, $attachment->filename);
369
    }
370
371
    /**
372
     * Ajax: move an issue to another project.
373
     *
374
     * @param Issue   $issue
375
     * @param Request $request
376
     *
377
     * @return \Symfony\Component\HttpFoundation\Response
378
     */
379
    public function postChangeProject(Issue $issue, Request $request)
380
    {
381
        $issue->updater()->changeProject((int) $request->input('project_id'));
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Tinyissue\Repository\RepositoryUpdater as the method changeProject() does only exist in the following sub-classes of Tinyissue\Repository\RepositoryUpdater: Tinyissue\Repository\Project\Issue\Updater. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
382
383
        return response()->json(['status' => true, 'url' => $issue->to()]);
384
    }
385
386
    /**
387
     * Ajax: change status of an issue.
388
     *
389
     * @param Issue   $issue
390
     * @param Request $request
391
     *
392
     * @return \Symfony\Component\HttpFoundation\Response
393
     */
394
    public function postChangeKanbanTag(Issue $issue, Request $request)
395
    {
396
        $newTag = Tag::find((int) $request->input('newtag'));
397
        $oldTag = Tag::find((int) $request->input('oldtag'));
398
399
        $issue->updater($this->getLoggedUser())->changeKanbanTag($newTag, $oldTag);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Tinyissue\Repository\RepositoryUpdater as the method changeKanbanTag() does only exist in the following sub-classes of Tinyissue\Repository\RepositoryUpdater: Tinyissue\Repository\Project\Issue\Updater. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
400
401
        return response()->json(['status' => true, 'issue' => $issue->id]);
402
    }
403
404
    /**
405
     * Ajax: returns comments for an issue.
406
     *
407
     * @param Project $project
408
     * @param Issue   $issue
409
     *
410
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\Vie
411
     */
412
    public function getIssueComments(Project $project, Issue $issue)
413
    {
414
        $issue->setRelation('project', $project);
415
        $activities = $issue->getCommentActivities();
416
417
        return view('project.issue.partials.activities', [
418
            'no_data'     => trans('tinyissue.no_comments'),
419
            'activities'  => $activities,
420
            'project'     => $project,
421
            'issue'       => $issue,
422
        ]);
423
    }
424
425
    /**
426
     * Ajax: returns activities for an issue excluding comments.
427
     *
428
     * @param Project $project
429
     * @param Issue   $issue
430
     *
431
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\Vie
432
     */
433
    public function getIssueActivity(Project $project, Issue $issue)
434
    {
435
        $issue->setRelation('project', $project);
436
        $activities = $issue->getGeneralActivities();
437
438
        return view('project.issue.partials.activities', [
439
            'no_data'     => trans('tinyissue.no_activities'),
440
            'activities'  => $activities,
441
            'project'     => $project,
442
            'issue'       => $issue,
443
        ]);
444
    }
445
}
446