Passed
Push — ft/states ( 7f21b4...728e57 )
by Ben
08:08
created

UploadMedia::addFile()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 23
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 7

Importance

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

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