Passed
Push — master ( 0f8e83...edb234 )
by Philippe
02:11 queued 12s
created

UploadMedia::validateFileUploads()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 7.8984

Importance

Changes 0
Metric Value
cc 6
eloc 8
nc 5
nop 1
dl 0
loc 10
ccs 5
cts 8
cp 0.625
crap 7.8984
rs 9.2222
c 0
b 0
f 0
1
<?php
2
3
namespace Thinktomorrow\Chief\Media;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Http\UploadedFile;
7
use Spatie\MediaLibrary\HasMedia\HasMedia;
8
use Thinktomorrow\AssetLibrary\Models\Asset;
9
use Thinktomorrow\AssetLibrary\Models\AssetUploader;
10
11
class UploadMedia
12
{
13
    /**
14
     * Upload from base64encoded files, usually
15
     * coming from slim upload component
16
     *
17
     * @param HasMedia $model
18
     * @param array $files_by_type
19
     * @param array $files_order_by_type
20
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
21
     */
22 79
    public function fromUploadComponent(HasMedia $model, array $files_by_type, array $files_order_by_type)
23
    {
24 79
        ini_set('memory_limit', '256M');
25
        
26 79
        $files_by_type = $this->sanitizeFilesParameter($files_by_type);
27 79
        $files_order_by_type = $this->sanitizeFilesOrderParameter($files_order_by_type);
28 79
        $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 79
        if (empty($files_by_type)) {
32 66
            foreach ($files_order_by_type as $type => $files) {
33 2
                $this->sortFiles($model, $type, $files);
34
            }
35
36 66
            return;
37
        }
38
39 13
        foreach ($files_by_type as $type => $files_by_locale) {
40 13
            foreach ($files_by_locale as $locale => $files) {
41 13
                $this->validateFileUploads($files);
42
                
43 13
                $fileIdsCollection = $files_order_by_type[$type] ?? [];
44
                
45 13
                $this->addFiles($model, $type, $files, $fileIdsCollection, $locale);
46 13
                $this->replaceFiles($model, $files);
47 13
                $this->removeFiles($model, $files);
48
            }
49 13
            $this->sortFiles($model, $type, $fileIdsCollection ?? []);
50
        }
51 13
    }
52
53 13
    private function addFiles(HasMedia $model, string $type, array $files, array &$files_order, string $locale = null)
54
    {
55 13
        if (!$this->actionExists($files, 'new')) {
56 4
            return;
57
        }
58
59 11
        foreach ($files['new'] as $id => $file) {
60 11
            if (!$file) {
61
                continue;
62
            }
63
64 11
            $this->addFile($model, $type, $file, $files_order, $locale);
65
        }
66 11
    }
67
68
    /**
69
     * @param HasMedia $model
70
     * @param array $files
71
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
72
     */
73 13
    private function replaceFiles(HasMedia $model, array $files)
74
    {
75 13
        if (!$this->actionExists($files, 'replace')) {
76 12
            return;
77
        }
78
79 2
        foreach ($files['replace'] as $id => $file) {
80 2
            if (!$file) {
81
                continue;
82
            }
83
84 2
            $asset = AssetUploader::uploadFromBase64(json_decode($file)->output->image, json_decode($file)->output->name);
85 2
            $model->replaceAsset($id, $asset->id);
86
        }
87 2
    }
88
89
    /**
90
     * @param HasMedia $model
91
     * @param array $files
92
     */
93 13
    private function removeFiles(HasMedia $model, array $files)
94
    {
95 13
        if (!$this->actionExists($files, 'delete')) {
96 11
            return;
97
        }
98
99 3
        foreach ($model->assets()->whereIn('id', $files['delete'])->get() as $asset) {
100 3
            $asset->delete();
101
        }
102 3
    }
103
104 13
    private function actionExists(array $files, string $action)
105
    {
106 13
        return (isset($files[$action]) && is_array($files[$action]) && !empty($files[$action]));
107
    }
108
109 11
    private function addFile(HasMedia $model, string $type, $file, array &$files_order, $locale = null)
110
    {
111 11
        if (is_string($file)) {
112 8
            $image_name = json_decode($file)->output->name;
113 8
            $asset      = $this->addAsset(json_decode($file)->output->image, $type, $locale, $image_name, $model);
114
        } else {
115 4
            $image_name = $file->getClientOriginalName();
116 4
            $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 11
        if (false !== ($key = array_search($image_name, $files_order))) {
122
            $files_order[$key] = (string) $asset->id;
123
        }
124 11
    }
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 11
    private function addAsset($file, $type = '', $locale = null, $filename = null, HasMedia $model)
132
    {
133 11
        $filename = $this->sluggifyFilename($filename);
134
135 11
        if (is_string($file)) {
136 8
            $asset = AssetUploader::uploadFromBase64($file, $filename);
137
        } else {
138 4
            $asset = AssetUploader::upload($file, $filename);
139
        }
140
141 11
        if ($asset instanceof Asset) {
142 11
            $asset->attachToModel($model, $type, $locale);
143
        }
144
145 11
        return $asset;
146
    }
147
148
    /**
149
     * @param $filename
150
     * @return string
151
     */
152 11
    private function sluggifyFilename($filename): string
153
    {
154 11
        $extension = substr($filename, strrpos($filename, '.') + 1);
155 11
        $filename  = substr($filename, 0, strrpos($filename, '.'));
156 11
        $filename  = Str::slug($filename) . '.' . $extension;
157
158 11
        return $filename;
159
    }
160
161
    /**
162
     * @param $files
163
     * @throws FileTooBigException
164
     */
165 13
    private function validateFileUploads($files): void
166
    {
167 13
        foreach ($files as $_files) {
168 13
            foreach ($_files as $file) {
169 13
                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 13
                            'post_max_size: ' . (int)(ini_get('post_max_size')) . 'MB'
175
                        );
176
                    }
177
                }
178
            }
179
        }
180 13
    }
181
182 79
    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 79
        $actions = ['new', 'replace', 'delete'];
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 79
    }
203
204 79
    private function sanitizeFilesParameter(array $files_by_type): array
205
    {
206 79
        $defaultLocale = config('app.fallback_locale');
207
208 79
        foreach ($files_by_type as $type => $files) {
209 13
            foreach ($files as $locale => $_files) {
210 13
                if (!in_array($locale, config('translatable.locales'))) {
211 9
                    unset($files_by_type[$type][$locale]);
212
213 9
                    if (!isset($files_by_type[$type][$defaultLocale])) {
214 9
                        $files_by_type[$type][$defaultLocale] = [];
215
                    }
216
217 13
                    $files_by_type[$type][$defaultLocale][$locale] = $_files;
218
                }
219
            }
220
        }
221
222 79
        return $files_by_type;
223
    }
224
225 79
    private function sanitizeFilesOrderParameter(array $files_order_by_locale): array
226
    {
227 79
        foreach ($files_order_by_locale as $locale => $fileIdsCollection) {
228 2
            foreach ($fileIdsCollection as $type => $commaSeparatedFileIds) {
229 2
                $type = str_replace("files-", "", $type);
230 2
                $files_order_by_type[$type][] = explode(',', $commaSeparatedFileIds);
231 2
                $files_order_by_type[$type] = collect($files_order_by_type)->flatten()->unique()->toArray();
232
            }
233
        }
234
235 79
        return $files_order_by_type ?? $files_order_by_locale;
236
    }
237
238 15
    private function sortFiles(HasMedia $model, $type, array $sortedAssetIds)
239
    {
240 15
        $assets = $model->assets()->where('asset_pivots.type', $type)->get();
241
242 15
        foreach ($assets as $asset) {
243 14
            $pivot = $asset->pivot;
244 14
            $pivot->order = array_search($asset->id, $sortedAssetIds);
245
246 14
            $pivot->save();
247
        }
248 15
    }
249
}
250