Completed
Push — fix/localized-img-bug ( daa57d...6977d8 )
by Ben
48:44 queued 39:43
created

UploadMedia::sortFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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

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