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

Passed
Push — v6-livewire-widget ( bb8951...53543e )
by Pedro
14:58
created

HandleRepeatableUploads::uploadRelationshipFiles()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 4
eloc 6
c 1
b 1
f 0
nc 3
nop 2
dl 0
loc 12
rs 10
1
<?php
2
3
namespace Backpack\CRUD\app\Library\Uploaders\Support\Traits;
4
5
use Backpack\CRUD\app\Library\CrudPanel\CrudPanelFacade as CRUD;
6
use Backpack\CRUD\app\Library\Uploaders\Support\Interfaces\UploaderInterface;
7
use Illuminate\Database\Eloquent\Model;
8
use Illuminate\Support\Collection;
9
use Illuminate\Support\Facades\Storage;
10
use Illuminate\Support\Str;
11
12
trait HandleRepeatableUploads
13
{
14
    public bool $handleRepeatableFiles = false;
15
16
    public ?string $repeatableContainerName = null;
17
18
    /*******************************
19
     * Setters - fluently configure the uploader
20
     *******************************/
21
    public function repeats(string $repeatableContainerName): self
22
    {
23
        $this->handleRepeatableFiles = true;
24
25
        $this->repeatableContainerName = $repeatableContainerName;
26
27
        return $this;
28
    }
29
30
    /*******************************
31
     * Getters
32
     *******************************/
33
    public function getRepeatableContainerName(): ?string
34
    {
35
        return $this->repeatableContainerName;
36
    }
37
38
    /*******************************
39
     * Default implementation methods
40
     *******************************/
41
    protected function uploadRepeatableFiles($values, $previousValues, $entry = null)
42
    {
43
    }
44
45
    protected function handleRepeatableFiles(Model $entry): Model
46
    {
47
        $values = collect(CRUD::getRequest()->get($this->getRepeatableContainerName()));
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

47
        $values = collect(CRUD::/** @scrutinizer ignore-call */ getRequest()->get($this->getRepeatableContainerName()));
Loading history...
48
        $files = collect(CRUD::getRequest()->file($this->getRepeatableContainerName()));
49
        $value = $this->mergeValuesRecursive($values, $files);
50
51
        if ($this->isRelationship) {
52
            return $this->uploadRelationshipFiles($entry, $value);
53
        }
54
55
        $entry->{$this->getRepeatableContainerName()} = json_encode($this->processRepeatableUploads($entry, $value));
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type array; however, parameter $values of Backpack\CRUD\app\Librar...cessRepeatableUploads() does only seem to accept Illuminate\Support\Collection, maybe add an additional type check? ( Ignorable by Annotation )

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

55
        $entry->{$this->getRepeatableContainerName()} = json_encode($this->processRepeatableUploads($entry, /** @scrutinizer ignore-type */ $value));
Loading history...
56
57
        return $entry;
58
    }
59
60
    private function uploadRelationshipFiles(Model $entry, mixed $value): Model
61
    {
62
        $modelCount = CRUD::get('uploaded_'.$this->getRepeatableContainerName().'_count');
0 ignored issues
show
Bug introduced by
The method get() 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

62
        /** @scrutinizer ignore-call */ 
63
        $modelCount = CRUD::get('uploaded_'.$this->getRepeatableContainerName().'_count');
Loading history...
63
        $value = $value->slice($modelCount, 1)->toArray();
64
65
        foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
66
            if (array_key_exists($modelCount, $value) && array_key_exists($uploader->getAttributeName(), $value[$modelCount])) {
67
                $entry->{$uploader->getAttributeName()} = $uploader->uploadFiles($entry, $value[$modelCount][$uploader->getAttributeName()]);
68
            }
69
        }
70
71
        return $entry;
72
    }
73
74
    protected function processRepeatableUploads(Model $entry, Collection $values): Collection
75
    {
76
        foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $uploader) {
77
            $uploadedValues = $uploader->uploadRepeatableFiles($values->pluck($uploader->getAttributeName())->toArray(), $this->getPreviousRepeatableValues($entry, $uploader));
78
79
            $values = $values->map(function ($item, $key) use ($uploadedValues, $uploader) {
80
                $item[$uploader->getAttributeName()] = $uploadedValues[$key] ?? null;
81
82
                return $item;
83
            });
84
        }
85
86
        return $values;
87
    }
88
89
    private function retrieveRepeatableFiles(Model $entry): Model
90
    {
91
        if ($this->isRelationship) {
92
            return $this->retrieveRepeatableRelationFiles($entry);
93
        }
94
95
        $repeatableUploaders = app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName());
96
97
        $values = $entry->{$this->getRepeatableContainerName()};
98
        $values = is_string($values) ? json_decode($values, true) : $values;
99
        $values = array_map(function ($item) use ($repeatableUploaders) {
100
            foreach ($repeatableUploaders as $upload) {
101
                $item[$upload->getAttributeName()] = $this->getValuesWithPathStripped($item, $upload);
102
            }
103
104
            return $item;
105
        }, $values);
106
107
        $entry->{$this->getRepeatableContainerName()} = $values;
108
109
        return $entry;
110
    }
111
112
    private function retrieveRepeatableRelationFiles(Model $entry)
113
    {
114
        switch($this->getRepeatableRelationType()) {
115
            case 'BelongsToMany':
116
            case 'MorphToMany':
117
                $pivotClass = app('crud')->getModel()->{$this->getUploaderSubfield()['baseEntity']}()->getPivotClass();
118
                $pivotFieldName = 'pivot_'.$this->getAttributeName();
0 ignored issues
show
Bug introduced by
It seems like getAttributeName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

118
                $pivotFieldName = 'pivot_'.$this->/** @scrutinizer ignore-call */ getAttributeName();
Loading history...
119
                $connectedEntry = new $pivotClass([$this->getAttributeName() => $entry->$pivotFieldName]);
120
                $entry->{$pivotFieldName} = $this->retrieveFiles($connectedEntry)->{$this->getAttributeName()};
0 ignored issues
show
Bug introduced by
It seems like retrieveFiles() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

120
                $entry->{$pivotFieldName} = $this->/** @scrutinizer ignore-call */ retrieveFiles($connectedEntry)->{$this->getAttributeName()};
Loading history...
121
122
                break;
123
            default:
124
                $entry = $this->retrieveFiles($entry);
125
        }
126
127
        return $entry;
128
    }
129
130
    private function getRepeatableRelationType()
131
    {
132
        return $this->getUploaderField()->getAttributes()['relation_type'];
133
    }
134
135
    private function getUploaderField()
136
    {
137
        return app('crud')->field($this->getRepeatableContainerName());
138
    }
139
140
    private function getUploaderSubfield()
141
    {
142
        return collect($this->getUploaderFieldSubfields())->where('name', '===', $this->getName())->first();
0 ignored issues
show
Bug introduced by
It seems like getName() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

142
        return collect($this->getUploaderFieldSubfields())->where('name', '===', $this->/** @scrutinizer ignore-call */ getName())->first();
Loading history...
143
    }
144
145
    private function getUploaderFieldSubfields()
146
    {
147
        return $this->getUploaderField()->getAttributes()['subfields'];
148
    }
149
150
    private function deleteRepeatableFiles(Model $entry): void
151
    {
152
        if ($this->isRelationship) {
153
            $this->deleteRelationshipFiles($entry);
154
155
            return;
156
        }
157
158
        $repeatableValues = collect($entry->{$this->getName()});
159
        foreach (app('UploadersRepository')->getRepeatableUploadersFor($this->getRepeatableContainerName()) as $upload) {
160
            if (! $upload->shouldDeleteFiles()) {
161
                continue;
162
            }
163
            $values = $repeatableValues->pluck($upload->getName())->toArray();
164
            foreach ($values as $value) {
165
                if (! $value) {
166
                    continue;
167
                }
168
169
                if (is_array($value)) {
170
                    foreach ($value as $subvalue) {
171
                        Storage::disk($upload->getDisk())->delete($upload->getPath().$subvalue);
172
                    }
173
174
                    continue;
175
                }
176
177
                Storage::disk($upload->getDisk())->delete($upload->getPath().$value);
178
            }
179
        }
180
    }
181
    /*******************************
182
     * Helper methods
183
     *******************************/
184
185
    /**
186
     * Given two multidimensional arrays/collections, merge them recursively.
187
     */
188
    protected function mergeValuesRecursive(array|Collection $array1, array|Collection $array2): array|Collection
189
    {
190
        $merged = $array1;
191
        foreach ($array2 as $key => &$value) {
192
            if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
193
                $merged[$key] = $this->mergeValuesRecursive($merged[$key], $value);
194
            } else {
195
                $merged[$key] = $value;
196
            }
197
        }
198
199
        return $merged;
200
    }
201
202
    /**
203
     * Repeatable items send `_order_` parameter in the request.
204
     * This holds the order of the items in the repeatable container.
205
     */
206
    protected function getFileOrderFromRequest(): array
207
    {
208
        $items = CRUD::getRequest()->input('_order_'.$this->getRepeatableContainerName()) ?? [];
209
210
        array_walk($items, function (&$key, $value) {
211
            $requestValue = $key[$this->getName()] ?? null;
212
            $key = $this->handleMultipleFiles ? (is_string($requestValue) ? explode(',', $requestValue) : $requestValue) : $requestValue;
213
        });
214
215
        return $items;
216
    }
217
218
    private function getPreviousRepeatableValues(Model $entry, UploaderInterface $uploader): array
219
    {
220
        $previousValues = json_decode($entry->getOriginal($uploader->getRepeatableContainerName()), true);
0 ignored issues
show
Bug introduced by
It seems like $entry->getOriginal($upl...eatableContainerName()) can also be of type array; however, parameter $json of json_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

220
        $previousValues = json_decode(/** @scrutinizer ignore-type */ $entry->getOriginal($uploader->getRepeatableContainerName()), true);
Loading history...
221
222
        if (! empty($previousValues)) {
223
            $previousValues = array_column($previousValues, $uploader->getName());
224
        }
225
226
        return $previousValues ?? [];
227
    }
228
229
    private function getValuesWithPathStripped(array|string|null $item, UploaderInterface $upload)
230
    {
231
        $uploadedValues = $item[$upload->getName()] ?? null;
232
        if (is_array($uploadedValues)) {
233
            return array_map(function ($value) use ($upload) {
234
                return Str::after($value, $upload->getPath());
235
            }, $uploadedValues);
236
        }
237
238
        return isset($uploadedValues) ? Str::after($uploadedValues, $upload->getPath()) : null;
239
    }
240
241
    private function deleteRelationshipFiles(Model $entry): void
242
    {
243
        if (in_array($this->getRepeatableRelationType(), ['BelongsToMany', 'MorphToMany'])) {
244
            $pivotAttributes = $entry->getAttributes();
245
            $connectedPivot = $entry->pivotParent->{$this->getRepeatableContainerName()}->where(function ($item) use ($pivotAttributes) {
246
                $itemPivotAttributes = $item->pivot->only(array_keys($pivotAttributes));
247
248
                return $itemPivotAttributes === $pivotAttributes;
249
            })->first();
250
251
            if (! $connectedPivot) {
252
                return;
253
            }
254
255
            $files = $connectedPivot->getOriginal()['pivot_'.$this->getAttributeName()];
256
257
            if (! $files) {
258
                return;
259
            }
260
261
            if (is_array($files)) {
262
                foreach ($files as $value) {
263
                    $value = Str::start($value, $this->getPath());
0 ignored issues
show
Bug introduced by
It seems like getPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

263
                    $value = Str::start($value, $this->/** @scrutinizer ignore-call */ getPath());
Loading history...
264
                    Storage::disk($this->getDisk())->delete($value);
0 ignored issues
show
Bug introduced by
It seems like getDisk() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

264
                    Storage::disk($this->/** @scrutinizer ignore-call */ getDisk())->delete($value);
Loading history...
265
                }
266
267
                return;
268
            }
269
270
            $value = Str::start($files, $this->getPath());
271
            Storage::disk($this->getDisk())->delete($value);
272
273
            return;
274
        }
275
276
        $this->deleteFiles($entry);
0 ignored issues
show
Bug introduced by
It seems like deleteFiles() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

276
        $this->/** @scrutinizer ignore-call */ 
277
               deleteFiles($entry);
Loading history...
277
    }
278
}
279