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; |
|
|
|
|
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( |
|
|
|
|
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) |
|
|
|
|
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
|
|
|
|
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.