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

Uploader   F
last analyzed

Complexity

Total Complexity 73

Size/Duplication

Total Lines 352
Duplicated Lines 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 124
c 1
b 1
f 0
dl 0
loc 352
rs 2.56
wmc 73

33 Methods

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

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 = $this->getUploadedFilesFromRequest();
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 getUploadedFilesFromRequest()
213
    {
214
        return 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

214
        return CRUD::/** @scrutinizer ignore-call */ getRequest()->file($this->getNameForRequest());
Loading history...
215
    }
216
217
    public function isFake(): bool
218
    {
219
        return $this->attachedToFakeField !== false;
220
    }
221
222
    public function getFakeAttribute(): bool|string
223
    {
224
        return $this->attachedToFakeField;
225
    }
226
227
    /*******************************
228
     * Setters - fluently configure the uploader
229
     *******************************/
230
    public function multiple(): self
231
    {
232
        $this->handleMultipleFiles = true;
233
234
        return $this;
235
    }
236
237
    public function relationship(bool $isRelationship): self
238
    {
239
        $this->isRelationship = $isRelationship;
240
241
        return $this;
242
    }
243
244
    public function fake(bool|string $isFake): self
245
    {
246
        $this->attachedToFakeField = $isFake;
247
248
        return $this;
249
    }
250
251
    /*******************************
252
     * Default implementation functions
253
     *******************************/
254
    public function uploadFiles(Model $entry, $values = null)
255
    {
256
    }
257
258
    private function retrieveFiles(Model $entry): Model
259
    {
260
        $value = $entry->{$this->getAttributeName()};
261
262
        if ($this->attachedToFakeField) {
263
            $values = $entry->{$this->attachedToFakeField};
264
265
            $values = is_string($values) ? json_decode($values, true) : $values;
266
            $attributeValue = $values[$this->getAttributeName()] ?? null;
267
            $attributeValue = is_array($attributeValue) ? array_map(fn ($value) => $this->getValueWithoutPath($value), $attributeValue) : $this->getValueWithoutPath($attributeValue);
268
            $values[$this->getAttributeName()] = $attributeValue;
269
            $entry->{$this->attachedToFakeField} = isset($entry->getCasts()[$this->attachedToFakeField]) ? $values : json_encode($values);
270
271
            return $entry;
272
        }
273
274
        if ($this->handleMultipleFiles) {
275
            if (! isset($entry->getCasts()[$this->getName()]) && is_string($value)) {
276
                $entry->{$this->getAttributeName()} = json_decode($value, true);
277
            }
278
279
            return $entry;
280
        }
281
282
        $entry->{$this->getAttributeName()} = $this->getValueWithoutPath($value);
283
284
        return $entry;
285
    }
286
287
    protected function deleteFiles(Model $entry)
288
    {
289
        if (! $this->shouldDeleteFiles()) {
290
            return;
291
        }
292
293
        if ($this->attachedToFakeField) {
294
            $values = $entry->{$this->attachedToFakeField};
295
            $values = is_string($values) ? json_decode($values, true) : $values;
296
            $values = $values[$this->getAttributeName()] ?? null;
297
        }
298
299
        $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...
300
301
        if ($values === null) {
302
            return;
303
        }
304
305
        if ($this->handleMultipleFiles) {
306
            // ensure we have an array of values when field is not casted in model.
307
            if (is_string($values)) {
308
                $values = json_decode($values, true);
309
            }
310
            foreach ($values ?? [] as $value) {
311
                $value = Str::start($value, $this->path);
312
                Storage::disk($this->disk)->delete($value);
313
            }
314
315
            return;
316
        }
317
318
        $values = Str::start($values, $this->path);
319
        Storage::disk($this->disk)->delete($values);
320
    }
321
322
    private function performFileDeletion(Model $entry)
323
    {
324
        if (! $this->handleRepeatableFiles && $this->deleteWhenEntryIsDeleted) {
325
            $this->deleteFiles($entry);
326
327
            return;
328
        }
329
330
        $this->deleteRepeatableFiles($entry);
331
    }
332
333
    /*******************************
334
     * helper methods
335
     *******************************/
336
    private function getPathFromConfiguration(array $crudObject, array $configuration): string
337
    {
338
        $this->path = $configuration['path'] ?? $crudObject['prefix'] ?? $this->path;
339
340
        return empty($this->path) ? $this->path : Str::of($this->path)->finish('/')->value();
341
    }
342
343
    private function getOriginalValue(Model $entry, $field = null)
344
    {
345
        $field ??= $this->getAttributeName();
346
347
        if ($this->updatedPreviousFiles !== null) {
348
            return $this->updatedPreviousFiles;
349
        }
350
351
        $previousValue = $entry->getOriginal($field);
352
353
        if (! $previousValue) {
354
            return $previousValue;
355
        }
356
357
        if (
358
            method_exists($entry, 'translationEnabled') &&
359
            $entry->translationEnabled() &&
360
            $entry->isTranslatableAttribute($field)
361
        ) {
362
            return $previousValue[$entry->getLocale()] ?? null;
363
        }
364
365
        return $previousValue;
366
    }
367
}
368