Passed
Push — master ( 50b84c...3f7c6c )
by Ben
05:31
created

UploadMedia::handleFiles()   B

Complexity

Conditions 10
Paths 11

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 10

Importance

Changes 1
Bugs 1 Features 0
Metric Value
eloc 12
c 1
b 1
f 0
dl 0
loc 19
ccs 9
cts 9
cp 1
rs 7.6666
cc 10
nc 11
nop 6
crap 10

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Thinktomorrow\Chief\Media;
4
5
use Illuminate\Http\UploadedFile;
6
use Spatie\MediaLibrary\HasMedia\HasMedia;
7
use Thinktomorrow\AssetLibrary\Models\Asset;
8
use Thinktomorrow\AssetLibrary\Models\AssetUploader;
9
10
class UploadMedia
11
{
12
    /**
13
     * Upload from base64encoded files, usually
14
     * coming from slim upload component
15
     *
16 50
     * @param HasMedia $model
17
     * @param array $files_by_type
18
     * @param array $files_order_by_type
19 50
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
20 41
     */
21 1
    public function fromUploadComponent(HasMedia $model, array $files_by_type, array $files_order_by_type)
22
    {
23
        $files_by_type = $this->sanitizeFilesParameter($files_by_type);
24 41
        $files_order_by_type = $this->sanitizeFilesOrderParameter($files_order_by_type);
25
        $this->validateParameters($files_by_type, $files_order_by_type);
26
27
        // When no files are uploaded, we still would like to sort our assets duh
28 9
        if (empty($files_by_type)) {
29 9
            foreach ($files_order_by_type as $type => $fileIdsCollection) {
30 9
                $this->sortFiles($model, $type, $fileIdsCollection);
31
            }
32 9
33 9
            return;
34 9
        }
35 9
36
        foreach ($files_by_type as $type => $files) {
37 9
            foreach ($files as $locale => $files) {
0 ignored issues
show
Comprehensibility Bug introduced by
$files is overwriting a variable from outer foreach loop.
Loading history...
38
                $this->validateFileUploads($files);
39 9
40
                $fileIdsCollection = $files_order_by_type[$type] ?? [];
41 9
42
                $this->addFiles($model, $type, $files, $fileIdsCollection, $locale);
43 9
                $this->replaceFiles($model, $files);
44 7
                $this->removeFiles($model, $files);
45
46 7
                $this->sortFiles($model, $type, $fileIdsCollection);
47
            }
48
        }
49
    }
50 7
51
    private function addFiles(HasMedia $model, string $type, array $files, array &$files_order, string $locale = null)
52
    {
53 9
        if(!$this->actionExists($files, 'new')) return;
54
55 7
        foreach ($files['new'] as $id => $file) {
56
            if (!$file) {
57 7
                continue;
58 4
            }
59 4
60
            $this->addFile($model, $type, $files_order, $file, $locale);
61 4
        }
62 4
    }
63
64
    /**
65
     * @param HasMedia $model
66
     * @param array $files
67 7
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
68
     */
69
    private function replaceFiles(HasMedia $model, array $files)
70 7
    {
71
        if(!$this->actionExists($files, 'replace')) return;
72
73
        foreach ($files['replace'] as $id => $file) {
74
            if (!$file) {
75
                continue;
76
            }
77 7
78
            $asset = AssetUploader::uploadFromBase64(json_decode($file)->output->image, json_decode($file)->output->name);
79 7
            $model->replaceAsset($id, $asset->id);
80
        }
81 7
    }
82 4
83
    /**
84 4
     * @param HasMedia $model
85
     * @param array $files
86
     */
87 7
    private function removeFiles(HasMedia $model, array $files)
88 7
    {
89
        if(!$this->actionExists($files, 'delete')) return;
90
91 7
        foreach ($model->assets()->whereIn('id', $files['delete'])->get() as $asset) {
92
            $asset->delete();
93
        }
94
    }
95
96
    private function actionExists(array $files, string $action)
97
    {
98
        return (isset($files[$action]) && is_array($files[$action]) && !empty($files[$action]));
99 9
    }
100
101 9
    private function addFile(HasMedia $model, string $type, array &$files_order, $file, $locale = null)
102 1
    {
103
        if (is_string($file)) {
104 1
            $image_name = json_decode($file)->output->name;
105
            $asset      = $this->addAsset(json_decode($file)->output->image, $type, $locale, $image_name, $model);
106
        } else {
107
            $image_name = $file->getClientOriginalName();
108 1
            $asset      = $this->addAsset($file, $type, $locale, $image_name, $model);
109 1
        }
110
111
        // New files are passed with their filename (instead of their id)
112 9
        // For new files we will replace the filename with the id.
113
        if (false !== ($key = array_search($image_name, $files_order))) {
114 9
            $files_order[$key] = $asset->id;
115
        }
116 9
    }
117 2
118 2
    /**
119
     * Note: this is a replication of the AssetTrait::addFile() with the exception
120
     * that we want to return the asset in order to retrieve the id. This is
121 9
     * currently not available via the AssetTrait.
122
     */
123
    private function addAsset($file, $type = '', $locale = null, $filename = null, HasMedia $model)
124
    {
125
        $filename = $this->sluggifyFilename($filename);
126
127 7
        if (is_string($file)) {
128
            $asset = AssetUploader::uploadFromBase64($file, $filename);
129 7
        } else {
130 7
            $asset = AssetUploader::upload($file, $filename);
131 7
        }
132
133 7
        if ($asset instanceof Asset) {
134
            $asset->attachToModel($model, $type, $locale);
135
        }
136
137
        return $asset;
138
    }
139
140 9
    /**
141
     * @param $filename
142 9
     * @return string
143 9
     */
144 9
    private function sluggifyFilename($filename): string
145
    {
146
        $extension = substr($filename, strrpos($filename, '.') + 1);
147
        $filename  = substr($filename, 0, strrpos($filename, '.'));
148
        $filename  = str_slug($filename) . '.' . $extension;
0 ignored issues
show
Deprecated Code introduced by
The function str_slug() has been deprecated: Str::slug() should be used directly instead. Will be removed in Laravel 5.9. ( Ignorable by Annotation )

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

148
        $filename  = /** @scrutinizer ignore-deprecated */ str_slug($filename) . '.' . $extension;

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
149 9
150
        return $filename;
151
    }
152
153
    /**
154
     * @param $files
155 9
     * @throws FileTooBigException
156
     */
157
    private function validateFileUploads($files): void
158
    {
159
        foreach ($files as $_files) {
160
            foreach ($_files as $file) {
161
                if ($file instanceof UploadedFile && !$file->isValid()) {
162
                    if ($file->getError() == UPLOAD_ERR_INI_SIZE) {
163
                        throw new FileTooBigException(
0 ignored issues
show
Bug introduced by
The type Thinktomorrow\Chief\Media\FileTooBigException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
164
                            'Cannot upload file because it exceeded the allowed upload_max_filesize: upload_max_filesize is smaller than post size. ' .
165
                            'upload_max_filesize: ' . (int)ini_get('upload_max_filesize') . 'MB, ' .
166
                            'post_max_size: ' . (int)(ini_get('post_max_size')) . 'MB'
167
                        );
168
                    }
169
                }
170
            }
171
        }
172
    }
173
174
    private function validateParameters(array $files_by_type, array $files_order_by_type)
175
    {
176
        $actions = ['new', 'replace', 'delete'];
177
178
        foreach ($files_by_type as $type => $files) {
179
            foreach ($files as $locale => $_files) {
180
                if (!in_array($locale, config('translatable.locales'))) {
181
                    throw new \InvalidArgumentException('Corrupt file payload. key is expected to be a valid locale [' . implode(',', config('translatable.locales', [])). ']. Instead [' . $locale . '] is given.');
182
                }
183
184
                if (!is_array($_files)) {
185
                    throw new \InvalidArgumentException('A valid files entry should be an array of files, key with either [new, replace or delete]. Instead a ' . gettype($_files) . ' is given.');
186
                }
187
188
                foreach ($_files as $action => $file) {
189
                    if (!in_array($action, $actions)) {
190
                        throw new \InvalidArgumentException('A valid files entry should have a key of either ['.implode(',', $actions).']. Instead ' . $action . ' is given.');
191
                    }
192
                }
193
            }
194
        }
195
196
        foreach ($files_order_by_type as $type => $fileIdsCollection) {
197
            foreach ($fileIdsCollection as $locale => $commaSeparatedFileIds) {
198
                if (!in_array($locale, config('translatable.locales'))) {
199
                    throw new \InvalidArgumentException('Corrupt file payload. key for the file order is expected to be a valid locale [' . implode(',', config('translatable.locales', [])). ']. Instead [' . $locale . '] is given.');
200
                }
201
            }
202
        }
203
    }
204
205
    private function sanitizeFilesParameter(array $files_by_type): array
206
    {
207
        $defaultLocale = config('app.fallback_locale');
208
209
        foreach ($files_by_type as $type => $files) {
210
            foreach ($files as $locale => $_files) {
211
                if (!in_array($locale, config('translatable.locales'))) {
212
                    unset($files_by_type[$type][$locale]);
213
214
                    if (!isset($files_by_type[$type][$defaultLocale])) {
215
                        $files_by_type[$type][$defaultLocale] = [];
216
                    }
217
218
                    $files_by_type[$type][$defaultLocale][$locale] = $_files;
219
                }
220
            }
221
        }
222
223
        return $files_by_type;
224
    }
225
226
    private function sanitizeFilesOrderParameter(array $files_order_by_type): array
227
    {
228
        $defaultLocale = config('app.fallback_locale');
229
230
        foreach ($files_order_by_type as $type => $fileIdsCollection) {
231
            if (!is_array($fileIdsCollection)) {
232
                $fileIdsCollection = [$defaultLocale => $fileIdsCollection];
233
                $files_order_by_type[$type] = $fileIdsCollection;
234
            }
235
236
            foreach ($fileIdsCollection as $locale => $commaSeparatedFileIds) {
237
                $files_order_by_type[$type][$locale] = explode(',', $commaSeparatedFileIds);
238
            }
239
        }
240
241
        return $files_order_by_type;
242
    }
243
244
    private function sortFiles(HasMedia $model, string $type, array $fileIdsCollection)
245
    {
246
        $sortedFileIds = [];
247
248
        foreach ($fileIdsCollection as $locale => $fileIds) {
249
            $sortedFileIds = array_merge($sortedFileIds, $fileIds);
250
        }
251
252
        $this->sortingAssetsByType($model, $type, $sortedFileIds);
253
    }
254
255
    private function sortingAssetsByType(HasMedia $model, $type, array $sortedAssetIds)
256
    {
257
        $assets = $model->assets()->where('asset_pivots.type', $type)->get();
258
259
        foreach ($assets as $asset) {
260
            $pivot = $asset->pivot;
261
            $pivot->order = array_search($asset->id, $sortedAssetIds);
262
263
            $pivot->save();
264
        }
265
    }
266
}
267