Passed
Push — analysis-XWEGOP ( a7f153 )
by Philippe
13:05 queued 02:50
created

UploadMedia::validateParameters()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

156
        $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...
157
158
        return $filename;
159
    }
160
161
    /**
162
     * @param $files
163
     * @throws FileTooBigException
164
     */
165
    private function validateFileUploads($files): void
166
    {
167
        foreach ($files as $_files) {
168
            foreach ($_files as $file) {
169
                if ($file instanceof UploadedFile && !$file->isValid()) {
170
                    if ($file->getError() == UPLOAD_ERR_INI_SIZE) {
171
                        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...
172
                            'Cannot upload file because it exceeded the allowed upload_max_filesize: upload_max_filesize is smaller than post size. ' .
173
                            'upload_max_filesize: ' . (int)ini_get('upload_max_filesize') . 'MB, ' .
174
                            'post_max_size: ' . (int)(ini_get('post_max_size')) . 'MB'
175
                        );
176
                    }
177
                }
178
            }
179
        }
180
    }
181
182
    private function validateParameters(array $files_by_type, array $files_order_by_type)
0 ignored issues
show
Unused Code introduced by
The parameter $files_order_by_type is not used and could be removed. ( Ignorable by Annotation )

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

182
    private function validateParameters(array $files_by_type, /** @scrutinizer ignore-unused */ array $files_order_by_type)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
183
    {
184
        $actions = ['new', 'replace', 'delete'];
185
        foreach ($files_by_type as $type => $files) {
186
            foreach ($files as $locale => $_files) {
187
                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
                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
                foreach ($_files as $action => $file) {
196
                    if (!in_array($action, $actions)) {
197
                        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
204
    private function sanitizeFilesParameter(array $files_by_type): array
205
    {
206
        $defaultLocale = config('app.fallback_locale');
207
208
        foreach ($files_by_type as $type => $files) {
209
            foreach ($files as $locale => $_files) {
210
                if (!in_array($locale, config('translatable.locales'))) {
211
                    unset($files_by_type[$type][$locale]);
212
213
                    if (!isset($files_by_type[$type][$defaultLocale])) {
214
                        $files_by_type[$type][$defaultLocale] = [];
215
                    }
216
217
                    $files_by_type[$type][$defaultLocale][$locale] = $_files;
218
                }
219
            }
220
        }
221
222
        return $files_by_type;
223
    }
224
225
    private function sanitizeFilesOrderParameter(array $files_order_by_locale): array
226
    {
227
        foreach ($files_order_by_locale as $locale => $fileIdsCollection) {
228
            foreach ($fileIdsCollection as $type => $commaSeparatedFileIds) {
229
                $type = str_replace("files-", "", $type);
230
                $files_order_by_type[$type][] = explode(',', $commaSeparatedFileIds);
231
                $files_order_by_type[$type] = collect($files_order_by_type)->flatten()->unique()->toArray();
232
            }
233
        }
234
235
        return $files_order_by_type ?? $files_order_by_locale;
236
    }
237
238
    private function sortFiles(HasMedia $model, $type, array $sortedAssetIds)
239
    {
240
        $assets = $model->assets()->where('asset_pivots.type', $type)->get();
241
242
        foreach ($assets as $asset) {
243
            $pivot = $asset->pivot;
244
            $pivot->order = array_search($asset->id, $sortedAssetIds);
245
246
            $pivot->save();
247
        }
248
    }
249
}
250