Passed
Push — ft/states ( 61e8cc...4cf7fd )
by Ben
06:59
created

UploadMedia::validateFileUploads()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 8.8343

Importance

Changes 0
Metric Value
eloc 8
c 0
b 0
f 0
dl 0
loc 10
ccs 4
cts 7
cp 0.5714
rs 9.2222
cc 6
nc 5
nop 1
crap 8.8343
1
<?php
2
3
namespace Thinktomorrow\Chief\Media;
4
5
use Illuminate\Support\Str;
6
use Illuminate\Http\UploadedFile;
7
use Thinktomorrow\AssetLibrary\Asset;
8
use Thinktomorrow\AssetLibrary\HasAsset;
9
use Thinktomorrow\AssetLibrary\Application\AddAsset;
10
use Thinktomorrow\AssetLibrary\Application\SortAssets;
11
use Thinktomorrow\Chief\Media\DuplicateAssetException;
12
use Thinktomorrow\AssetLibrary\Application\DetachAsset;
13
use Thinktomorrow\AssetLibrary\Application\ReplaceAsset;
14
use Thinktomorrow\AssetLibrary\Application\AssetUploader;
15
16
class UploadMedia
17
{
18
    /**
19
     * Upload from base64encoded files, usually
20
     * coming from slim upload component
21
     *
22
     * @param HasAsset $model
23
     * @param array $files_by_type
24
     * @param array $files_order_by_type
25
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
26 86
     */
27
    public function fromUploadComponent(HasAsset $model, array $files_by_type, array $files_order_by_type)
28 86
    {
29 86
        ini_set('memory_limit', '256M');
30 86
        $files_by_type = $this->sanitizeFilesParameter($files_by_type);
31 86
        $files_order_by_type = $this->sanitizeFilesOrderParameter($files_order_by_type);
32
        $this->validateParameters($files_by_type, $files_order_by_type);
33
34 86
        // When no files are uploaded, we still would like to sort our assets duh
35 70
        if (empty($files_by_type)) {
36 2
            foreach ($files_order_by_type as $type => $files) {
37
                app(SortAssets::class)->handle($model, $type, $files);
38
            }
39 70
40
            return;
41
        }
42 16
43 16
        foreach ($files_by_type as $type => $files_by_locale) {
44 16
            foreach ($files_by_locale as $locale => $files) {
45
                $this->validateFileUploads($files);
46 16
47
                $fileIdsCollection = $files_order_by_type[$type] ?? [];
48 16
49 16
                $this->addFiles($model, $type, $files, $fileIdsCollection, $locale);
50 16
                $this->replaceFiles($model, $files);
51
                $this->removeFiles($model, $files, $type, $locale);
52 16
            }
53
            app(SortAssets::class)->handle($model, $type, $fileIdsCollection ?? []);
54 16
        }
55
    }
56 16
57
    private function addFiles(HasAsset $model, string $type, array $files, array &$files_order, string $locale = null)
58 16
    {
59 4
        if (!$this->actionExists($files, 'new')) {
60
            return;
61
        }
62 14
63 14
        foreach ($files['new'] as $id => $file) {
64
            if ($file) {
65
                $this->addFile($model, $type, $file, $files_order, $locale);
66
            }
67 14
        }
68
    }
69 14
70
    /**
71
     * @param HasAsset $model
72
     * @param array $files
73
     * @throws \Spatie\MediaLibrary\Exceptions\FileCannotBeAdded
74
     */
75
    private function replaceFiles(HasAsset $model, array $files)
76 16
    {
77
        if (!$this->actionExists($files, 'replace')) {
78 16
            return;
79 15
        }
80
81
        foreach ($files['replace'] as $id => $file) {
82 2
            if ($file) {
83 2
                $asset = AssetUploader::uploadFromBase64(json_decode($file)->output->image, json_decode($file)->output->name);
84
                app(ReplaceAsset::class)->handle($model, $id, $asset->id);
85
            }
86
        }
87 2
    }
88 2
89
    /**
90 2
     * @param HasAsset $model
91
     * @param array $files
92
     */
93
    private function removeFiles(HasAsset $model, array $files, string $type, string $locale)
94
    {
95
        if (!$this->actionExists($files, 'delete')) {
96 16
            return;
97
        }
98 16
99 14
        foreach ($files['delete'] as $id => $file) {
100
            if ($file) {
101
                app(DetachAsset::class)->detach($model, $file, $type, $locale);
102 3
            }
103 3
        }
104
    }
105
106 3
    private function actionExists(array $files, string $action)
107
    {
108 3
        return (isset($files[$action]) && is_array($files[$action]) && !empty($files[$action]));
109
    }
110 16
111
    private function addFile(HasAsset $model, string $type, $file, array &$files_order, $locale = null)
112 16
    {
113
        if (isset(json_decode($file)->output)) {
114
            $image_name = json_decode($file)->output->name;
115 14
            $asset      = app(AddAsset::class)->add($model, json_decode($file)->output->image, $type, $locale, $this->sluggifyFilename($image_name));
0 ignored issues
show
Unused Code introduced by
The assignment to $asset is dead and can be removed.
Loading history...
116
        } else {
117 14
            if ($file instanceof UploadedFile) {
118 8
                $image_name = $file->getClientOriginalName();
119 8
                $asset      = app(AddAsset::class)->add($model, $file, $type, $locale, $this->sluggifyFilename($image_name));
120
121 7
                // New files are passed with their filename (instead of their id)
122 4
                // For new files we will replace the filename with the id.
123 4
                if (false !== ($key = array_search($image_name, $files_order))) {
124
                    $files_order[$key] = (string) $asset->id;
125
                }
126
            } else {
127 4
                $file   = Asset::find($file);
128 4
                if ($file) {
129
                    if ($model->assetRelation()->where('asset_pivots.type', $type)->where('asset_pivots.locale', $locale)->get()->contains($file)) {
130
                        throw new DuplicateAssetException();
131 3
                    }
132 3
133
                    $asset  = app(AddAsset::class)->add($model, $file, $type, $locale);
134
                }
135 14
            }
136
        }
137
    }
138
139
    /**
140
     * @param $filename
141 11
     * @return string
142
     */
143 11
    private function sluggifyFilename($filename): string
144 11
    {
145 11
        $extension = substr($filename, strrpos($filename, '.') + 1);
146
        $filename  = substr($filename, 0, strrpos($filename, '.'));
147 11
        $filename  = Str::slug($filename) . '.' . $extension;
148
149
        return $filename;
150
    }
151
152
    /**
153
     * @param $files
154 16
     * @throws FileTooBigException
155
     */
156 16
    private function validateFileUploads($files): void
157 16
    {
158 16
        foreach ($files as $_files) {
159
            foreach ($_files as $file) {
160
                if ($file instanceof UploadedFile && !$file->isValid()) {
161
                    if ($file->getError() == UPLOAD_ERR_INI_SIZE) {
162
                        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...
163 16
                            'Cannot upload file because it exceeded the allowed upload_max_filesize: upload_max_filesize is smaller than post size. ' .
164
                            'upload_max_filesize: ' . (int)ini_get('upload_max_filesize') . 'MB, ' .
165
                            'post_max_size: ' . (int)(ini_get('post_max_size')) . 'MB'
166
                        );
167
                    }
168
                }
169 16
            }
170
        }
171 86
    }
172
173 86
    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

173
    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...
174 86
    {
175 16
        $actions = ['new', 'replace', 'delete'];
176 16
        foreach ($files_by_type as $type => $files) {
177
            foreach ($files as $locale => $_files) {
178
                foreach ($_files as $action => $file) {
179
                    if (!in_array($action, $actions)) {
180 16
                        throw new \InvalidArgumentException('A valid files entry should have a key of either ['.implode(',', $actions).']. Instead ' . $action . ' is given.');
181
                    }
182
                }
183
            }
184 16
        }
185 16
    }
186 16
187
    private function sanitizeFilesParameter(array $files_by_type): array
188
    {
189
        $defaultLocale = config('app.fallback_locale');
190
191 86
        foreach ($files_by_type as $type => $files) {
192
            foreach ($files as $locale => $_files) {
193 86
                if (!in_array($locale, config('translatable.locales'))) {
194
                    unset($files_by_type[$type][$locale]);
195 86
196
                    if (!isset($files_by_type[$type][$defaultLocale])) {
197 86
                        $files_by_type[$type][$defaultLocale] = [];
198 16
                    }
199 16
200 10
                    $files_by_type[$type][$defaultLocale][$locale] = $_files;
201
                }
202 10
            }
203 10
        }
204
205
        return $files_by_type;
206 16
    }
207
208
    private function sanitizeFilesOrderParameter(array $files_order_by_locale): array
209
    {
210
        foreach ($files_order_by_locale as $locale => $fileIdsCollection) {
211 86
            foreach ($fileIdsCollection as $type => $commaSeparatedFileIds) {
212
                $type = str_replace("files-", "", $type);
213
                $files_order_by_type[$type][] = explode(',', $commaSeparatedFileIds);
214 86
                $files_order_by_type[$type] = collect($files_order_by_type)->flatten()->unique()->toArray();
215
            }
216 86
        }
217 2
218 2
        return $files_order_by_type ?? $files_order_by_locale;
219 2
    }
220
}
221