1
|
|
|
<?php declare(strict_types=1);
|
2
|
|
|
|
3
|
|
|
namespace XoopsModules\Songlist;
|
4
|
|
|
|
5
|
|
|
use function mb_substr;
|
6
|
|
|
|
7
|
|
|
use const DS;
|
8
|
|
|
|
9
|
|
|
/**
|
10
|
|
|
* Class SonglistMediaUploader
|
11
|
|
|
*/
|
12
|
|
|
class Uploader
|
13
|
|
|
{
|
14
|
|
|
public $mediaName;
|
15
|
|
|
public $mediaType;
|
16
|
|
|
public $mediaSize;
|
17
|
|
|
public $mediaTmpName;
|
18
|
|
|
public $mediaError;
|
19
|
|
|
public $uploadDir = '';
|
20
|
|
|
public $allowedMimeTypes = [];
|
21
|
|
|
public $allowedExtensions = [];
|
22
|
|
|
public $maxFileSize = 3299999999;
|
23
|
|
|
public $maxWidth;
|
24
|
|
|
public $maxHeight;
|
25
|
|
|
public $targetFileName;
|
26
|
|
|
public $prefix;
|
27
|
|
|
public $errors = [];
|
28
|
|
|
public $savedDestination;
|
29
|
|
|
public $savedFileName;
|
30
|
|
|
|
31
|
|
|
/**
|
32
|
|
|
* Constructor
|
33
|
|
|
*
|
34
|
|
|
* @param string $uploadDir
|
35
|
|
|
* @param array $allowedMimeTypes
|
36
|
|
|
* @param int $maxFileSize
|
37
|
|
|
* @param int $maxWidth
|
38
|
|
|
* @param int $maxHeight
|
39
|
|
|
* @param array $allowedExtensions
|
40
|
|
|
* @internal param int $cmodvalue
|
41
|
|
|
*/
|
42
|
|
|
public function __construct($uploadDir, $allowedMimeTypes, $maxFileSize, $maxWidth = null, $maxHeight = null, $allowedExtensions = null)
|
43
|
|
|
{
|
44
|
|
|
if (\is_array($allowedMimeTypes)) {
|
|
|
|
|
45
|
|
|
$this->allowedMimeTypes = &$allowedMimeTypes;
|
46
|
|
|
}
|
47
|
|
|
// $this->uploadDir = $uploadDir . (DS != mb_substr($uploadDir, mb_strlen($uploadDir) - 1, 1) ? DS : '');
|
48
|
|
|
// if (\is_dir($uploadDir)) {
|
49
|
|
|
// foreach (\explode(DS, $uploadDir) as $folder) {
|
50
|
|
|
// $path .= DS . $folder;
|
51
|
|
|
// if (!\mkdir($path, 0777) && !\is_dir($path)) {
|
52
|
|
|
// throw new \RuntimeException(\sprintf('Directory "%s" was not created', $path));
|
53
|
|
|
// }
|
54
|
|
|
// }
|
55
|
|
|
// }
|
56
|
|
|
$this->uploadDir = $uploadDir;
|
57
|
|
|
$this->maxFileSize = (int)$maxFileSize;
|
58
|
|
|
if (isset($maxWidth)) {
|
59
|
|
|
$this->maxWidth = (int)$maxWidth;
|
60
|
|
|
}
|
61
|
|
|
if (isset($maxHeight)) {
|
62
|
|
|
$this->maxHeight = (int)$maxHeight;
|
63
|
|
|
}
|
64
|
|
|
if (isset($allowedExtensions) && \is_array($allowedExtensions)) {
|
65
|
|
|
$this->allowedExtensions = &$allowedExtensions;
|
66
|
|
|
}
|
67
|
|
|
}
|
68
|
|
|
|
69
|
|
|
/**
|
70
|
|
|
* Fetch the uploaded file
|
71
|
|
|
*
|
72
|
|
|
* @param $index_name
|
73
|
|
|
* @param int $index Index of the file (if more than one uploaded under that name)
|
74
|
|
|
* @return bool
|
75
|
|
|
* @internal param string $media_name Name of the file field
|
76
|
|
|
*/
|
77
|
|
|
public function fetchMedia($index_name, $index = null): bool
|
78
|
|
|
{
|
79
|
|
|
if (!isset($_FILES[$index_name])) {
|
80
|
|
|
$this->setErrors('File not found');
|
81
|
|
|
|
82
|
|
|
return false;
|
83
|
|
|
}
|
84
|
|
|
|
85
|
|
|
if (\is_array($_FILES[$index_name]['name'][$index]) && isset($index)) {
|
86
|
|
|
$this->mediaName = $_FILES[$index_name]['name'][$index];
|
87
|
|
|
$this->mediaType = $_FILES[$index_name]['type'][$index];
|
88
|
|
|
$this->mediaSize = $_FILES[$index_name]['size'][$index];
|
89
|
|
|
$this->mediaTmpName = $_FILES[$index_name]['tmp_name'][$index];
|
90
|
|
|
$this->mediaError = !empty($_FILES[$index_name]['error'][$index]) ? $_FILES[$index_name]['errir'][$index] : 0;
|
91
|
|
|
} else {
|
92
|
|
|
$this->mediaName = $_FILES[$index_name]['name'];
|
93
|
|
|
$this->mediaType = $_FILES[$index_name]['type'];
|
94
|
|
|
$this->mediaSize = $_FILES[$index_name]['size'];
|
95
|
|
|
$this->mediaTmpName = $_FILES[$index_name]['tmp_name'];
|
96
|
|
|
$this->mediaError = !empty($_FILES[$index_name]['error']) ? $_FILES[$index_name]['error'] : 0;
|
97
|
|
|
}
|
98
|
|
|
$this->errors = [];
|
99
|
|
|
if ((int)$this->mediaSize < 0) {
|
100
|
|
|
$this->setErrors('Invalid File Size');
|
101
|
|
|
|
102
|
|
|
return false;
|
103
|
|
|
}
|
104
|
|
|
if ('' == $this->mediaName) {
|
105
|
|
|
$this->setErrors('Filename Is Empty');
|
106
|
|
|
|
107
|
|
|
return false;
|
108
|
|
|
}
|
109
|
|
|
if ('none' === $this->mediaTmpName || !\is_uploaded_file($this->mediaTmpName) || 0 == $this->mediaSize) {
|
110
|
|
|
$this->setErrors('No file uploaded');
|
111
|
|
|
|
112
|
|
|
return false;
|
113
|
|
|
}
|
114
|
|
|
if ($this->mediaError > 0) {
|
115
|
|
|
$this->setErrors('Error occurred: Error #' . $this->mediaError);
|
116
|
|
|
|
117
|
|
|
return false;
|
118
|
|
|
}
|
119
|
|
|
|
120
|
|
|
return true;
|
121
|
|
|
}
|
122
|
|
|
|
123
|
|
|
/**
|
124
|
|
|
* Set the target filename
|
125
|
|
|
*
|
126
|
|
|
* @param string $value
|
127
|
|
|
**/
|
128
|
|
|
public function setTargetFileName(string $value): void
|
129
|
|
|
{
|
130
|
|
|
$this->targetFileName = \trim($value);
|
131
|
|
|
}
|
132
|
|
|
|
133
|
|
|
/**
|
134
|
|
|
* Set the prefix
|
135
|
|
|
*
|
136
|
|
|
* @param string $value
|
137
|
|
|
**/
|
138
|
|
|
public function setPrefix($value): void
|
139
|
|
|
{
|
140
|
|
|
$this->prefix = \trim($value);
|
141
|
|
|
}
|
142
|
|
|
|
143
|
|
|
/**
|
144
|
|
|
* Get the uploaded filename
|
145
|
|
|
*
|
146
|
|
|
* @return string
|
147
|
|
|
**/
|
148
|
|
|
public function getMediaName(): string
|
149
|
|
|
{
|
150
|
|
|
return $this->mediaName;
|
151
|
|
|
}
|
152
|
|
|
|
153
|
|
|
/**
|
154
|
|
|
* Get the type of the uploaded file
|
155
|
|
|
*
|
156
|
|
|
* @return string
|
157
|
|
|
**/
|
158
|
|
|
public function getMediaType(): string
|
159
|
|
|
{
|
160
|
|
|
return $this->mediaType;
|
161
|
|
|
}
|
162
|
|
|
|
163
|
|
|
/**
|
164
|
|
|
* Get the size of the uploaded file
|
165
|
|
|
*
|
166
|
|
|
* @return int
|
167
|
|
|
**/
|
168
|
|
|
public function getMediaSize(): int
|
169
|
|
|
{
|
170
|
|
|
return $this->mediaSize;
|
171
|
|
|
}
|
172
|
|
|
|
173
|
|
|
/**
|
174
|
|
|
* Get the temporary name that the uploaded file was stored under
|
175
|
|
|
*
|
176
|
|
|
* @return string
|
177
|
|
|
**/
|
178
|
|
|
public function getMediaTmpName(): string
|
179
|
|
|
{
|
180
|
|
|
return $this->mediaTmpName;
|
181
|
|
|
}
|
182
|
|
|
|
183
|
|
|
/**
|
184
|
|
|
* Get the saved filename
|
185
|
|
|
*
|
186
|
|
|
* @return string
|
187
|
|
|
**/
|
188
|
|
|
public function getSavedFileName(): string
|
189
|
|
|
{
|
190
|
|
|
return $this->savedFileName;
|
191
|
|
|
}
|
192
|
|
|
|
193
|
|
|
/**
|
194
|
|
|
* Get the destination the file is saved to
|
195
|
|
|
*
|
196
|
|
|
* @return string
|
197
|
|
|
**/
|
198
|
|
|
public function getSavedDestination(): string
|
199
|
|
|
{
|
200
|
|
|
return $this->savedDestination;
|
201
|
|
|
}
|
202
|
|
|
|
203
|
|
|
/**
|
204
|
|
|
* Check the file and copy it to the destination
|
205
|
|
|
*
|
206
|
|
|
* @param int $chmod
|
207
|
|
|
* @return bool
|
208
|
|
|
*/
|
209
|
|
|
public function upload($chmod = 0644): bool
|
210
|
|
|
{
|
211
|
|
|
if ('' == $this->uploadDir) {
|
212
|
|
|
$this->setErrors('Upload directory not set');
|
213
|
|
|
|
214
|
|
|
return false;
|
215
|
|
|
}
|
216
|
|
|
if (!\is_dir($this->uploadDir)) {
|
217
|
|
|
$this->setErrors('Failed opening directory: ' . $this->uploadDir);
|
218
|
|
|
|
219
|
|
|
return false;
|
220
|
|
|
}
|
221
|
|
|
if (!\is_writable($this->uploadDir)) {
|
222
|
|
|
$this->setErrors('Failed opening directory with write permission: ' . $this->uploadDir);
|
223
|
|
|
|
224
|
|
|
return false;
|
225
|
|
|
}
|
226
|
|
|
if (!$this->checkMimeType()) {
|
227
|
|
|
$this->setErrors('MIME type not allowed: ' . $this->mediaType);
|
228
|
|
|
|
229
|
|
|
return false;
|
230
|
|
|
}
|
231
|
|
|
if (!$this->checkExtension()) {
|
232
|
|
|
$this->setErrors('Extension not allowed');
|
233
|
|
|
|
234
|
|
|
return false;
|
235
|
|
|
}
|
236
|
|
|
if (!$this->checkMaxFileSize()) {
|
237
|
|
|
$this->setErrors('File size too large: ' . $this->mediaSize);
|
238
|
|
|
}
|
239
|
|
|
if (!$this->checkMaxWidth()) {
|
240
|
|
|
$this->setErrors(\sprintf('File width must be smaller than %u', $this->maxWidth));
|
241
|
|
|
}
|
242
|
|
|
if (!$this->checkMaxHeight()) {
|
243
|
|
|
$this->setErrors(\sprintf('File height must be smaller than %u', $this->maxHeight));
|
244
|
|
|
}
|
245
|
|
|
if (\count($this->errors) > 0) {
|
246
|
|
|
return false;
|
247
|
|
|
}
|
248
|
|
|
if (!$this->_copyFile($chmod)) {
|
249
|
|
|
$this->setErrors('Failed uploading file: ' . $this->mediaName);
|
250
|
|
|
|
251
|
|
|
return false;
|
252
|
|
|
}
|
253
|
|
|
|
254
|
|
|
return true;
|
255
|
|
|
}
|
256
|
|
|
|
257
|
|
|
/**
|
258
|
|
|
* Copy the file to its destination
|
259
|
|
|
*
|
260
|
|
|
* @param $chmod
|
261
|
|
|
* @return bool
|
262
|
|
|
*/
|
263
|
|
|
public function _copyFile($chmod): bool
|
264
|
|
|
{
|
265
|
|
|
$matched = [];
|
266
|
|
|
if (!\preg_match('/\.([a-zA-Z0-9]+)$/', $this->mediaName, $matched)) {
|
267
|
|
|
return false;
|
268
|
|
|
}
|
269
|
|
|
if (isset($this->targetFileName)) {
|
270
|
|
|
$this->savedFileName = $this->targetFileName;
|
271
|
|
|
} elseif (isset($this->prefix)) {
|
272
|
|
|
$this->savedFileName = \uniqid($this->prefix, true) . '.' . \mb_strtolower($matched[1]);
|
273
|
|
|
} else {
|
274
|
|
|
$this->savedFileName = \mb_strtolower($this->mediaName);
|
275
|
|
|
}
|
276
|
|
|
$this->savedDestination = $this->uploadDir . '/' . $this->savedFileName;
|
277
|
|
|
if (!\move_uploaded_file($this->mediaTmpName, $this->savedDestination)) {
|
278
|
|
|
return false;
|
279
|
|
|
}
|
280
|
|
|
@\chmod($this->savedDestination, $chmod);
|
|
|
|
|
281
|
|
|
|
282
|
|
|
return true;
|
283
|
|
|
}
|
284
|
|
|
|
285
|
|
|
/**
|
286
|
|
|
* Is the file the right size?
|
287
|
|
|
*
|
288
|
|
|
* @return bool
|
289
|
|
|
**/
|
290
|
|
|
public function checkMaxFileSize(): bool
|
291
|
|
|
{
|
292
|
|
|
if ($this->mediaSize > $this->maxFileSize) {
|
293
|
|
|
return false;
|
294
|
|
|
}
|
295
|
|
|
|
296
|
|
|
return true;
|
297
|
|
|
}
|
298
|
|
|
|
299
|
|
|
/**
|
300
|
|
|
* Is the picture the right width?
|
301
|
|
|
*
|
302
|
|
|
* @return bool
|
303
|
|
|
**/
|
304
|
|
|
public function checkMaxWidth(): bool
|
305
|
|
|
{
|
306
|
|
|
if (!isset($this->maxWidth) || $this->maxWidth < 1) {
|
307
|
|
|
return true;
|
308
|
|
|
}
|
309
|
|
|
if (false !== $dimension = \getimagesize($this->mediaTmpName)) {
|
310
|
|
|
if ($dimension[0] > $this->maxWidth) {
|
311
|
|
|
return false;
|
312
|
|
|
}
|
313
|
|
|
} else {
|
314
|
|
|
\trigger_error(\sprintf('Failed fetching image size of %s, skipping max width check..', $this->mediaTmpName), \E_USER_WARNING);
|
315
|
|
|
}
|
316
|
|
|
|
317
|
|
|
return true;
|
318
|
|
|
}
|
319
|
|
|
|
320
|
|
|
/**
|
321
|
|
|
* Is the picture the right height?
|
322
|
|
|
*
|
323
|
|
|
* @return bool
|
324
|
|
|
**/
|
325
|
|
|
public function checkMaxHeight(): bool
|
326
|
|
|
{
|
327
|
|
|
if (!isset($this->maxHeight) || $this->maxHeight < 1) {
|
328
|
|
|
return true;
|
329
|
|
|
}
|
330
|
|
|
if (false !== $dimension = \getimagesize($this->mediaTmpName)) {
|
331
|
|
|
if ($dimension[1] > $this->maxHeight) {
|
332
|
|
|
return false;
|
333
|
|
|
}
|
334
|
|
|
} else {
|
335
|
|
|
\trigger_error(\sprintf('Failed fetching image size of %s, skipping max height check..', $this->mediaTmpName), \E_USER_WARNING);
|
336
|
|
|
}
|
337
|
|
|
|
338
|
|
|
return true;
|
339
|
|
|
}
|
340
|
|
|
|
341
|
|
|
/**
|
342
|
|
|
* Is the file the right Mime type
|
343
|
|
|
*
|
344
|
|
|
* (is there a right type of mime? ;-)
|
345
|
|
|
*
|
346
|
|
|
* @return bool
|
347
|
|
|
**/
|
348
|
|
|
public function checkMimeType(): bool
|
349
|
|
|
{
|
350
|
|
|
if (\count($this->allowedMimeTypes) > 0 && !\in_array($this->mediaType, $this->allowedMimeTypes, true)) {
|
351
|
|
|
return false;
|
352
|
|
|
}
|
353
|
|
|
|
354
|
|
|
return true;
|
355
|
|
|
}
|
356
|
|
|
|
357
|
|
|
/**
|
358
|
|
|
* Is the file the right extension
|
359
|
|
|
*
|
360
|
|
|
* @return bool
|
361
|
|
|
**/
|
362
|
|
|
public function checkExtension(): bool
|
363
|
|
|
{
|
364
|
|
|
$ext = mb_substr(mb_strrchr($this->mediaName, '.'), 1);
|
365
|
|
|
if (!empty($this->allowedExtensions) && !\in_array(mb_strtolower($ext), $this->allowedExtensions, true)) {
|
366
|
|
|
return false;
|
367
|
|
|
}
|
368
|
|
|
|
369
|
|
|
return true;
|
370
|
|
|
}
|
371
|
|
|
|
372
|
|
|
/**
|
373
|
|
|
* Add an error
|
374
|
|
|
*
|
375
|
|
|
* @param string $error
|
376
|
|
|
**/
|
377
|
|
|
public function setErrors($error): void
|
378
|
|
|
{
|
379
|
|
|
$this->errors[] = \trim($error);
|
380
|
|
|
}
|
381
|
|
|
|
382
|
|
|
/**
|
383
|
|
|
* Get generated errors
|
384
|
|
|
*
|
385
|
|
|
* @param bool $ashtml Format using HTML?
|
386
|
|
|
*
|
387
|
|
|
* @return array|string Array of array messages OR HTML string
|
388
|
|
|
*/
|
389
|
|
|
public function &getErrors($ashtml = true)
|
390
|
|
|
{
|
391
|
|
|
if (!$ashtml) {
|
392
|
|
|
return $this->errors;
|
393
|
|
|
}
|
394
|
|
|
$ret = '';
|
395
|
|
|
if (\count($this->errors) > 0) {
|
396
|
|
|
$ret = '<h4>Errors Returned While Uploading</h4>';
|
397
|
|
|
foreach ($this->errors as $error) {
|
398
|
|
|
$ret .= $error . '<br>';
|
399
|
|
|
}
|
400
|
|
|
}
|
401
|
|
|
|
402
|
|
|
return $ret;
|
403
|
|
|
}
|
404
|
|
|
}
|
405
|
|
|
|