Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Test Setup Failed
Pull Request — main (#5725)
by Pedro
25:49 queued 10:59
created

Uploader   F

Complexity

Total Complexity 72

Size/Duplication

Total Lines 347
Duplicated Lines 0 %

Importance

Changes 9
Bugs 5 Features 0
Metric Value
eloc 123
c 9
b 5
f 0
dl 0
loc 347
rs 2.64
wmc 72

32 Methods

Rating   Name   Duplication   Size   Complexity  
A getExpirationTimeInMinutes() 0 3 1
A for() 0 3 1
A getName() 0 3 1
A getDisk() 0 3 1
A getPath() 0 3 1
A useTemporaryUrl() 0 3 1
A uploadFiles() 0 2 1
A performFileDeletion() 0 9 3
A getAttributeName() 0 3 1
A isFake() 0 3 1
A retrieveUploadedFiles() 0 7 2
A fake() 0 5 1
A getFakeAttribute() 0 3 1
A shouldKeepPreviousValueUnchanged() 0 3 3
A getValueWithoutPath() 0 3 2
A shouldDeleteFiles() 0 3 1
A getPathFromConfiguration() 0 5 2
A getPreviousFiles() 0 9 3
B deleteFiles() 0 33 8
A multiple() 0 5 1
A __construct() 0 10 4
A isRelationship() 0 3 1
A getOriginalValue() 0 23 6
A shouldUploadFiles() 0 3 1
A relationship() 0 5 1
A canHandleMultipleFiles() 0 3 1
A getIdentifier() 0 7 2
A hasDeletedFiles() 0 3 3
B retrieveFiles() 0 27 8
A deleteUploadedFiles() 0 10 3
A getNameForRequest() 0 3 1
A storeUploadedFiles() 0 21 5

How to fix   Complexity   

Complex Class

Complex classes like Uploader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Uploader, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Backpack\CRUD\app\Library\Uploaders;
4
5
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
6
use Backpack\CRUD\app\Library\Uploaders\Support\Interfaces\UploaderInterface;
7
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleFileNaming;
8
use Backpack\CRUD\app\Library\Uploaders\Support\Traits\HandleRepeatableUploads;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Database\Eloquent\SoftDeletes;
11
use Illuminate\Support\Facades\Storage;
12
use Illuminate\Support\Str;
13
14
abstract class Uploader implements UploaderInterface
15
{
16
    use HandleFileNaming;
17
    use HandleRepeatableUploads;
0 ignored issues
show
introduced by
The trait Backpack\CRUD\app\Librar...HandleRepeatableUploads requires some properties which are not provided by Backpack\CRUD\app\Library\Uploaders\Uploader: $model, $pivot, $pivotParent
Loading history...
18
19
    private string $name;
20
21
    private string $disk = 'public';
22
23
    private string $path = '';
24
25
    public bool $handleMultipleFiles = false;
26
27
    private bool $deleteWhenEntryIsDeleted = true;
28
29
    private bool|string $attachedToFakeField = false;
30
31
    /**
32
     * Cloud disks have the ability to generate temporary URLs to files, should we do it?
33
     */
34
    private bool $useTemporaryUrl = false;
35
36
    /**
37
     * When using temporary urls, define the time that the url will be valid.
38
     */
39
    private int $temporaryUrlExpirationTimeInMinutes = 1;
40
41
    /**
42
     * Indicates if the upload is relative to a relationship field/column.
43
     */
44
    private bool $isRelationship = false;
45
46
    /**
47
     * When previous files are updated, we need to keep track of them so that we don't add deleted files to the new list.
48
     */
49
    private $updatedPreviousFiles = null;
50
51
    public function __construct(array $crudObject, array $configuration)
52
    {
53
        $this->name = $crudObject['name'];
54
        $this->disk = $configuration['disk'] ?? $crudObject['disk'] ?? $this->disk;
55
        $this->path = $this->getPathFromConfiguration($crudObject, $configuration);
56
        $this->attachedToFakeField = isset($crudObject['fake']) && $crudObject['fake'] ? ($crudObject['store_in'] ?? 'extras') : ($crudObject['store_in'] ?? false);
57
        $this->useTemporaryUrl = $configuration['temporaryUrl'] ?? $this->useTemporaryUrl;
58
        $this->temporaryUrlExpirationTimeInMinutes = $configuration['temporaryUrlExpirationTime'] ?? $this->temporaryUrlExpirationTimeInMinutes;
59
        $this->deleteWhenEntryIsDeleted = $configuration['deleteWhenEntryIsDeleted'] ?? $this->deleteWhenEntryIsDeleted;
60
        $this->fileNamer = is_callable($configuration['fileNamer'] ?? null) ? $configuration['fileNamer'] : $this->getFileNameGeneratorInstance($configuration['fileNamer'] ?? null);
61
    }
62
63
    /*******************************
64
     * Static methods
65
     *******************************/
66
    public static function for(array $crudObject, array $definition): UploaderInterface
67
    {
68
        return new static($crudObject, $definition);
69
    }
70
71
    /*******************************
72
     * public methods - event handler methods
73
     *******************************/
74
    public function storeUploadedFiles(Model $entry): Model
75
    {
76
        if ($this->handleRepeatableFiles) {
77
            return $this->handleRepeatableFiles($entry);
78
        }
79
80
        $values = CRUD::getRequest()->file($this->getNameForRequest());
0 ignored issues
show
Bug introduced by
The method getRequest() does not exist on Backpack\CRUD\app\Librar...udPanel\CrudPanelFacade. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

80
        $values = CRUD::/** @scrutinizer ignore-call */ getRequest()->file($this->getNameForRequest());
Loading history...
81
82
        if ($this->attachedToFakeField) {
83
            $fakeFieldValue = $entry->{$this->attachedToFakeField};
84
            $fakeFieldValue = is_string($fakeFieldValue) ? json_decode($fakeFieldValue, true) : (array) $fakeFieldValue;
85
            $fakeFieldValue[$this->getAttributeName()] = $this->uploadFiles($entry, $values);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $fakeFieldValue[$this->getAttributeName()] is correct as $this->uploadFiles($entry, $values) targeting Backpack\CRUD\app\Librar...Uploader::uploadFiles() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
86
87
            $entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $fakeFieldValue : json_encode($fakeFieldValue);
88
89
            return $entry;
90
        }
91
92
        $entry->{$this->getAttributeName()} = $this->uploadFiles($entry, $values);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $entry->$this->getAttributeName() is correct as $this->uploadFiles($entry, $values) targeting Backpack\CRUD\app\Librar...Uploader::uploadFiles() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93
94
        return $entry;
95
    }
96
97
    public function retrieveUploadedFiles(Model $entry): Model
98
    {
99
        if ($this->handleRepeatableFiles) {
100
            return $this->retrieveRepeatableFiles($entry);
101
        }
102
103
        return $this->retrieveFiles($entry);
104
    }
105
106
    public function deleteUploadedFiles(Model $entry): void
107
    {
108
        if (! in_array(SoftDeletes::class, class_uses_recursive($entry), true)) {
109
            $this->performFileDeletion($entry);
110
111
            return;
112
        }
113
114
        if ($entry->isForceDeleting() === true) {
115
            $this->performFileDeletion($entry);
116
        }
117
    }
118
119
    /*******************************
120
     * Getters
121
     *******************************/
122
    public function getName(): string
123
    {
124
        return $this->name;
125
    }
126
127
    public function getAttributeName(): string
128
    {
129
        return Str::afterLast($this->name, '.');
130
    }
131
132
    public function getDisk(): string
133
    {
134
        return $this->disk;
135
    }
136
137
    public function getPath(): string
138
    {
139
        return $this->path;
140
    }
141
142
    public function useTemporaryUrl(): bool
143
    {
144
        return $this->useTemporaryUrl;
145
    }
146
147
    public function getExpirationTimeInMinutes(): int
148
    {
149
        return $this->temporaryUrlExpirationTimeInMinutes;
150
    }
151
152
    public function shouldDeleteFiles(): bool
153
    {
154
        return $this->deleteWhenEntryIsDeleted;
155
    }
156
157
    public function shouldUploadFiles($entryValue): bool
158
    {
159
        return true;
160
    }
161
162
    public function shouldKeepPreviousValueUnchanged(Model $entry, $entryValue): bool
163
    {
164
        return $entry->exists && ($entryValue === null || $entryValue === [null]);
165
    }
166
167
    public function hasDeletedFiles($entryValue): bool
168
    {
169
        return $entryValue === false || $entryValue === null || $entryValue === [null];
170
    }
171
172
    public function getIdentifier(): string
173
    {
174
        if ($this->handleRepeatableFiles) {
175
            return $this->repeatableContainerName.'_'.$this->name;
176
        }
177
178
        return $this->name;
179
    }
180
181
    public function getNameForRequest(): string
182
    {
183
        return $this->repeatableContainerName ?? $this->name;
184
    }
185
186
    public function canHandleMultipleFiles(): bool
187
    {
188
        return $this->handleMultipleFiles;
189
    }
190
191
    public function isRelationship(): bool
192
    {
193
        return $this->isRelationship;
194
    }
195
196
    public function getPreviousFiles(Model $entry): mixed
197
    {
198
        if (! $this->attachedToFakeField) {
199
            return $this->getOriginalValue($entry);
200
        }
201
        $value = $this->getOriginalValue($entry, $this->attachedToFakeField);
202
        $value = is_string($value) ? json_decode($value, true) : (array) $value;
203
204
        return $value[$this->getAttributeName()] ?? null;
205
    }
206
207
    public function getValueWithoutPath(?string $value = null): ?string
208
    {
209
        return $value ? Str::after($value, $this->path) : null;
210
    }
211
212
    public function isFake(): bool
213
    {
214
        return $this->attachedToFakeField !== false;
215
    }
216
217
    public function getFakeAttribute(): bool|string
218
    {
219
        return $this->attachedToFakeField;
220
    }
221
222
    /*******************************
223
     * Setters - fluently configure the uploader
224
     *******************************/
225
    public function multiple(): self
226
    {
227
        $this->handleMultipleFiles = true;
228
229
        return $this;
230
    }
231
232
    public function relationship(bool $isRelationship): self
233
    {
234
        $this->isRelationship = $isRelationship;
235
236
        return $this;
237
    }
238
239
    public function fake(bool|string $isFake): self
240
    {
241
        $this->attachedToFakeField = $isFake;
242
243
        return $this;
244
    }
245
246
    /*******************************
247
     * Default implementation functions
248
     *******************************/
249
    public function uploadFiles(Model $entry, $values = null)
250
    {
251
    }
252
253
    private function retrieveFiles(Model $entry): Model
254
    {
255
        $value = $entry->{$this->getAttributeName()};
256
257
        if ($this->attachedToFakeField) {
258
            $values = $entry->{$this->attachedToFakeField};
259
260
            $values = is_string($values) ? json_decode($values, true) : $values;
261
            $attributeValue = $values[$this->getAttributeName()] ?? null;
262
            $attributeValue = is_array($attributeValue) ? array_map(fn ($value) => $this->getValueWithoutPath($value), $attributeValue) : $this->getValueWithoutPath($attributeValue);
263
            $values[$this->getAttributeName()] = $attributeValue;
264
            $entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $values : json_encode($values);
265
266
            return $entry;
267
        }
268
269
        if ($this->handleMultipleFiles) {
270
            if (! isset($entry->getCasts()[$this->getName()]) && is_string($value)) {
271
                $entry->{$this->getAttributeName()} = json_decode($value, true);
272
            }
273
274
            return $entry;
275
        }
276
277
        $entry->{$this->getAttributeName()} = $this->getValueWithoutPath($value);
278
279
        return $entry;
280
    }
281
282
    protected function deleteFiles(Model $entry)
283
    {
284
        if (! $this->shouldDeleteFiles()) {
285
            return;
286
        }
287
288
        if ($this->attachedToFakeField) {
289
            $values = $entry->{$this->attachedToFakeField};
290
            $values = is_string($values) ? json_decode($values, true) : $values;
291
            $values = $values[$this->getAttributeName()] ?? null;
292
        }
293
294
        $values ??= $entry->{$this->getAttributeName()};
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $values does not seem to be defined for all execution paths leading up to this point.
Loading history...
295
296
        if ($values === null) {
297
            return;
298
        }
299
300
        if ($this->handleMultipleFiles) {
301
            // ensure we have an array of values when field is not casted in model.
302
            if (is_string($values)) {
303
                $values = json_decode($values, true);
304
            }
305
            foreach ($values ?? [] as $value) {
306
                $value = Str::start($value, $this->path);
307
                Storage::disk($this->disk)->delete($value);
308
            }
309
310
            return;
311
        }
312
313
        $values = Str::start($values, $this->path);
314
        Storage::disk($this->disk)->delete($values);
315
    }
316
317
    private function performFileDeletion(Model $entry)
318
    {
319
        if (! $this->handleRepeatableFiles && $this->deleteWhenEntryIsDeleted) {
320
            $this->deleteFiles($entry);
321
322
            return;
323
        }
324
325
        $this->deleteRepeatableFiles($entry);
326
    }
327
328
    /*******************************
329
     * helper methods
330
     *******************************/
331
    private function getPathFromConfiguration(array $crudObject, array $configuration): string
332
    {
333
        $this->path = $configuration['path'] ?? $crudObject['prefix'] ?? $this->path;
334
335
        return empty($this->path) ? $this->path : Str::of($this->path)->finish('/')->value();
336
    }
337
338
    private function getOriginalValue(Model $entry, $field = null)
339
    {
340
        $field ??= $this->getAttributeName();
341
342
        if ($this->updatedPreviousFiles !== null) {
343
            return $this->updatedPreviousFiles;
344
        }
345
346
        $previousValue = $entry->getOriginal($field);
347
348
        if (! $previousValue) {
349
            return $previousValue;
350
        }
351
352
        if (
353
            method_exists($entry, 'translationEnabled') &&
354
            $entry->translationEnabled() &&
355
            $entry->isTranslatableAttribute($field)
356
        ) {
357
            return $previousValue[$entry->getLocale()] ?? null;
358
        }
359
360
        return $previousValue;
361
    }
362
}
363