Completed
Push — master ( 91f8d9...64265e )
by Mohamed
09:45 queued 06:57
created

Issue::actions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2
Metric Value
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
cc 2
eloc 3
nc 2
nop 0
crap 2
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\Form;
13
14
use Tinyissue\Model;
15
16
/**
17
 * Issue is a class to defines fields & rules for add/edit issue form.
18
 *
19
 * @author Mohamed Alsharaf <[email protected]>
20
 */
21
class Issue extends FormAbstract
22
{
23
    /**
24
     * An instance of project model.
25
     *
26
     * @var Model\Project
27
     */
28
    protected $project;
29
30
    /**
31
     * Collection of all tags.
32
     *
33
     * @var \Illuminate\Database\Eloquent\Collection
34
     */
35
    protected $tags = null;
36
37
    /**
38
     * @param string $type
39
     *
40
     * @return \Illuminate\Database\Eloquent\Collection|null
41
     */
42 7 View Code Duplication
    protected function getTags($type = null)
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...
43
    {
44 7
        if ($this->tags === null) {
45 7
            $this->tags = (new Model\Tag())->getGroupTags();
46
        }
47
48 7
        if ($type) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $type of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
49 7
            return $this->tags->where('name', $type)->first()->tags;
50
        }
51
52
        return $this->tags;
53
    }
54
55
    /**
56
     * Get issue tag for specific type/group.
57
     *
58
     * @param string $type
59
     *
60
     * @return int
61
     */
62 7
    protected function getIssueTag($type)
63
    {
64 7
        if ($this->isEditing()) {
65 2
            $groupId     = $this->getTags($type)->first()->parent_id;
66 2
            $selectedTag = $this->getModel()->tags->where('parent_id', $groupId);
67
68 2
            if ($selectedTag->count() > 0) {
69
                return $selectedTag->last();
70
            }
71
        }
72
73 7
        return new Model\Tag();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Tinyissue\Model\Tag(); (Tinyissue\Model\Tag) is incompatible with the return type documented by Tinyissue\Form\Issue::getIssueTag of type integer.

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...
74
    }
75
76
    /**
77
     * @param array $params
78
     *
79
     * @return void
80
     */
81 6
    public function setup(array $params)
82
    {
83 6
        $this->project = $params['project'];
84 6
        if (!empty($params['issue'])) {
85 2
            $this->editingModel($params['issue']);
86
        }
87 6
    }
88
89
    /**
90
     * @return array
91
     */
92 6
    public function actions()
93
    {
94
        return [
95 6
            'submit' => $this->isEditing() ? 'update_issue' : 'create_issue',
96
        ];
97
    }
98
99
    /**
100
     * @return array
101
     */
102 6
    public function fields()
103
    {
104 6
        $issueModify = \Auth::user()->permission('issue-modify');
105
106 6
        $fields = $this->fieldTitle();
107 6
        $fields += $this->fieldBody();
108 6
        $fields += $this->fieldTypeTags();
109
110
        // Only on creating new issue
111 6
        if (!$this->isEditing()) {
112 5
            $fields += $this->fieldUpload();
113
        }
114
115
        // Show fields for users with issue modify permission
116 6
        if ($issueModify) {
117 6
            $fields += $this->issueModifyFields();
118
        }
119
120 6
        return $fields;
121
    }
122
123
    /**
124
     * Return a list of fields for users with issue modify permission.
125
     *
126
     * @return array
127
     */
128 6
    protected function issueModifyFields()
129
    {
130 6
        $fields = [];
131
132 6
        $fields['internal_status'] = [
133
            'type' => 'legend',
134
        ];
135
136
        // Status tags
137 6
        $fields += $this->fieldStatusTags();
138
139
        // Assign users
140 6
        $fields += $this->fieldAssignedTo();
141
142
        // Quotes
143 6
        $fields += $this->fieldTimeQuote();
144
145
        // Resolution tags
146 6
        $fields += $this->fieldResolutionTags();
147
148 6
        return $fields;
149
    }
150
151
    /**
152
     * Returns title field.
153
     *
154
     * @return array
155
     */
156 7
    protected function fieldTitle()
157
    {
158
        return [
159
            'title' => [
160
                'type'  => 'text',
161
                'label' => 'title',
162 7
            ],
163
        ];
164
    }
165
166
    /**
167
     * Returns body field.
168
     *
169
     * @return array
170
     */
171 7
    protected function fieldBody()
172
    {
173
        return [
174
            'body' => [
175
                'type'  => 'textarea',
176
                'label' => 'issue',
177 7
            ],
178
        ];
179
    }
180
181
    /**
182
     * Returns status tag field.
183
     *
184
     * @return array
185
     */
186 6 View Code Duplication
    protected function fieldStatusTags()
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 6
        $currentTag = $this->getIssueTag('status');
189
190 6
        if ($currentTag && !$currentTag->canView()) {
0 ignored issues
show
Bug introduced by
The method canView cannot be called on $currentTag (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
191
            $tags = [$currentTag];
192
        } else {
193 6
            $tags = $this->getTags('status');
194
        }
195
196 6
        $options = [];
197 6
        foreach ($tags as $tag) {
0 ignored issues
show
Bug introduced by
The expression $tags of type object<Illuminate\Databa...nteger,{"0":"integer"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
198 6
            $options[ucwords($tag->name)] = [
199 6
                'name'      => 'tag_status',
200 6
                'value'     => $tag->id,
201 6
                'data-tags' => $tag->id,
202 6
                'color'     => $tag->bgcolor,
203
            ];
204
        }
205
206 6
        $fields['tag_status'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
207 6
            'label'  => 'status',
208 6
            'type'   => 'radioButton',
209 6
            'radios' => $options,
210 6
            'check'  => $this->getIssueTag('status')->id,
211
        ];
212
213 6
        return $fields;
214
    }
215
216
    /**
217
     * Returns tags field.
218
     *
219
     * @return array
220
     */
221 7 View Code Duplication
    protected function fieldTypeTags()
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...
222
    {
223 7
        $currentTag = $this->getIssueTag('type');
224
225 7
        if ($currentTag && !$currentTag->canView()) {
0 ignored issues
show
Bug introduced by
The method canView cannot be called on $currentTag (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
226
            $tags = [$currentTag];
227
        } else {
228 7
            $tags = $this->getTags('type');
229
        }
230
231 7
        $options = [];
232 7
        foreach ($tags as $tag) {
0 ignored issues
show
Bug introduced by
The expression $tags of type object<Illuminate\Databa...nteger,{"0":"integer"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
233 7
            $options[ucwords($tag->name)] = [
234 7
                'name'      => 'tag_type',
235 7
                'value'     => $tag->id,
236 7
                'data-tags' => $tag->id,
237 7
                'color'     => $tag->bgcolor,
238
            ];
239
        }
240
241 7
        $fields['tag_type'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
242 7
            'label'  => 'type',
243 7
            'type'   => 'radioButton',
244 7
            'radios' => $options,
245 7
            'check'  => $this->getIssueTag('type')->id,
246
        ];
247
248 7
        return $fields;
249
    }
250
251
    /**
252
     * Returns tags field.
253
     *
254
     * @return array
255
     */
256 6
    protected function fieldResolutionTags()
257
    {
258 6
        $currentTag = $this->getIssueTag('resolution');
259
260 6
        if ($currentTag && !$currentTag->canView()) {
0 ignored issues
show
Bug introduced by
The method canView cannot be called on $currentTag (of type integer).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
261
            $tags = [$currentTag];
262
        } else {
263 6
            $tags = $this->getTags('resolution');
264
        }
265
266
        $options = [
267 6
            trans('tinyissue.none') => [
268
                'name'      => 'tag_resolution',
269
                'value'     => 0,
270
                'data-tags' => 0,
271
                'color'     => '#62CFFC',
272 6
            ],
273
        ];
274 6
        foreach ($tags as $tag) {
0 ignored issues
show
Bug introduced by
The expression $tags of type object<Illuminate\Databa...nteger,{"0":"integer"}> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
275 6
            $options[ucwords($tag->name)] = [
276 6
                'name'      => 'tag_resolution',
277 6
                'value'     => $tag->id,
278 6
                'data-tags' => $tag->id,
279 6
                'color'     => $tag->bgcolor,
280
            ];
281
        }
282
283 6
        $fields['tag_resolution'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$fields was never initialized. Although not strictly required by PHP, it is generally a good practice to add $fields = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
284 6
            'label'  => 'resolution',
285 6
            'type'   => 'radioButton',
286 6
            'radios' => $options,
287 6
            'check'  => $this->getIssueTag('resolution')->id,
288
        ];
289
290 6
        return $fields;
291
    }
292
293
    /**
294
     * Returns assigned to field.
295
     *
296
     * @return array
297
     */
298 6
    protected function fieldAssignedTo()
299
    {
300
        return [
301
            'assigned_to' => [
302 6
                'type'    => 'select',
303 6
                'label'   => 'assigned_to',
304 6
                'options' => [0 => ''] + $this->project->usersCanFixIssue()->get()->lists('fullname', 'id')->all(),
305 6
                'value'   => (int) $this->project->default_assignee,
306
            ],
307
        ];
308
    }
309
310
    /**
311
     * Returns upload field.
312
     *
313
     * @return array
314
     */
315 6
    protected function fieldUpload()
316
    {
317 6
        $user                      = \Auth::guest() ? new Model\User() : \Auth::user();
318 6
        $fields                    = $this->projectUploadFields('upload', $this->project, $user);
319 6
        $fields['upload']['label'] = 'attachments';
320
321 6
        return $fields;
322
    }
323
324
    /**
325
     * Returns time quote field.
326
     *
327
     * @return array
328
     */
329 6
    protected function fieldTimeQuote()
330
    {
331
        return [
332
            'time_quote' => [
333 6
                'type'   => 'groupField',
334 6
                'label'  => 'quote',
335
                'fields' => [
336
                    'h' => [
337 6
                        'type'          => 'number',
338 6
                        'append'        => trans('tinyissue.hours'),
339 6
                        'value'         => $this->extractQuoteValue('h'),
340 6
                        'addGroupClass' => 'col-sm-12 col-md-12 col-lg-4',
341
                    ],
342
                    'm' => [
343 6
                        'type'          => 'number',
344 6
                        'append'        => trans('tinyissue.minutes'),
345 6
                        'value'         => $this->extractQuoteValue('m'),
346 6
                        'addGroupClass' => 'col-sm-12 col-md-12 col-lg-4',
347
                    ],
348
                ],
349 6
                'addClass' => 'row issue-quote',
350
            ],
351
        ];
352
    }
353
354
    /**
355
     * @return array
356
     */
357 7
    public function rules()
358
    {
359
        $rules = [
360 7
            'title' => 'required|max:200',
361
            'body'  => 'required',
362
        ];
363
364 7
        return $rules;
365
    }
366
367
    /**
368
     * @return string
369
     */
370
    public function getRedirectUrl()
371
    {
372
        if ($this->isEditing()) {
373
            return $this->getModel()->to('edit');
374
        }
375
376
        return 'project/' . $this->project->id . '/issue/new';
377
    }
378
379
    /**
380
     * Extract number of hours, or minutes, or seconds from a quote.
381
     *
382
     * @param string $part
383
     *
384
     * @return float|int
385
     */
386 6
    protected function extractQuoteValue($part)
387
    {
388 6
        if ($this->getModel() instanceof Model\Project\Issue) {
389 2
            $seconds = $this->getModel()->time_quote;
390 2
            if ($part === 'h') {
391 2
                return floor($seconds / 3600);
392
            }
393
394 2
            if ($part === 'm') {
395 2
                return ($seconds / 60) % 60;
396
            }
397
        }
398
399 5
        return 0;
400
    }
401
}
402