Completed
Pull Request — master (#274)
by
unknown
63:55 queued 33:13
created

UploadMedia   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 261
Duplicated Lines 0 %

Test Coverage

Coverage 93.33%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 55
eloc 102
c 3
b 1
f 0
dl 0
loc 261
ccs 112
cts 120
cp 0.9333
rs 6

14 Methods

Rating   Name   Duplication   Size   Complexity  
A sortFiles() 0 9 2
A fromUploadComponent() 0 27 5
A addFiles() 0 12 4
A addFile() 0 14 3
A sanitizeFilesParameter() 0 19 5
A validateFileUploads() 0 10 6
A actionExists() 0 3 3
A sanitizeFilesOrderParameter() 0 16 4
A addAsset() 0 15 3
B validateParameters() 0 26 10
A replaceFiles() 0 13 4
A sluggifyFilename() 0 7 1
A removeFiles() 0 8 3
A sortingAssetsByType() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like UploadMedia 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 UploadMedia, and based on these observations, apply Extract Interface, too.

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

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

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