1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace app\models\types; |
4
|
|
|
|
5
|
|
|
use Yii; |
6
|
|
|
use Mhor\MediaInfo\MediaInfo; |
7
|
|
|
use yii\helpers\FileHelper; |
8
|
|
|
use yii\helpers\Url; |
9
|
|
|
use yii\web\UploadedFile; |
10
|
|
|
use app\models\ContentType; |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* This is the model class for Media content type. |
14
|
|
|
* |
15
|
|
|
* @property \FileInstance $upload |
16
|
|
|
* @property int $size |
17
|
|
|
*/ |
18
|
|
|
abstract class Media extends ContentType |
19
|
|
|
{ |
20
|
|
|
const BASE_PATH = 'uploads/'; |
21
|
|
|
const TYPE_PATH = 'tmp/'; |
22
|
|
|
const BASE_URI = '@web/'; |
23
|
|
|
public $usable = false; |
24
|
|
|
public $canPreview = true; |
25
|
|
|
|
26
|
|
|
public $upload; |
27
|
|
|
public $_size; |
28
|
|
|
public $_filename; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* {@inheritdoc} |
32
|
|
|
*/ |
33
|
|
|
public function upload($fileInstance) |
34
|
|
|
{ |
35
|
|
|
if ($fileInstance === null) { |
36
|
|
|
$this->addError('load', Yii::t('app', 'There\'s no file')); |
37
|
|
|
|
38
|
|
|
return false; |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
$this->upload = $fileInstance; |
42
|
|
|
|
43
|
|
|
$filename = $this->upload->baseName.'.'.$this->upload->extension; |
44
|
|
|
$tmpFilepath = tempnam(sys_get_temp_dir(), 'LCDS_'); |
45
|
|
|
|
46
|
|
|
if ($this->upload->saveAs($tmpFilepath)) { |
47
|
|
View Code Duplication |
if (static::validateFile($tmpFilepath)) { |
|
|
|
|
48
|
|
|
return ['filename' => $filename, 'tmppath' => $tmpFilepath, 'duration' => static::getDuration($tmpFilepath)]; |
49
|
|
|
} |
50
|
|
|
$this->addError('load', Yii::t('app', 'Invalid file')); |
51
|
|
|
unlink($tmpFilepath); |
52
|
|
|
|
53
|
|
|
return false; |
54
|
|
|
} |
55
|
|
|
$this->addError('load', Yii::t('app', 'Cannot save file')); |
56
|
|
|
|
57
|
|
|
return false; |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Custom file validation based on mediainfo description. |
62
|
|
|
* |
63
|
|
|
* @param string $realFilepath filesystem path |
64
|
|
|
* |
65
|
|
|
* @return bool is file valid |
66
|
|
|
*/ |
67
|
|
|
public static function validateFile($realFilepath) |
68
|
|
|
{ |
69
|
|
|
return false; |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
/** |
73
|
|
|
* Validate an url based on PHP filter_var. |
74
|
|
|
* |
75
|
|
|
* @param string $url |
76
|
|
|
* |
77
|
|
|
* @return bool valid |
78
|
|
|
*/ |
79
|
|
|
public static function validateUrl($url) |
80
|
|
|
{ |
81
|
|
|
return $url && filter_var($url, FILTER_VALIDATE_URL); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* {@inheritdoc} |
86
|
|
|
*/ |
87
|
|
|
public function sideload($url) |
88
|
|
|
{ |
89
|
|
|
if (!self::validateUrl($url)) { |
90
|
|
|
$this->addError('load', Yii::t('app', 'Empty or incorrect URL')); |
91
|
|
|
|
92
|
|
|
return false; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
$curl = curl_init($url); |
96
|
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); |
97
|
|
|
if (Yii::$app->params['proxy']) { |
98
|
|
|
curl_setopt($curl, CURLOPT_PROXY, Yii::$app->params['proxy']); |
99
|
|
|
} |
100
|
|
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); |
101
|
|
|
curl_setopt($curl, CURLOPT_HEADERFUNCTION, [&$this, 'readHeaderFilename']); |
102
|
|
|
$fileContent = curl_exec($curl); |
103
|
|
|
$error = curl_error($curl); |
104
|
|
|
curl_close($curl); |
105
|
|
|
|
106
|
|
|
if ($error) { |
107
|
|
|
$this->addError('load', Yii::t('app', $error)); |
108
|
|
|
|
109
|
|
|
return false; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
$filename = $this->_filename; |
113
|
|
|
if (!$filename) { |
114
|
|
|
$urlSplit = explode('/', $url); |
115
|
|
|
$filename = $urlSplit[count($urlSplit) - 1]; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$tmpFilepath = tempnam(sys_get_temp_dir(), 'LCDS_'); |
119
|
|
|
|
120
|
|
|
$file = fopen($tmpFilepath, 'w+'); |
121
|
|
|
fwrite($file, $fileContent); |
122
|
|
|
fclose($file); |
123
|
|
|
|
124
|
|
|
$fileInstance = new UploadedFile(); |
125
|
|
|
$fileInstance->name = $filename; |
126
|
|
|
$fileInstance->tempName = $tmpFilepath; |
127
|
|
|
$fileInstance->type = FileHelper::getMimeType($fileInstance->tempName); |
128
|
|
|
$fileInstance->size = $this->_size; |
129
|
|
|
$this->upload = $fileInstance; |
130
|
|
|
|
131
|
|
View Code Duplication |
if (static::validateFile($fileInstance->tempName)) { |
|
|
|
|
132
|
|
|
return ['filename' => $filename, 'tmppath' => $tmpFilepath, 'duration' => static::getDuration($tmpFilepath)]; |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$this->addError('load', Yii::t('app', 'Invalid file')); |
136
|
|
|
unlink($fileInstance->tempName); |
137
|
|
|
|
138
|
|
|
return false; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* Header parsing method used to get size & filename. |
143
|
|
|
* |
144
|
|
|
* @param mixed $curl curl handler |
145
|
|
|
* @param string $header |
146
|
|
|
* |
147
|
|
|
* @return int header length |
148
|
|
|
*/ |
149
|
|
|
public function readHeaderFilename($curl, $header) |
150
|
|
|
{ |
151
|
|
|
if (strpos($header, 'Content-Length:') === 0 && preg_match('/(\d+)/', $header, $matches)) { |
152
|
|
|
$this->_size = intval(trim($matches[1])); |
153
|
|
|
} elseif (strpos($header, 'Content-Disposition:') === 0 && preg_match('/filename=(.*)$/', $header, $matches)) { |
154
|
|
|
$this->_filename = trim(str_replace('"', '', $matches[1])); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
return strlen($header); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* {@inheritdoc} |
162
|
|
|
*/ |
163
|
|
|
public function getLoadError() |
164
|
|
|
{ |
165
|
|
|
$errors = $this->getErrors(); |
166
|
|
|
if (array_key_exists('load', $errors) && count($errors['load'])) { |
167
|
|
|
return implode(' - ', $errors['load']); |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return Yii::t('app', 'Incorrect file'); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Get storage path from web root. |
175
|
|
|
* |
176
|
|
|
* @return string path |
177
|
|
|
*/ |
178
|
|
|
public static function getPath() |
179
|
|
|
{ |
180
|
|
|
return self::BASE_PATH.static::TYPE_PATH; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* Get Yii aliased storage path. |
185
|
|
|
* |
186
|
|
|
* @return string path |
187
|
|
|
*/ |
188
|
|
|
public static function getWebPath() |
189
|
|
|
{ |
190
|
|
|
return self::BASE_URI.self::getPath(); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* Get filesystem storage path. |
195
|
|
|
* |
196
|
|
|
* @return string path |
197
|
|
|
*/ |
198
|
|
|
public static function getRealPath() |
199
|
|
|
{ |
200
|
|
|
return \Yii::getAlias('@app/').'web/'.self::getPath(); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Try to get media info for this media. |
205
|
|
|
* |
206
|
|
|
* @return \Mhor\MediaInfo\Container\MediaInfoContainer|null media info |
207
|
|
|
*/ |
208
|
|
|
protected static function getMediaInfo($realFilepath) |
209
|
|
|
{ |
210
|
|
|
try { |
211
|
|
|
return (new MediaInfo())->getInfo($realFilepath); |
212
|
|
|
} catch (\RuntimeException $e) { |
213
|
|
|
return; |
214
|
|
|
} |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
/** |
218
|
|
|
* Use mediainfo to parse media duration. |
219
|
|
|
* |
220
|
|
|
* @param string $realFilepath |
221
|
|
|
* |
222
|
|
|
* @return int|null media duration |
223
|
|
|
*/ |
224
|
|
|
public static function getDuration($realFilepath) |
225
|
|
|
{ |
226
|
|
|
$mediainfo = static::getMediaInfo($realFilepath); |
227
|
|
|
if ($mediainfo) { |
228
|
|
|
$general = $mediainfo->getGeneral(); |
229
|
|
|
if ($general && $general->get('duration')) { |
230
|
|
|
return ceil($general->get('duration')->getMilliseconds() / 1000); |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* {@inheritdoc} |
237
|
|
|
*/ |
238
|
|
|
public function transformDataBeforeSave($insert, $data) |
239
|
|
|
{ |
240
|
|
|
if ($insert) { |
241
|
|
|
list($path, $filename) = explode('§', $data); |
242
|
|
|
$tmppath = FileHelper::normalizePath($path); |
243
|
|
|
|
244
|
|
|
$parts = explode(DIRECTORY_SEPARATOR, $tmppath); |
245
|
|
|
array_pop($parts); // Remove filename |
246
|
|
|
if (implode(DIRECTORY_SEPARATOR, $parts) == sys_get_temp_dir() && strpos(DIRECTORY_SEPARATOR, $filename) === false && file_exists($tmppath)) { |
247
|
|
|
ini_set('mbstring.substitute_character', 'none'); |
248
|
|
|
$filename = mb_convert_encoding($filename, 'ASCII'); |
249
|
|
|
$filename = static::getUniqFilename(static::getRealPath(), $filename); |
250
|
|
|
$data = static::getWebPath().$filename; |
251
|
|
|
$realFilepath = static::getRealPath().$filename; |
252
|
|
|
|
253
|
|
|
if (rename($tmppath, $realFilepath)) { |
254
|
|
|
return $data; |
255
|
|
|
} |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
return null; |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
return $data; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* Create unique filename by checking for existence and appending to filename. |
266
|
|
|
* |
267
|
|
|
* @param string $path filepath |
268
|
|
|
* @param string $filename |
269
|
|
|
* |
270
|
|
|
* @return string unique filename |
271
|
|
|
*/ |
272
|
|
|
protected static function getUniqFilename($path, $filename) |
273
|
|
|
{ |
274
|
|
|
if (!file_exists($path.$filename)) { |
275
|
|
|
return $filename; |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
$parts = explode('.', $filename); |
279
|
|
|
if (count($parts) > 1) { |
280
|
|
|
$ext = array_pop($parts); |
281
|
|
|
} else { |
282
|
|
|
$ext = null; |
283
|
|
|
} |
284
|
|
|
$name = implode('.', $parts); |
285
|
|
|
|
286
|
|
|
$i = 1; |
287
|
|
|
$filename = $name.$i.($ext ? '.'.$ext : ''); |
288
|
|
|
while (file_exists($path.$filename)) { |
289
|
|
|
$filename = $name.++$i.($ext ? '.'.$ext : ''); |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
return $filename; |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
/** |
296
|
|
|
* {@inheritdoc} |
297
|
|
|
*/ |
298
|
|
|
public function processData($data) |
299
|
|
|
{ |
300
|
|
|
return Url::to($data); |
301
|
|
|
} |
302
|
|
|
} |
303
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.