305 lines
8.6 KiB
PHP
305 lines
8.6 KiB
PHP
<?php
|
||
|
||
namespace ForkBB\Core;
|
||
|
||
use ForkBB\Core\File;
|
||
use ForkBB\Core\Image;
|
||
use ForkBB\Core\Exceptions\FileException;
|
||
use InvalidArgumentException;
|
||
|
||
class Files
|
||
{
|
||
/**
|
||
* Максимальный размер для картинок
|
||
* @var int
|
||
*/
|
||
protected $maxImgSize;
|
||
|
||
/**
|
||
* Максимальный размер для файлов
|
||
* @var int
|
||
*/
|
||
protected $maxFileSize;
|
||
|
||
/**
|
||
* Текст ошибки
|
||
* @var null|string
|
||
*/
|
||
protected $error;
|
||
|
||
/**
|
||
* Список кодов типов картинок и расширений для них????
|
||
* @var array
|
||
*/
|
||
protected $imageType = [
|
||
1 => 'gif',
|
||
2 => 'jpg',
|
||
3 => 'png',
|
||
4 => 'swf',
|
||
5 => 'psd',
|
||
6 => 'bmp',
|
||
7 => 'tiff',
|
||
8 => 'tiff',
|
||
9 => 'jpc',
|
||
10 => 'jp2',
|
||
11 => 'jpx',
|
||
12 => 'jb2',
|
||
13 => 'swc',
|
||
14 => 'iff',
|
||
15 => 'wbmp',
|
||
16 => 'xbm',
|
||
17 => 'ico',
|
||
18 => 'webp',
|
||
];
|
||
|
||
/**
|
||
* Список единиц измерения
|
||
* @var string
|
||
*/
|
||
protected $units = 'BKMGTPEZY';
|
||
|
||
public function __construct(/* string|int */ $maxFileSize, /* string|int */ $maxImgSize)
|
||
{
|
||
$init = \min(
|
||
$this->size(\ini_get('upload_max_filesize')),
|
||
$this->size(\ini_get('post_max_size'))
|
||
);
|
||
$this->maxImgSize = \min(
|
||
$this->size($maxImgSize),
|
||
$init
|
||
);
|
||
$this->maxFileSize = \min(
|
||
$this->size($maxFileSize),
|
||
$init
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Возвращает максимальный размер картинки для загрузки
|
||
*/
|
||
public function maxImgSize(string $unit = null): int
|
||
{
|
||
return $this->size($this->maxImgSize, $unit);
|
||
}
|
||
|
||
/**
|
||
* Возвращает максимальный размер файла для загрузки
|
||
*/
|
||
public function maxFileSize(string $unit = null): int
|
||
{
|
||
return $this->size($this->maxFileSize, $unit);
|
||
}
|
||
|
||
/**
|
||
* Переводит объем информации из одних единиц в другие
|
||
* кило = 1024, а не 1000
|
||
*/
|
||
public function size(/* int|float|string */ $value, string $to = null) /* : int|float */
|
||
{
|
||
if (\is_string($value)) {
|
||
if (! \preg_match('%^([^a-z]+)([a-z]+)?$%i', \trim($value), $matches)) {
|
||
throw new InvalidArgumentException('Expected string indicating the amount of information');
|
||
}
|
||
if (! \is_numeric($matches[1])) {
|
||
throw new InvalidArgumentException('String does not contain number');
|
||
}
|
||
|
||
$value = 0 + $matches[1];
|
||
|
||
if (! empty($matches[2])) {
|
||
$unit = \strtoupper($matches[2][0]);
|
||
$expo = \strpos($this->units, $unit);
|
||
|
||
if (false === $expo) {
|
||
throw new InvalidArgumentException('Unknown unit');
|
||
}
|
||
|
||
$value *= 1024 ** $expo;
|
||
}
|
||
}
|
||
|
||
if (\is_string($to)) {
|
||
$to = \trim($to);
|
||
$unit = \strtoupper($to[0]);
|
||
$expo = \strpos($this->units, $unit);
|
||
|
||
if (false === $expo) {
|
||
throw new InvalidArgumentException('Unknown unit');
|
||
}
|
||
|
||
$value /= 1024 ** $expo;
|
||
}
|
||
|
||
return 0 + $value;
|
||
}
|
||
|
||
/**
|
||
* Возвращает текст ошибки
|
||
*/
|
||
public function error(): ?string
|
||
{
|
||
return $this->error;
|
||
}
|
||
|
||
/**
|
||
* Определяет по содержимому файла расширение картинки????
|
||
*/
|
||
public function isImage(/* mixed */ $file): ?string
|
||
{
|
||
if (\is_string($file)) {
|
||
if (\function_exists('\\exif_imagetype')) {
|
||
$type = \exif_imagetype($file);
|
||
} elseif (
|
||
\function_exists('\\getimagesize')
|
||
&& false !== ($type = @\getimagesize($file))
|
||
&& $type[0] > 0
|
||
&& $type[1] > 0
|
||
) {
|
||
$type = $type[2];
|
||
} else {
|
||
$type = 0;
|
||
}
|
||
|
||
return $this->imageType[$type] ?? null;
|
||
}
|
||
|
||
return $file instanceof Image ? $file->ext() : null;
|
||
}
|
||
|
||
/**
|
||
* Получает файл(ы) из формы
|
||
*/
|
||
public function upload(array $file) /* : mixed */
|
||
{
|
||
$this->error = null;
|
||
|
||
if (! isset($file['tmp_name'], $file['name'], $file['type'], $file['error'], $file['size'])) {
|
||
$this->error = 'Expected file description array';
|
||
|
||
return false;
|
||
}
|
||
|
||
if (\is_array($file['tmp_name'])) {
|
||
$result = [];
|
||
foreach ($file['tmp_name'] as $key => $value) {
|
||
// изображение не было отправлено
|
||
if (
|
||
'' === $file['name'][$key]
|
||
&& empty($file['size'][$key])
|
||
) {
|
||
continue;
|
||
}
|
||
|
||
$cur = $this->uploadOneFile([
|
||
'tmp_name' => $value,
|
||
'name' => $file['name'][$key],
|
||
'type' => $file['type'][$key],
|
||
'error' => $file['error'][$key],
|
||
'size' => $file['size'][$key],
|
||
]);
|
||
|
||
if (false === $cur) {
|
||
return false;
|
||
}
|
||
|
||
$result[] = $cur;
|
||
}
|
||
|
||
return empty($result) ? null : $result;
|
||
} else {
|
||
return '' === $file['name'] && empty($file['size']) ? null : $this->uploadOneFile($file);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает один файл из формы
|
||
*/
|
||
protected function uploadOneFile(array $file) /* : mixed */
|
||
{
|
||
if (\UPLOAD_ERR_OK !== $file['error']) {
|
||
switch ($file['error']) {
|
||
case \UPLOAD_ERR_INI_SIZE:
|
||
$this->error = 'The uploaded file exceeds the upload_max_filesize';
|
||
break;
|
||
case \UPLOAD_ERR_FORM_SIZE:
|
||
$this->error = 'The uploaded file exceeds the MAX_FILE_SIZE';
|
||
break;
|
||
case \UPLOAD_ERR_PARTIAL:
|
||
$this->error = 'The uploaded file was only partially uploaded';
|
||
break;
|
||
case \UPLOAD_ERR_NO_FILE:
|
||
$this->error = 'No file was uploaded';
|
||
break;
|
||
case \UPLOAD_ERR_NO_TMP_DIR:
|
||
$this->error = 'Missing a temporary folder';
|
||
break;
|
||
case \UPLOAD_ERR_CANT_WRITE:
|
||
$this->error = 'Failed to write file to disk';
|
||
break;
|
||
case \UPLOAD_ERR_EXTENSION:
|
||
$this->error = 'A PHP extension stopped the file upload';
|
||
break;
|
||
default:
|
||
$this->error = 'Unknown upload error';
|
||
break;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
if (! \is_uploaded_file($file['tmp_name'])) {
|
||
$this->error = 'The specified file was not uploaded';
|
||
|
||
return false;
|
||
}
|
||
|
||
if (false === ($pos = \strrpos($file['name'], '.'))) {
|
||
$name = $file['name'];
|
||
$ext = null;
|
||
} else {
|
||
$name = \substr($file['name'], 0, $pos);
|
||
$ext = \substr($file['name'], $pos + 1);
|
||
}
|
||
|
||
$isImage = $this->isImage($file['tmp_name']);
|
||
|
||
if (null !== $isImage) {
|
||
$ext = $isImage;
|
||
$isImage = 'swf' !== $isImage; // флеш не будет картинкой
|
||
}
|
||
|
||
if ($isImage) {
|
||
if ($file['size'] > $this->maxImgSize) {
|
||
$this->error = 'The image too large';
|
||
|
||
return false;
|
||
}
|
||
} else {
|
||
if ($file['size'] > $this->maxFileSize) {
|
||
$this->error = 'The file too large';
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
$options = [
|
||
'filename' => $name,
|
||
'extension' => $ext,
|
||
'basename' => $name . '.' . $ext,
|
||
'mime' => $file['type'],
|
||
// 'size' => $file['size'],
|
||
];
|
||
|
||
try {
|
||
if ($isImage) {
|
||
return new Image($file['tmp_name'], $options);
|
||
} else {
|
||
return new File($file['tmp_name'], $options);
|
||
}
|
||
} catch (FileException $e) {
|
||
$this->error = $e->getMessage();
|
||
|
||
return false;
|
||
}
|
||
}
|
||
}
|