浏览代码

2018-04-01

Visman 7 年之前
父节点
当前提交
40600704f4

+ 9 - 0
app/Core/Exceptions/FileException.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace ForkBB\Core\Exceptions;
+
+use ForkBB\Core\Exceptions\ForkException;
+
+class FileException extends ForkException
+{
+}

+ 305 - 0
app/Core/File.php

@@ -0,0 +1,305 @@
+<?php
+
+namespace ForkBB\Core;
+
+use ForkBB\Core\Exceptions\FileException;
+use InvalidArgumentException;
+
+class File
+{
+    /**
+     * Текст ошибки
+     * @var null|string
+     */
+    protected $error;
+
+    /**
+     * Путь до файла
+     * @var null|string
+     */
+    protected $path;
+
+    /**
+     * Содержимое файла
+     * @var null|string
+     */
+    protected $data;
+
+    /**
+     * Оригинальное имя файла без расширения
+     * @var null|string
+     */
+    protected $name;
+
+    /**
+     * Оригинальное расширение файла
+     * @var null|string
+     */
+    protected $ext;
+
+    /**
+     * Размер оригинального файла
+     */
+    protected $size;
+
+    /**
+     * Флаг автопереименования файла
+     * @var bool
+     */
+    protected $rename  = false;
+
+    /**
+     * Флаг перезаписи файла
+     * @var bool
+     */
+    protected $rewrite = false;
+
+    /**
+     * Паттерн для pathinfo
+     * @var string
+     */
+    protected $pattern = '%^(?!.*?\.\.)([\w.\x5C/:-]*[\x5C/])?(\*|[\w.-]+)\.(\*|[a-z\d]+)$%i';
+
+    /**
+     * Конструктор
+     *
+     * @param string $path
+     * @param array $options
+     *
+     * @throws FileException
+     */
+    public function __construct($path, $options)
+    {
+        if (! \is_file($path)) {
+            throw new FileException('File not found');
+        }
+        if (! \is_readable($path)) {
+            throw new FileException('File can not be read');
+        }
+
+        $this->path = $path;
+        $this->data = null;
+
+        $name = null;
+        $ext  = null;
+        if (isset($options['basename'])) {
+            if (false === ($pos = \strrpos($options['basename'], '.'))) {
+                $name = $options['basename'];
+            } else {
+                $name = \substr($options['basename'], 0, $pos);
+                $ext  = \substr($options['basename'], $pos + 1);
+            }
+        }
+
+        $this->name = isset($options['filename']) && \is_string($options['filename']) ? $options['filename'] : $name;
+        $this->ext  = isset($options['extension']) && \is_string($options['extension']) ? $options['extension'] : $ext;
+
+        $this->size = \is_string($this->data) ? \strlen($this->data) : \filesize($path);
+        if (! $this->size) {
+            throw new FileException('File size is undefined');
+        }
+    }
+
+    /**
+     * Возвращает текст ошибки
+     *
+     * @return null|string
+     */
+    public function error()
+    {
+        return $this->error;
+    }
+
+    /**
+     * Фильрует и переводит в латиницу(?) имя файла
+     *
+     * @param string $name
+     *
+     * @return string
+     */
+    protected function filterName($name)
+    {
+        if (\function_exists('\transliterator_transliterate')) {
+            $name = \transliterator_transliterate("Any-Latin; NFD; [:Nonspacing Mark:] Remove; NFC; [:Punctuation:] Remove; Lower();", $name);
+        }
+
+        $name = \trim(\preg_replace('%[^\w.-]+%', '-', $name), '-');
+
+        if (! isset($name{0})) {
+            $name = (string) \time();
+        }
+
+        return $name;
+    }
+
+    /**
+     * Возвращает информацию о пути к сохраняемому файлу с учетом подстановок
+     *
+     * @param string $path
+     *
+     * @return false|array
+     */
+    protected function pathinfo($path)
+    {
+        if (! \preg_match($this->pattern, $path, $matches)) {
+            $this->error = 'The path/name format is broken';
+            return false;
+        }
+
+        if ('*' === $matches[2]) {
+            $matches[2] = $this->filterName($this->name);
+        }
+
+        if ('*' === $matches[3]) {
+            $matches[3] = $this->ext;
+        } elseif ('(' === $matches[3]{0} && ')' === $matches[3]{\strlen($matches[3]) - 1}) {
+            $matches[3] = \explode('|', \substr($matches[3], 1, -1));
+
+            if (1 === \count($matches[3])) {
+                $matches[3] = \array_pop($matches[3]);
+            }
+        }
+
+        return [
+            'dirname'   => $matches[1],
+            'filename'  => $matches[2],
+            'extension' => $matches[3],
+        ];
+    }
+
+    /**
+     * Устанавливает флаг автопереименования файла
+     *
+     * @param bool $rename
+     *
+     * @return File
+     */
+    public function rename($rename)
+    {
+        $this->rename = $rename;
+
+        return $this;
+    }
+
+    /**
+     * Устанавливает флаг перезаписи файла
+     *
+     * @param bool $rewrite
+     *
+     * @return File
+     */
+    public function rewrite($rewrite)
+    {
+        $this->rewrite = $rewrite;
+
+        return $this;
+    }
+
+    /**
+     * Создает/проверяет на запись директорию
+     *
+     * @param string $dirname
+     *
+     * @return bool
+     */
+    protected function dirProc($dirname)
+    {
+        if (! \is_dir($dirname)) {
+            if (! @\mkdir($dirname, 0755)) {
+                $this->error = 'Can not create directory';
+                return false;
+            }
+        }
+        if (! \is_writable($dirname)) {
+            $this->error = 'No write access for directory';
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Создает/устанавливает права на файл
+     *
+     * @param string $path
+     *
+     * @return bool
+     */
+    protected function fileProc($path)
+    {
+        if (\is_string($this->data)) {
+            if (! \file_put_contents($this->path, $path)) {
+                $this->error = 'Error writing file';
+                return false;
+            }
+        } else {
+            if (! \copy($this->path, $path)) {
+                $this->error = 'Error copying file';
+                return false;
+            }
+        }
+        @\chmod($path, 0644);
+
+        return true;
+    }
+
+    /**
+     * Сохраняет файл по указанному шаблону пути
+     *
+     * @param string $path
+     *
+     * @return bool
+     */
+    public function toFile($path)
+    {
+        $info = $this->pathinfo($path);
+
+        if (false === $info || ! $this->dirProc($info['dirname'])) {
+            return false;
+        }
+
+        if ($this->rename) {
+            $old = $info['filename'];
+            $i   = 1;
+            while (\file_exists($info['dirname'] . $info['filename'] . '.' . $info['extension'])) {
+                ++$i;
+                $info['filename'] = $old . '_' . $i;
+            }
+        } elseif (! $this->rewrite && \file_exists($info['dirname'] . $info['filename'] . '.' . $info['extension'])) {
+            $this->error = 'Such file already exists';
+            return false;
+        }
+
+        $path = $info['dirname'] . $info['filename'] . '.' . $info['extension'];
+
+        if ($this->fileProc($path)) {
+            $this->path = $path;
+            $this->name = $info['filename'];
+            $this->ext  = $info['extension'];
+            $this->size = \filesize($path);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public function name()
+    {
+        return $this->name;
+    }
+
+    public function ext()
+    {
+        return $this->ext;
+    }
+
+    public function size()
+    {
+        return $this->size;
+    }
+
+    public function path()
+    {
+        return $this->path;
+    }
+}

+ 326 - 0
app/Core/Files.php

@@ -0,0 +1,326 @@
+<?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',
+    ];
+
+    /**
+     * Конструктор
+     *
+     * @param string|int $maxFileSize
+     * @param string|int $maxImgSize
+     *
+     */
+    public function __construct($maxFileSize, $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
+        );
+    }
+
+    /**
+     * Возвращает максимальный размер картинки для загрузки
+     *
+     * @param string $unit
+     *
+     * @return int
+     */
+    public function maxImgSize($unit = null)
+    {
+        return $this->size($this->maxImgSize, $unit);
+    }
+
+    /**
+     * Возвращает максимальный размер файла для загрузки
+     *
+     * @param string $unit
+     *
+     * @return int
+     */
+    public function maxFileSize($unit = null)
+    {
+        return $this->size($this->maxFileSize, $unit);
+    }
+
+    /**
+     * Переводит объем информации из одних единиц в другие
+     *
+     * @param int|string $value
+     * @param string $to
+     *
+     * @return int
+     */
+    public function size($value, $to = null)
+    {
+        if (\is_string($value)) {
+            $value = \trim($value, "Bb \t\n\r\0\x0B");
+
+            if (! isset($value{0})) {
+                return 0;
+            }
+
+            $from = $value{\strlen($value) - 1};
+            $value = (int) $value;
+
+            switch ($from) {
+                case 'G':
+                case 'g':
+                    $value *= 1024;
+                case 'M':
+                case 'm':
+                    $value *= 1024;
+                case 'K':
+                case 'k':
+                    $value *= 1024;
+            }
+        }
+
+        if (\is_string($to)) {
+            $to = \trim($to, "Bb \t\n\r\0\x0B");
+
+            switch ($to) {
+                case 'G':
+                case 'g':
+                    $value /= 1024;
+                case 'M':
+                case 'm':
+                    $value /= 1024;
+                case 'K':
+                case 'k':
+                    $value /= 1024;
+            }
+        }
+
+        return (int) $value;
+    }
+
+    /**
+     * Возвращает текст ошибки
+     *
+     * @return null|string
+     */
+    public function error()
+    {
+        return $this->error;
+    }
+
+    /**
+     * Определяет по содержимому файла расширение картинки
+     *
+     * @param mixed $file
+     *
+     * @return false|string
+     */
+    public function isImage($file)
+    {
+        if (\is_string($file)) {
+            if (\function_exists('\exif_imagetype')) {
+                $type = \exif_imagetype($file);
+            } elseif (false !== ($type = @\getimagesize($file)) && $type[0] > 0 && $type[1] > 0) {
+                $type = $type[2];
+            } else {
+                $type = 0;
+            }
+            return isset($this->imageType[$type]) ? $this->imageType[$type] : false;
+        }
+
+        return $file instanceof Image ? $file->ext() : false;
+    }
+
+    /**
+     * Получает файл(ы) из формы
+     *
+     * @param array $file
+     *
+     * @return mixed
+     */
+    public function upload(array $file)
+    {
+        $this->error = null;
+
+        if (! isset($file['tmp_name'])
+            || ! isset($file['name'])
+            || ! isset($file['type'])
+            || ! isset($file['error'])
+            || ! isset($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);
+        }
+    }
+
+    /**
+     * Получает один файл из формы
+     *
+     * @param array $file
+     *
+     * @return mixed
+     */
+    protected function uploadOneFile(array $file)
+    {
+        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 (false !== $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;
+        }
+    }
+}

+ 168 - 0
app/Core/Image.php

@@ -0,0 +1,168 @@
+<?php
+
+namespace ForkBB\Core;
+
+use ForkBB\Core\Files;
+use ForkBB\Core\File;
+use ForkBB\Core\Exceptions\FileException;
+use InvalidArgumentException;
+
+class Image extends File
+{
+    /**
+     * Изображение
+     * @var false|resource
+     */
+    protected $image;
+
+    /**
+     * Качество изображения
+     * @var int
+     */
+    protected $quality = 100;
+
+    /**
+     * Паттерн для pathinfo
+     * @var string
+     */
+    protected $pattern = '%^(?!.*?\.\.)([\w.\x5C/:-]*[\x5C/])?(\*|[\w.-]+)\.(\*|[a-z\d]+|\([a-z\d]+(?:\|[a-z\d]+)*\))$%i';
+
+    /**
+     * Конструктор
+     *
+     * @param string $path
+     * @param array $options
+     *
+     * @throws FileException
+     */
+    public function __construct($path, $options)
+    {
+        parent::__construct($path, $options);
+
+        if (! \extension_loaded('gd') || ! \function_exists('\imagecreatetruecolor')) {
+            throw new FileException('GD library not connected');
+        }
+
+        if (\is_string($this->data)) {
+            $this->image = @\imagecreatefromstring($this->data);
+        } else {
+            $this->image = @\imagecreatefromstring(\file_get_contents($this->path));
+        }
+
+        if (false === $this->image) {
+            throw new FileException('Invalid image data');
+        }
+    }
+
+    /**
+     * Изменяет размер изображения при необходимости
+     *
+     * @param int $maxW
+     * @param int $maxH
+     *
+     * @throws FileException
+     *
+     * @return Image
+     */
+    public function resize($maxW, $maxH)
+    {
+        $oldW   = \imagesx($this->image);
+        $oldH   = \imagesy($this->image);
+        $wr     = ($maxW < 1) ? 1 : $maxW / $oldW;
+        $hr     = ($maxH < 1) ? 1 : $maxH / $oldH;
+        $r      = \min($wr, $hr, 1);
+        $width  = \round($oldW * $r);
+        $height = \round($oldH * $r);
+
+        if (false === ($image = \imagecreatetruecolor($width, $height))) {
+            throw new FileException('Failed to create new truecolor image');
+        }
+        if (false === ($color = \imagecolorallocatealpha($image, 255, 255, 255, 127))) {
+            throw new FileException('Failed to create color for image');
+        }
+        if (false === \imagefill($image, 0, 0, $color)) {
+            throw new FileException('Failed to fill image with color');
+        }
+        \imagecolortransparent($image, $color);
+        $palette = \imagecolorstotal($this->image);
+        if ($palette > 0 && ! \imagetruecolortopalette($image, true, $palette)) {
+            throw new FileException('Failed to convert image to palette');
+        }
+        if (false === \imagecopyresampled($image, $this->image, 0, 0, 0, 0, $width, $height, $oldW, $oldH)) {
+            throw new FileException('Failed to resize image');
+        }
+        if (false === \imagealphablending($image, false) || false === \imagesavealpha($image, true)) {
+            throw new FileException('Failed to adjust image');
+        }
+
+        $this->image = $image;
+
+        return $this;
+    }
+
+    /**
+     * Возвращает информацию о пути к сохраняемой картинке с учетом подстановок
+     *
+     * @param string $path
+     *
+     * @return false|array
+     */
+    protected function pathinfo($path)
+    {
+        $info = parent::pathinfo($path);
+
+        if (false === $info) {
+            return false;
+        }
+
+        if (\is_array($info['extension'])) {
+            if (\in_array($this->ext, $info['extension'])) {
+                $info['extension'] = $this->ext;
+            } else {
+                $info['extension'] = \reset($info['extension']); // ???? выбор расширения?
+            }
+        }
+
+        return $info;
+    }
+
+    /**
+     * Создает/устанавливает права на картинку
+     *
+     * @param string $path
+     *
+     * @return bool
+     */
+    protected function fileProc($path)
+    {
+        switch (\pathinfo($path, \PATHINFO_EXTENSION)) {
+            case 'jpg':
+                $result = @\imagejpeg($this->image, $path, $this->quality);
+                break;
+            case 'png':
+                $quality = \floor((100 - $this->quality) / 11);
+                $result = @\imagepng($this->image, $path, $quality);
+                break;
+            case 'gif':
+                $result = @\imagegif($this->image, $path);
+                break;
+            default:
+                $this->error = 'File type not supported';
+                return false;
+        }
+
+        if (! $result) {
+            $this->error = 'Error writing file';
+            return false;
+        }
+        @\chmod($path, 0644);
+
+        return true;
+    }
+
+    public function __destruct() {
+        if (\is_resource($this->image)) {
+            \imagedestroy($this->image);
+        }
+    }
+}

+ 62 - 1
app/Core/Validator.php

@@ -3,6 +3,7 @@
 namespace ForkBB\Core;
 
 use ForkBB\Core\Container;
+use ForkBB\Core\File;
 use RuntimeException;
 
 class Validator
@@ -108,6 +109,8 @@ class Validator
             'array'         => [$this, 'vArray'],
             'checkbox'      => [$this, 'vCheckbox'],
             'email'         => [$this, 'vEmail'],
+            'file'          => [$this, 'vFile'],
+            'image'         => [$this, 'vImage'],
             'in'            => [$this, 'vIn'],
             'integer'       => [$this, 'vInteger'],
             'login'         => [$this, 'vLogin'],
@@ -616,9 +619,22 @@ class Validator
                 $this->addError('The :alias maximum is :attr');
             }
         } elseif (\is_array($value)) {
-            if (\count($value) > $attr) {
+            if (\reset($value) instanceof File) {
+                $attr *= 1024;
+                foreach ($value as $file) {
+                    if ($file->size() > $attr) {
+                        $this->addError('The :alias contains too large a file');
+                        return null;
+                    }
+                }
+            } elseif (\count($value) > $attr) {
                 $this->addError('The :alias maximum is :attr elements');
             }
+        } elseif ($value instanceof File) {
+            if ($value->size() > $attr * 1024) {
+                $this->addError('The :alias contains too large a file');
+                return null;
+            }
         } elseif (null !== $value) {
             $this->addError('The :alias maximum is :attr'); //????
             return null;
@@ -713,4 +729,49 @@ class Validator
         }
         return $value;
     }
+
+
+    protected function vFile($v, $value, $attr)
+    {
+        if (null === $value) {
+            return null;
+        }
+        if (! \is_array($value)) {
+            $this->addError('The :alias not contains file');
+            return null;
+        }
+        $value = $this->c->Files->upload($value);
+        if (null === $value) {
+            return null;
+        } elseif (false === $value) {
+            $this->addError($this->c->Files->error());
+            return null;
+        } elseif ('multiple' === $attr) {
+            if (! \is_array($value)) {
+                $value = [$value];
+            }
+        } elseif (\is_array($value)) {
+            $this->addError('The :alias contains more than one file');
+            return null;
+        }
+        return $value;
+    }
+
+    protected function vImage($v, $value, $attr)
+    {
+        $value = $this->vFile($v, $value, $attr);
+
+        if (\is_array($value)) {
+            foreach ($value as $file) {
+                if (false === $this->c->Files->isImage($file)) {
+                    $this->addError('The :alias not contains image');
+                    return null;
+                }
+            }
+        } elseif (null !== $value && false === $this->c->Files->isImage($value)) {
+            $this->addError('The :alias not contains image');
+            return null;
+        }
+        return $value;
+    }
 }

+ 33 - 3
app/Models/Pages/Profile.php

@@ -2,6 +2,7 @@
 
 namespace ForkBB\Models\Pages;
 
+use ForkBB\Core\Image;
 use ForkBB\Models\Page;
 use ForkBB\Models\User\Model as User;
 
@@ -52,16 +53,45 @@ class Profile extends Page
         $this->c->Lang->load('profile');
 
         if ($isEdit && 'POST' === $method) {
+            $v = $this->c->Validator->reset()
+                ->addValidators([
+                ])->addRules([
+                    'token'         => 'token:EditUserProfile',
+                    'upload_avatar' => $rules->useAvatar ? 'image|max:' . $this->c->Files->maxImgSize('K') : 'absent',
+                ])->addAliases([
+                ])->addArguments([
+                    'token'         => ['id' => $curUser->id],
+                ])->addMessages([
+                ]);
 
+            if ($v->validation($_FILES + $_POST)) {
+                if ($v->upload_avatar instanceof Image) {
+                    $curUser->deleteAvatar();
+                    $v->upload_avatar
+                        ->rename(false)
+                        ->rewrite(true)
+                        ->resize((int) $this->c->config->o_avatars_width, (int) $this->c->config->o_avatars_height)
+                        ->toFile($this->c->DIR_PUBLIC . "{$this->c->config->o_avatars_dir}/{$curUser->id}.(jpg|png|gif)");
+#                    var_dump(
+#                        $v->upload_avatar->path(),
+#                        $v->upload_avatar->name(),
+#                        $v->upload_avatar->ext(),
+#                        $v->upload_avatar->size(),
+#                       $v->upload_avatar->error()
+#                    );
+                }
+            }
+
+            $this->fIswev  = $v->getErrors();
         }
 
         $clSuffix = $isEdit ? '-edit' : '';
 
         if ($isEdit) {
             $form = [
-                'action' => $this->c->Router->link('EditUserProfile',  ['id' => $curUser->id]),
+                'action' => $this->c->Router->link('EditUserProfile', ['id' => $curUser->id]),
                 'hidden' => [
-                    'token' => $this->c->Csrf->create('EditUserProfile',  ['id' => $curUser->id]),
+                    'token' => $this->c->Csrf->create('EditUserProfile', ['id' => $curUser->id]),
                 ],
                 'sets'   => [],
                 'btns'   => [
@@ -141,7 +171,7 @@ class Profile extends Page
             }
             if ($isEdit) {
                 $form['enctype'] = 'multipart/form-data';
-                $form['hidden']['MAX_FILE_SIZE'] = 999999999;
+                $form['hidden']['MAX_FILE_SIZE'] = $this->c->Files->maxImgSize();
 
                 $fields['upload_avatar'] = [
                     'id'        => 'upload_avatar',

+ 21 - 3
app/Models/User/Model.php

@@ -9,6 +9,12 @@ use RuntimeException;
 
 class Model extends DataModel
 {
+    /**
+     * Типы аватарок
+     * @var array
+     */
+    protected $avatarTypes = ['jpg', 'gif', 'png'];
+
     /**
      * Статус неподтвержденного
      *
@@ -157,9 +163,7 @@ class Model extends DataModel
      */
     protected function getavatar()
     {
-        $filetypes = ['jpg', 'gif', 'png'];
-
-        foreach ($filetypes as $type) {
+        foreach ($this->avatarTypes as $type) {
             $path = $this->c->DIR_PUBLIC . "{$this->c->config->o_avatars_dir}/{$this->id}.{$type}";
 
             if (\is_file($path) && \getimagesize($path)) {
@@ -170,6 +174,20 @@ class Model extends DataModel
         return null;
     }
 
+    /**
+     * Удаляет аватару пользователя
+     */
+    public function deleteAvatar()
+    {
+        foreach ($this->avatarTypes as $type) {
+            $path = $this->c->DIR_PUBLIC . "{$this->c->config->o_avatars_dir}/{$this->id}.{$type}";
+
+            if (\is_file($path)) {
+                @\unlink($path);
+            }
+        }
+    }
+
     /**
      * Титул пользователя
      *

+ 7 - 0
app/config/main.dist.php

@@ -35,6 +35,8 @@ return [
         'smTplBl'  => ['url'],
     ],
     'MAX_POST_SIZE' => 65536,
+    'MAX_IMG_SIZE'  => '2M',
+    'MAX_FILE_SIZE' => '2M',
 
     'shared' => [
         'DB' => [
@@ -110,6 +112,11 @@ return [
             'class' => \ForkBB\Core\Parser::class,
             'flag'  => ENT_HTML5,
         ],
+        'Files' => [
+            'class' => \ForkBB\Core\Files::class,
+            'file'  => '%MAX_FILE_SIZE%',
+            'img'   => '%MAX_IMG_SIZE%',
+        ],
 
     ],
     'multiple'  => [

+ 2 - 0
composer.json

@@ -18,6 +18,8 @@
     },
     "require": {
         "php": ">=5.6.0",
+        "ext-gd": "*",
+        "ext-mbstring": "*",
         "artoodetoo/dirk": "dev-master",
         "miovisman/parserus": "dev-master"
     }