1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @link http://www.writesdown.com/ |
4
|
|
|
* @copyright Copyright (c) 2015 WritesDown |
5
|
|
|
* @license http://www.writesdown.com/license/ |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace common\components; |
9
|
|
|
|
10
|
|
|
use common\models\Media; |
11
|
|
|
use common\models\Post; |
12
|
|
|
use Imagine\Image\Box; |
13
|
|
|
use Imagine\Image\ManipulatorInterface; |
14
|
|
|
use Imagine\Image\Point; |
15
|
|
|
use Yii; |
16
|
|
|
use yii\data\Pagination; |
17
|
|
|
use yii\helpers\ArrayHelper; |
18
|
|
|
use yii\helpers\FileHelper; |
19
|
|
|
use yii\helpers\Url; |
20
|
|
|
use yii\imagine\Image; |
21
|
|
|
use yii\web\Response; |
22
|
|
|
use yii\web\UploadedFile; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Upload handler for Media model. |
26
|
|
|
* |
27
|
|
|
* @author Agiel K. Saputra <[email protected]> |
28
|
|
|
* @since 0.1.0 |
29
|
|
|
*/ |
30
|
|
|
class MediaUploadHandler |
31
|
|
|
{ |
32
|
|
|
const PRINT_RESPONSE = true; |
33
|
|
|
const NOT_PRINT_RESPONSE = false; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var array Options for upload handler, can be overridden over class constructs. |
37
|
|
|
*/ |
38
|
|
|
protected $options = []; |
39
|
|
|
/** |
40
|
|
|
* @var array Used to generate response. |
41
|
|
|
*/ |
42
|
|
|
protected $response = []; |
43
|
|
|
/** |
44
|
|
|
* @var array Grouping files based on its extension. |
45
|
|
|
*/ |
46
|
|
|
protected $fileTypes = [ |
47
|
|
|
'image' => [ |
48
|
|
|
'extensions' => '/\.(gif|jpg|jpeg|png)$/i', |
49
|
|
|
], |
50
|
|
|
'audio' => [ |
51
|
|
|
'extensions' => '/\.(m4a|mp3|wav|wma|oga)$/i', |
52
|
|
|
'mime_icon' => 'img/mime/audio.png', |
53
|
|
|
], |
54
|
|
|
'video' => [ |
55
|
|
|
'extensions' => '/\.(3gp|mkv|flv|og?(a|g)|avi|mov|wmv|mp4|m4p|mp?(g|2|eg|e|v))$/i', |
56
|
|
|
'mime_icon' => 'img/mime/video.png', |
57
|
|
|
], |
58
|
|
|
'pdf' => [ |
59
|
|
|
'extensions' => '/\.(pdf|xps)$/i', |
60
|
|
|
'mime_icon' => 'img/mime/pdf.png', |
61
|
|
|
], |
62
|
|
|
'spreadsheet' => [ |
63
|
|
|
'extensions' => '/\.(xls|xlsx|ods|csv|xml)$/i', |
64
|
|
|
'mime_icon' => 'img/mime/spreadsheet.png', |
65
|
|
|
], |
66
|
|
|
'document' => [ |
67
|
|
|
'extensions' => '/\.(doc?(m|x)|odt)$/i', |
68
|
|
|
'mime_icon' => 'img/mime/document.png', |
69
|
|
|
], |
70
|
|
|
'archive' => [ |
71
|
|
|
'extensions' => '/\.(rar|zip|tar|7zip)$/i', |
72
|
|
|
'mime_icon' => 'img/mime/archive.png', |
73
|
|
|
], |
74
|
|
|
'code' => [ |
75
|
|
|
'extensions' => '/\.(php|c?pp|java|vb?s|html|js|css)$/i', |
76
|
|
|
'mime_icon' => 'img/mime/audio.png', |
77
|
|
|
], |
78
|
|
|
'interactive' => [ |
79
|
|
|
'extensions' => '/\.(ppt|pptx|odp)$/i', |
80
|
|
|
'icon' => 'img/mime/interactive.png', |
81
|
|
|
], |
82
|
|
|
'text' => [ |
83
|
|
|
'extensions' => '/\.(txt|md|bat)$/i', |
84
|
|
|
'mime_icon' => 'img/mime/text.png', |
85
|
|
|
], |
86
|
|
|
]; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* @var Media |
90
|
|
|
*/ |
91
|
|
|
private $_media; |
92
|
|
|
/** |
93
|
|
|
* @var array Used to create Media Meta. |
94
|
|
|
*/ |
95
|
|
|
private $_meta; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* Create object of MediaUploadHandler. |
99
|
|
|
* |
100
|
|
|
* @param array|null $options |
101
|
|
|
* @param bool $initialize |
102
|
|
|
*/ |
103
|
|
|
public function __construct($options = null, $initialize = true) |
104
|
|
|
{ |
105
|
|
|
// Set response format to RAW. |
106
|
|
|
Yii::$app->response->format = Response::FORMAT_RAW; |
107
|
|
|
// Set options of MediaUploadHandler. |
108
|
|
|
$this->setOptions($options); |
|
|
|
|
109
|
|
|
|
110
|
|
|
if ($initialize) { |
111
|
|
|
$this->initialize(); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* Initialize the action of MediaUploadHandler based on request method if set true. |
117
|
|
|
*/ |
118
|
|
|
protected function initialize() |
119
|
|
|
{ |
120
|
|
|
switch (Yii::$app->request->method) { |
121
|
|
|
case 'OPTIONS': |
122
|
|
|
case 'HEAD': |
123
|
|
|
$this->head(); |
124
|
|
|
break; |
125
|
|
|
case 'PATCH': |
126
|
|
|
case 'PUT': |
127
|
|
|
case 'POST': |
128
|
|
|
$this->post($this->getOption('print_response')); |
|
|
|
|
129
|
|
|
break; |
130
|
|
|
case 'GET': |
131
|
|
|
$this->get($this->getOption('print_response')); |
|
|
|
|
132
|
|
|
break; |
133
|
|
|
case 'DELETE': |
134
|
|
|
$this->delete($this->getOption('print_response')); |
|
|
|
|
135
|
|
|
break; |
136
|
|
|
default: |
137
|
|
|
$this->setHeader('HTTP/1.1 405 Method Not Allowed'); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Get server var based on id. Return null when it's not exist. |
143
|
|
|
* |
144
|
|
|
* @param $id |
145
|
|
|
* @return mixed |
146
|
|
|
*/ |
147
|
|
|
protected function getServerVar($id) |
|
|
|
|
148
|
|
|
{ |
149
|
|
|
if (isset($_SERVER[$id])) { |
150
|
|
|
return $_SERVER[$id]; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
return null; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* Get singular param name. |
158
|
|
|
* |
159
|
|
|
* @return string |
160
|
|
|
*/ |
161
|
|
|
protected function getSingularParamName() |
162
|
|
|
{ |
163
|
|
|
return substr($this->options['param_name'], 0, -1); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Adds a new header. |
168
|
|
|
* If there is already a header with the same name, it will be replaced. |
169
|
|
|
* |
170
|
|
|
* @param string $name The name of the header. |
171
|
|
|
* @param string $value The value of the header. |
172
|
|
|
*/ |
173
|
|
|
protected function setHeader($name, $value = '') |
174
|
|
|
{ |
175
|
|
|
Yii::$app->response->headers->set($name, $value); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Set header content-type. |
180
|
|
|
*/ |
181
|
|
|
protected function sendContentTypeHeader() |
182
|
|
|
{ |
183
|
|
|
$this->setHeader('Vary', 'Accept'); |
184
|
|
|
|
185
|
|
|
if (strpos($this->getServerVar('HTTP_ACCEPT'), 'application/json') !== false) { |
186
|
|
|
$this->setHeader('Content-type', 'application/json'); |
187
|
|
|
} else { |
188
|
|
|
$this->setHeader('Content-type', 'text/plain'); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Set header Access-Control-*. |
194
|
|
|
*/ |
195
|
|
|
protected function sendAccessControlHeaders() |
196
|
|
|
{ |
197
|
|
|
$this->setHeader('Access-Control-Allow-Origin', $this->options['access_control_allow_origin']); |
198
|
|
|
$this->setHeader('Access-Control-Allow-Credentials', $this->options['access_control_allow_credentials'] |
199
|
|
|
? 'true' |
200
|
|
|
: 'false' |
201
|
|
|
); |
202
|
|
|
$this->setHeader('Access-Control-Allow-Methods', implode(', ', $this->options['access_control_allow_methods'])); |
203
|
|
|
$this->setHeader('Access-Control-Allow-Headers', implode(', ', $this->options['access_control_allow_headers'])); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Finds the Media model based on its primary key value. |
208
|
|
|
* If the model is not found it will return null. |
209
|
|
|
* |
210
|
|
|
* @param integer $id |
211
|
|
|
* @return Media|array |
212
|
|
|
*/ |
213
|
|
|
protected function findMedia($id) |
214
|
|
|
{ |
215
|
|
|
if (($model = Media::findOne($id)) !== null) { |
216
|
|
|
return $model; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
return null; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Finds the Post model based on its primary key value. |
224
|
|
|
* If the model is not found it will return null. |
225
|
|
|
* |
226
|
|
|
* @param integer $id |
227
|
|
|
* @return Post|null |
228
|
|
|
*/ |
229
|
|
|
protected function findPost($id) |
230
|
|
|
{ |
231
|
|
|
if (($model = Post::findOne($id)) !== null) { |
232
|
|
|
return $model; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
return null; |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Get user path of login user. It can be disabled by override config, set user_dirs to false. |
240
|
|
|
* |
241
|
|
|
* @return string The username of login user. |
242
|
|
|
*/ |
243
|
|
|
protected function getUserPath() |
244
|
|
|
{ |
245
|
|
|
if ($this->options['user_dirs'] && !Yii::$app->user->isGuest) { |
246
|
|
|
return Yii::$app->user->identity->username . '/'; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
return ''; |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
/** |
253
|
|
|
* Year-month path generated by date function can be disable by set year_month_path to false. |
254
|
|
|
* |
255
|
|
|
* @return string date(/Y/m). |
256
|
|
|
*/ |
257
|
|
|
protected function getYearMonthPath() |
258
|
|
|
{ |
259
|
|
|
if ($this->options['year_month_dirs']) { |
260
|
|
|
return date('Y/m/'); |
261
|
|
|
} |
262
|
|
|
|
263
|
|
|
return ''; |
264
|
|
|
} |
265
|
|
|
|
266
|
|
|
/** |
267
|
|
|
* Get upload path based on current config, generate upload_dir/user_path/y/m/filename.ext. |
268
|
|
|
* |
269
|
|
|
* @param null $fileName Filename and extension (filename.ext). |
270
|
|
|
* @return string |
271
|
|
|
*/ |
272
|
|
|
protected function getUploadPath($fileName = null) |
273
|
|
|
{ |
274
|
|
|
return $this->getOption('upload_dir') . $this->getUserPath() . $this->getYearMonthPath() . $fileName; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Get file-path of the filename. |
279
|
|
|
* |
280
|
|
|
* @param string|null $fileName |
281
|
|
|
* @return string |
282
|
|
|
*/ |
283
|
|
|
protected function getFilePath($fileName = null) |
284
|
|
|
{ |
285
|
|
|
return $this->getOption('upload_dir') . $fileName; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Generate slug for uploaded file. |
290
|
|
|
* Replace all space to - and transform all character to lowercase. |
291
|
|
|
* |
292
|
|
|
* @param string $fileName |
293
|
|
|
* @param array $replace The replace_pairs parameter may be used as a substitute for to and from in which case. |
294
|
|
|
* it's an array in the form array('from' => 'to', ...). |
295
|
|
|
* @param string $delimiter |
296
|
|
|
* @see strtr |
297
|
|
|
* @return string Clean name |
298
|
|
|
*/ |
299
|
|
|
protected function generateSlug($fileName, $replace = [], $delimiter = '-') |
300
|
|
|
{ |
301
|
|
|
setlocale(LC_ALL, 'en_US.UTF8'); |
302
|
|
|
$fileName = trim($fileName); |
303
|
|
|
|
304
|
|
|
if (!empty($replace)) { |
305
|
|
|
$fileName = strtr($fileName, $replace); |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
$cleanName = iconv('UTF-8', 'ASCII//TRANSLIT', $fileName); |
309
|
|
|
$cleanName = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $cleanName); |
310
|
|
|
$cleanName = strtolower(trim($cleanName, '-')); |
311
|
|
|
$cleanName = preg_replace("/[\/_|+ -]+/", $delimiter, $cleanName); |
312
|
|
|
|
313
|
|
|
return $cleanName; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Callback function of upCountName. |
318
|
|
|
* |
319
|
|
|
* @param array $matches |
320
|
|
|
* @return string |
321
|
|
|
*/ |
322
|
|
|
protected function upCountNameCallback($matches) |
323
|
|
|
{ |
324
|
|
|
$index = isset($matches[1]) ? intval($matches[1]) + 1 : 1; |
325
|
|
|
$ext = isset($matches[2]) ? $matches[2] : ''; |
326
|
|
|
|
327
|
|
|
return '-' . $index . $ext; |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* The number before fileName extension is replaced by upCountNameCallback. |
332
|
|
|
* |
333
|
|
|
* @param string $fileName |
334
|
|
|
* @return mixed |
335
|
|
|
* @see upCountNameCallback |
336
|
|
|
*/ |
337
|
|
|
protected function upCountName($fileName) |
338
|
|
|
{ |
339
|
|
|
return preg_replace_callback('/(?:(?:\-([\d]+))?(\.[^.]+))?$/', [$this, 'upCountNameCallback'], $fileName, 1); |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
/** |
343
|
|
|
* Get filename of uploaded file. |
344
|
|
|
* If the filename is already exist in the upload directory |
345
|
|
|
* then the number between - and before the extension plus 1. |
346
|
|
|
* |
347
|
|
|
* @param UploadedFile $file |
348
|
|
|
* @return string |
349
|
|
|
*/ |
350
|
|
|
protected function getFileName($file) |
351
|
|
|
{ |
352
|
|
|
$index = 0; |
353
|
|
|
$fileName = $this->generateSlug($file->baseName); |
354
|
|
|
$fileName .= '.' . $file->extension; |
355
|
|
|
$fileName = trim(basename(stripslashes($fileName)), ".\x00..\x20"); |
356
|
|
|
|
357
|
|
|
if (!$fileName) { |
358
|
|
|
$fileName = str_replace('.', '-', microtime(true)); |
359
|
|
|
} |
360
|
|
|
|
361
|
|
|
while (is_file($this->getUploadPath($fileName))) { |
362
|
|
|
$fileName = $this->upCountName($fileName); |
363
|
|
|
$index++; |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
if ($index !== 0) { |
367
|
|
|
// Replace media title |
368
|
|
|
$this->_media->title = $file->baseName . ' ' . $index; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return $fileName; |
372
|
|
|
} |
373
|
|
|
|
374
|
|
|
/** |
375
|
|
|
* @param \imagine\image\ImageInterface $image |
376
|
|
|
* @param string $filePath |
377
|
|
|
* @return bool |
378
|
|
|
*/ |
379
|
|
|
protected function correctExifRotation($image, $filePath) |
380
|
|
|
{ |
381
|
|
|
if (!function_exists('exif_read_data')) { |
382
|
|
|
return false; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
$exif = @exif_read_data($filePath); |
386
|
|
|
|
387
|
|
|
if ($exif === false) { |
388
|
|
|
return false; |
389
|
|
|
} |
390
|
|
|
|
391
|
|
|
$orientation = (int)@$exif['Orientation']; |
392
|
|
|
|
393
|
|
|
if ($orientation < 2 || $orientation > 8) { |
394
|
|
|
return false; |
395
|
|
|
} |
396
|
|
|
|
397
|
|
|
switch ($orientation) { |
398
|
|
|
case 8: |
399
|
|
|
$image->rotate(-90); |
400
|
|
|
break; |
401
|
|
|
case 3: |
402
|
|
|
$image->rotate(180); |
403
|
|
|
break; |
404
|
|
|
case 6: |
405
|
|
|
$image->rotate(90); |
406
|
|
|
break; |
407
|
|
|
} |
408
|
|
|
|
409
|
|
|
return true; |
410
|
|
|
} |
411
|
|
|
|
412
|
|
|
/** |
413
|
|
|
* @param $fileName |
414
|
|
|
* @param $version |
415
|
|
|
* @param $options |
416
|
|
|
* @return bool|\imagine\Image\ManipulatorInterface |
417
|
|
|
*/ |
418
|
|
|
protected function createScaledImage($fileName, $version, $options) |
419
|
|
|
{ |
420
|
|
|
$success = false; |
421
|
|
|
$filePath = $this->getFilePath($fileName); |
422
|
|
|
$image = Image::getImagine()->open($filePath); |
423
|
|
|
|
424
|
|
|
if ($this->getOption('correct_exif_rotation')) { |
425
|
|
|
$this->correctExifRotation($image, $filePath); |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
$maxWidth = $imageWidth = $image->getSize()->getWidth(); |
429
|
|
|
$maxHeight = $imageHeight = $image->getSize()->getHeight(); |
430
|
|
|
|
431
|
|
|
if (!empty($options['max_width'])) { |
432
|
|
|
$maxWidth = $options['max_width']; |
433
|
|
|
} |
434
|
|
|
|
435
|
|
|
if (!empty($options['max_height'])) { |
436
|
|
|
$maxHeight = $options['max_height']; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
$scale = min($maxWidth / $imageWidth, $maxHeight / $imageHeight); |
440
|
|
|
|
441
|
|
|
if ($scale > 0 && $scale <= 1) { |
442
|
|
|
if (empty($options['crop'])) { |
443
|
|
|
$newWidth = round($imageWidth * $scale); |
444
|
|
|
$newHeight = round($imageHeight * $scale); |
445
|
|
|
$newFileName = substr($fileName, 0, -(strlen($this->_media->file->extension) + 1)) . |
446
|
|
|
'-' . $newWidth . |
447
|
|
|
'x' . $newHeight . |
448
|
|
|
'.' . $this->_media->file->extension; |
449
|
|
|
$newFilePath = $this->getFilePath($newFileName); |
450
|
|
|
$success = $image->thumbnail(new Box($newWidth, $newHeight)) |
451
|
|
|
->save($newFilePath); |
452
|
|
View Code Duplication |
if ($success) { |
|
|
|
|
453
|
|
|
$this->_meta['versions'][$version] = [ |
454
|
|
|
'url' => $newFileName, |
455
|
|
|
'width' => $newWidth, |
456
|
|
|
'height' => $newHeight, |
457
|
|
|
]; |
458
|
|
|
} |
459
|
|
|
} else { |
460
|
|
|
if (($imageWidth / $imageHeight) >= ($maxWidth / $maxHeight)) { |
461
|
|
|
$newWidth = round($imageWidth / ($imageHeight / $maxHeight)); |
462
|
|
|
$newHeight = $maxHeight; |
463
|
|
|
} else { |
464
|
|
|
$newWidth = $maxWidth; |
465
|
|
|
$newHeight = round($imageHeight / ($imageWidth / $maxWidth)); |
466
|
|
|
} |
467
|
|
|
$pointX = abs(round(($newWidth - $maxWidth) / 2)); |
468
|
|
|
$pointY = abs(round(($newHeight - $maxHeight) / 2)); |
469
|
|
|
$newFileName = substr($fileName, 0, -(strlen($this->_media->file->extension) + 1)) . |
470
|
|
|
'-' . $maxWidth . |
471
|
|
|
'x' . $maxHeight . |
472
|
|
|
'.' . $this->_media->file->extension; |
473
|
|
|
$newFilePath = $this->getFilePath($newFileName); |
474
|
|
|
$success = $image->thumbnail(new Box($newWidth, $newHeight), ManipulatorInterface::THUMBNAIL_OUTBOUND) |
475
|
|
|
->crop(new Point($pointX, $pointY), new Box($maxWidth, $maxHeight)) |
476
|
|
|
->save($newFilePath); |
477
|
|
View Code Duplication |
if ($success) { |
|
|
|
|
478
|
|
|
$this->_meta['versions'][$version] = [ |
479
|
|
|
'url' => $newFileName, |
480
|
|
|
'width' => $maxWidth, |
481
|
|
|
'height' => $maxWidth, |
482
|
|
|
]; |
483
|
|
|
} |
484
|
|
|
} |
485
|
|
|
} |
486
|
|
|
|
487
|
|
|
return $success; |
488
|
|
|
} |
489
|
|
|
|
490
|
|
|
/** |
491
|
|
|
* Handle image file. |
492
|
|
|
* |
493
|
|
|
* @param string $fileName |
494
|
|
|
*/ |
495
|
|
|
protected function handleImageFile($fileName) |
496
|
|
|
{ |
497
|
|
|
if ($versions = $this->getOption('versions')) { |
498
|
|
|
foreach ($versions as $version => $options) { |
|
|
|
|
499
|
|
|
$this->createScaledImage($fileName, $version, $options); |
500
|
|
|
} |
501
|
|
|
} |
502
|
|
|
} |
503
|
|
|
|
504
|
|
|
/** |
505
|
|
|
* Set icon url. |
506
|
|
|
* |
507
|
|
|
* @param string $fileName |
508
|
|
|
* @return string |
509
|
|
|
*/ |
510
|
|
|
protected function setIconUrl($fileName) |
511
|
|
|
{ |
512
|
|
|
foreach ($this->fileTypes as $name => $type) { |
513
|
|
|
if (preg_match($type['extensions'], $fileName)) { |
514
|
|
|
if ($name === 'image') { |
515
|
|
|
return $this->_meta['versions']['thumbnail']['url']; |
516
|
|
|
} |
517
|
|
|
|
518
|
|
|
return $type['mime_icon']; |
519
|
|
|
} |
520
|
|
|
} |
521
|
|
|
|
522
|
|
|
return 'img/mime/default.png'; |
523
|
|
|
} |
524
|
|
|
|
525
|
|
|
/** |
526
|
|
|
* Handle uploaded file. If the uploaded file is valid image type, the file will be resize or crop |
527
|
|
|
* based on versions in options. |
528
|
|
|
* |
529
|
|
|
* @param UploadedFile $file |
530
|
|
|
*/ |
531
|
|
|
protected function handleFileUpload($file) |
532
|
|
|
{ |
533
|
|
|
$this->_meta['filename'] = $this->getFileName($file); |
534
|
|
|
$this->_meta['file_size'] = $file->size; |
535
|
|
|
$uploadDir = $this->getUploadPath(); |
536
|
|
|
$uploadPath = $this->getUploadPath($this->_meta['filename']); |
537
|
|
|
|
538
|
|
|
if (!is_dir($uploadDir)) { |
539
|
|
|
FileHelper::createDirectory($uploadDir, $this->getOption('mkdir_mode')); |
|
|
|
|
540
|
|
|
} |
541
|
|
|
|
542
|
|
|
if ($file->saveAs($uploadPath)) { |
543
|
|
|
$this->_meta['versions']['full']['url'] = $this->getUserPath() |
544
|
|
|
. $this->getYearMonthPath() |
545
|
|
|
. $this->_meta['filename']; |
546
|
|
|
|
547
|
|
|
if (preg_match($this->fileTypes['image']['extensions'], $this->_meta['filename'])) { |
548
|
|
|
$image = Image::getImagine()->open($this->getFilePath($this->_meta['versions']['full']['url'])); |
549
|
|
|
$this->handleImageFile($this->_meta['versions']['full']['url']); |
550
|
|
|
$this->_meta['versions']['full']['width'] = $image->getSize()->getWidth(); |
551
|
|
|
$this->_meta['versions']['full']['height'] = $image->getSize()->getHeight(); |
552
|
|
|
} |
553
|
|
|
|
554
|
|
|
$this->_meta['icon_url'] = $this->setIconUrl($this->_meta['filename']); |
555
|
|
|
} |
556
|
|
|
} |
557
|
|
|
|
558
|
|
|
/** |
559
|
|
|
* @return \yii\data\Pagination |
560
|
|
|
*/ |
561
|
|
|
protected function getPages() |
562
|
|
|
{ |
563
|
|
|
$query = Media::find()->orderBy(['id' => SORT_DESC]); |
564
|
|
|
$pages = new Pagination([ |
565
|
|
|
'totalCount' => $query->count(), |
566
|
|
|
'pageSize' => $this->getOption('files_per_page'), |
567
|
|
|
]); |
568
|
|
|
|
569
|
|
|
return $pages; |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
/** |
573
|
|
|
* @param $pages Pagination |
574
|
|
|
* @return array |
575
|
|
|
*/ |
576
|
|
|
protected function getPaging($pages) |
577
|
|
|
{ |
578
|
|
|
$currentPage = $pages->getPage(); |
579
|
|
|
$perPage = $pages->getPageSize(); |
580
|
|
|
$result = [ |
581
|
|
|
'next_url' => '', |
582
|
|
|
'current_page' => $currentPage, |
583
|
|
|
'per_page' => $perPage, |
584
|
|
|
]; |
585
|
|
|
$pageCount = $pages->getPageCount(); |
586
|
|
|
|
587
|
|
|
if ($currentPage + 1 < $pageCount) { |
588
|
|
|
if (($page = $currentPage + 1) >= $pageCount - 1) { |
589
|
|
|
$page = $pageCount - 1; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
return [ |
593
|
|
|
'next_url' => Url::to(['get-json', 'page' => $page + 1, 'per-page' => $pages->getPageSize()]), |
594
|
|
|
'current_page' => $page, |
595
|
|
|
'per_page' => $perPage, |
596
|
|
|
]; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
return $result; |
600
|
|
|
} |
601
|
|
|
|
602
|
|
|
/** |
603
|
|
|
* Set options of upload Handler. |
604
|
|
|
* |
605
|
|
|
* @param array $options |
606
|
|
|
*/ |
607
|
|
|
public function setOptions($options = []) |
608
|
|
|
{ |
609
|
|
|
$this->options = [ |
610
|
|
|
'script_url' => Yii::$app->request->absoluteUrl, |
611
|
|
|
'upload_dir' => Yii::getAlias('@public/uploads/'), |
612
|
|
|
'upload_url' => Media::getUploadUrl(), |
613
|
|
|
'user_dirs' => true, |
614
|
|
|
'year_month_dirs' => true, |
615
|
|
|
'mkdir_mode' => 0755, |
616
|
|
|
'param_name' => 'files', |
617
|
|
|
'access_control_allow_origin' => '*', |
618
|
|
|
'access_control_allow_credentials' => false, |
619
|
|
|
'correct_exif_rotation' => true, |
620
|
|
|
'pagination_route' => '/media/get-json', |
621
|
|
|
'access_control_allow_methods' => [ |
622
|
|
|
'OPTIONS', |
623
|
|
|
'HEAD', |
624
|
|
|
'GET', |
625
|
|
|
'POST', |
626
|
|
|
'PUT', |
627
|
|
|
'PATCH', |
628
|
|
|
'DELETE', |
629
|
|
|
], |
630
|
|
|
'access_control_allow_headers' => [ |
631
|
|
|
'Content-Type', |
632
|
|
|
'Content-Range', |
633
|
|
|
'Content-Disposition', |
634
|
|
|
], |
635
|
|
|
'versions' => [ |
636
|
|
|
'large' => [ |
637
|
|
|
'max_width' => 1024, |
638
|
|
|
'max_height' => 1024, |
639
|
|
|
], |
640
|
|
|
'medium' => [ |
641
|
|
|
'max_width' => 300, |
642
|
|
|
'max_height' => 300, |
643
|
|
|
], |
644
|
|
|
'thumbnail' => [ |
645
|
|
|
'max_width' => 150, |
646
|
|
|
'max_height' => 150, |
647
|
|
|
'crop' => 1, |
648
|
|
|
], |
649
|
|
|
], |
650
|
|
|
'files_per_page' => 100, |
651
|
|
|
'print_response' => true, |
652
|
|
|
]; |
653
|
|
|
|
654
|
|
|
if ($options) { |
|
|
|
|
655
|
|
|
$this->options = ArrayHelper::merge($this->options, $options); |
656
|
|
|
} |
657
|
|
|
} |
658
|
|
|
|
659
|
|
|
/** |
660
|
|
|
* Get all of MediaUploadHandler Options. |
661
|
|
|
*/ |
662
|
|
|
public function getOptions() |
663
|
|
|
{ |
664
|
|
|
return $this->options; |
665
|
|
|
} |
666
|
|
|
|
667
|
|
|
/** |
668
|
|
|
* Get single option of MediaUploadHandler. |
669
|
|
|
* Return string or array if option exist, or return null if not exist. |
670
|
|
|
* |
671
|
|
|
* @param string $id |
672
|
|
|
* @return string|array|null |
673
|
|
|
*/ |
674
|
|
|
public function getOption($id) |
675
|
|
|
{ |
676
|
|
|
if (isset($this->options[$id])) { |
677
|
|
|
return $this->options[$id]; |
678
|
|
|
} |
679
|
|
|
|
680
|
|
|
return null; |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Generate response based on Media primary key. |
685
|
|
|
* |
686
|
|
|
* @param Media $media |
687
|
|
|
* @return array |
688
|
|
|
*/ |
689
|
|
|
public function generateResponse($media) |
690
|
|
|
{ |
691
|
|
|
$metadata = $media->getMeta('metadata'); |
692
|
|
|
$response = ArrayHelper::merge(ArrayHelper::toArray($media), $metadata); |
693
|
|
|
$response['date_formatted'] = Yii::$app->formatter->asDatetime($media->date); |
694
|
|
|
$response['readable_size'] = Yii::$app->formatter->asShortSize($metadata['file_size']); |
695
|
|
|
$response['delete_url'] = Url::to(['/media/ajax-delete', 'id' => $media->id, 'delete' => '1']); |
696
|
|
|
$response['update_url'] = Url::to(['/media/update', 'id' => $media->id]); |
697
|
|
|
$response['view_url'] = $media->getUrl(); |
698
|
|
|
|
699
|
|
|
if (preg_match('/^image\//', $media->mime_type)) { |
700
|
|
|
$response['type'] = 'image'; |
701
|
|
|
$response['icon_url'] = $this->getOption('upload_url') . $metadata['icon_url']; |
702
|
|
|
} else { |
703
|
|
|
$response['icon_url'] = Yii::getAlias('@web') . '/' . $metadata['icon_url']; |
704
|
|
|
if (preg_match('/^video\//', $media->mime_type)) { |
705
|
|
|
$response['type'] = 'video'; |
706
|
|
|
} elseif (preg_match('/^audio\//', $media->mime_type)) { |
707
|
|
|
$response['type'] = 'audio'; |
708
|
|
|
} else { |
709
|
|
|
$response['type'] = 'file'; |
710
|
|
|
} |
711
|
|
|
} |
712
|
|
|
|
713
|
|
|
return $response; |
714
|
|
|
} |
715
|
|
|
|
716
|
|
|
/** |
717
|
|
|
* Set response. |
718
|
|
|
* |
719
|
|
|
* @param array $response |
720
|
|
|
*/ |
721
|
|
|
public function setResponse($response) |
722
|
|
|
{ |
723
|
|
|
$this->response = $response; |
724
|
|
|
} |
725
|
|
|
|
726
|
|
|
|
727
|
|
|
/** |
728
|
|
|
* Generate response in the form of a string of json. |
729
|
|
|
* |
730
|
|
|
* @param bool $printResponse |
731
|
|
|
* @return array |
732
|
|
|
*/ |
733
|
|
|
public function getResponse($printResponse = self::PRINT_RESPONSE) |
734
|
|
|
{ |
735
|
|
|
if ($printResponse) { |
736
|
|
|
$this->head(); |
737
|
|
|
$content = Json::encode($this->response); |
738
|
|
|
$redirect = stripslashes(Yii::$app->request->getQueryParam('redirect')); |
739
|
|
|
if ($redirect) { |
740
|
|
|
$this->setHeader('Location', sprintf($redirect, rawurlencode($content))); |
741
|
|
|
|
742
|
|
|
return null; |
743
|
|
|
} |
744
|
|
|
echo $content; |
745
|
|
|
} |
746
|
|
|
|
747
|
|
|
return $this->response; |
748
|
|
|
} |
749
|
|
|
|
750
|
|
|
/** |
751
|
|
|
* Set response header. |
752
|
|
|
*/ |
753
|
|
|
public function head() |
754
|
|
|
{ |
755
|
|
|
$this->setHeader('Pragma', 'no-cache'); |
756
|
|
|
$this->setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); |
757
|
|
|
$this->setHeader('Content-Disposition', 'inline; filename="files.json"'); |
758
|
|
|
// Prevent Internet Explorer from MIME-sniffing the content-type: |
759
|
|
|
$this->setHeader('X-Content-Type-Options', 'nosniff'); |
760
|
|
|
if ($this->options['access_control_allow_origin']) { |
761
|
|
|
$this->sendAccessControlHeaders(); |
762
|
|
|
} |
763
|
|
|
$this->sendContentTypeHeader(); |
764
|
|
|
} |
765
|
|
|
|
766
|
|
|
/** |
767
|
|
|
* Get media files. |
768
|
|
|
* |
769
|
|
|
* @param int $id |
770
|
|
|
* @param bool $printResponse |
771
|
|
|
* @return array |
772
|
|
|
*/ |
773
|
|
|
public function get($id = null, $printResponse = self::PRINT_RESPONSE) |
774
|
|
|
{ |
775
|
|
|
$content = []; |
776
|
|
|
|
777
|
|
|
if ($id && $media = $this->findMedia($id)) { |
|
|
|
|
778
|
|
|
$response = [ |
779
|
|
|
$this->getSingularParamName() => $this->generateResponse($media), |
|
|
|
|
780
|
|
|
]; |
781
|
|
|
} else { |
782
|
|
|
$query = Media::find()->orderBy(['id' => SORT_DESC]); |
783
|
|
|
$pages = $this->getPages(); |
784
|
|
|
|
785
|
|
|
$query->andFilterWhere(['like', 'post_id', Yii::$app->request->get('post')]) |
786
|
|
|
->andFilterWhere(['like', 'mime_type', Yii::$app->request->get('type')]) |
787
|
|
|
->andFilterWhere(['like', 'title', Yii::$app->request->get('keyword')]) |
788
|
|
|
->orFilterWhere(['like', 'content', Yii::$app->request->get('keyword')]); |
789
|
|
|
|
790
|
|
|
if ($models = $query->offset($pages->offset)->limit($pages->limit)->all()) { |
791
|
|
|
foreach ($models as $media) { |
792
|
|
|
/* @var $media Media */ |
793
|
|
|
$content[] = $this->generateResponse($media); |
794
|
|
|
} |
795
|
|
|
} |
796
|
|
|
$response = [ |
797
|
|
|
$this->getOption('param_name') => $content, |
798
|
|
|
'paging' => $this->getPaging($pages), |
799
|
|
|
]; |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
$this->setResponse($response); |
803
|
|
|
|
804
|
|
|
return $this->getResponse($printResponse); |
805
|
|
|
} |
806
|
|
|
|
807
|
|
|
/** |
808
|
|
|
* Upload file to server. |
809
|
|
|
* |
810
|
|
|
* @param bool $printResponse |
811
|
|
|
* @return array |
812
|
|
|
*/ |
813
|
|
|
public function post($printResponse = self::PRINT_RESPONSE) |
814
|
|
|
{ |
815
|
|
|
if (Yii::$app->request->get('delete') && $id = Yii::$app->request->get('id')) { |
816
|
|
|
return $this->delete($id, $printResponse); |
817
|
|
|
} |
818
|
|
|
|
819
|
|
|
$response = []; |
820
|
|
|
$this->_media = new Media(); |
821
|
|
|
$this->_media->file = UploadedFile::getInstance($this->_media, 'file'); |
822
|
|
|
|
823
|
|
|
if ($this->_media->file !== null && $this->_media->validate(['file'])) { |
824
|
|
|
if ($postId = Yii::$app->request->get('post')) { |
825
|
|
|
$post = $this->findPost($postId); |
826
|
|
|
$this->_media->post_id = $post->id; |
827
|
|
|
} |
828
|
|
|
|
829
|
|
|
$this->_media->title = $this->_media->file->baseName; |
830
|
|
|
$this->_media->mime_type = $this->_media->file->type; |
831
|
|
|
$this->handleFileUpload($this->_media->file); |
832
|
|
|
|
833
|
|
|
if ($this->_media->save(false)) { |
834
|
|
|
if ($this->_media->setMeta('metadata', $this->_meta)) { |
835
|
|
|
$response = $this->generateResponse($this->_media); |
836
|
|
|
} |
837
|
|
|
} |
838
|
|
|
} else { |
839
|
|
|
$response = [ |
840
|
|
|
'error' => $this->_media->getFirstError('file'), |
841
|
|
|
'filename' => isset($this->_media->file->name) ? $this->_media->file->name : null, |
842
|
|
|
'file_size' => isset($this->_media->file->size) ? $this->_media->file->size : null, |
843
|
|
|
]; |
844
|
|
|
} |
845
|
|
|
|
846
|
|
|
$this->setResponse([ |
847
|
|
|
$this->getOption('param_name') => [$response], |
848
|
|
|
]); |
849
|
|
|
|
850
|
|
|
return $this->getResponse($printResponse); |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
/** |
854
|
|
|
* Delete files based on media primary key |
855
|
|
|
* |
856
|
|
|
* @param int $id Primary key of Media |
857
|
|
|
* @param bool $printResponse |
858
|
|
|
* @return array |
859
|
|
|
* @throws \Exception |
860
|
|
|
*/ |
861
|
|
|
public function delete($id, $printResponse = self::PRINT_RESPONSE) |
862
|
|
|
{ |
863
|
|
|
$success = true; |
864
|
|
|
$response = []; |
865
|
|
|
$media = $this->findMedia($id); |
866
|
|
|
$metadata = $media->getMeta('metadata'); |
|
|
|
|
867
|
|
|
|
868
|
|
|
if ($media->delete()) { |
|
|
|
|
869
|
|
|
foreach ($metadata['versions'] as $version) { |
870
|
|
|
$filePath = $this->getFilePath($version['url']); |
871
|
|
|
$success = is_file($filePath) && unlink($filePath); |
872
|
|
|
} |
873
|
|
|
$response[$metadata['filename']] = $success; |
874
|
|
|
} |
875
|
|
|
|
876
|
|
|
$this->setResponse($response); |
877
|
|
|
|
878
|
|
|
return $this->getResponse($printResponse); |
879
|
|
|
} |
880
|
|
|
} |
881
|
|
|
|
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.