kuvalda-file/File.php

659 lines
21 KiB
PHP
Raw Normal View History

2024-11-20 15:56:54 +03:00
<?php
namespace dominion\file;
use Yii;
use yii\web\UploadedFile;
use Imagine\Image\Box;
use Imagine\Image\ImageInterface;
use \yii\imagine\Image;
/**
* This is the model class for table "file".
*
* @property int $id
* @property string $date
* @property string $module
* @property int $height
* @property int $width
* @property int $fileSize
* @property string $md5
* @property string $ext
* @property string $description
* @property string $originalName
*/
class File extends \yii\db\ActiveRecord
{
public $file;
public $cropFile = false;
public $convertJpg = true;
/**
* {@inheritdoc}
*/
public static function tableName()
{
return 'file';
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[['date'], 'safe'],
[['module'], 'required'],
[['height', 'width', 'fileSize'], 'integer'],
[['description'], 'string'],
[['module', 'originalName', 'name'], 'string', 'max' => 500],
[['md5', 'ext'], 'string', 'max' => 50],
];
}
/**
* {@inheritdoc}
*/
/*public function attributeLabels()
{
return [
'id' => Module::t('app', 'ID'),
'date' => Module::t('app', 'Date'),
'module' => Module::t('app', 'Module'),
'height' => Module::t('app', 'Height'),
'width' => Module::t('app', 'Width'),
'fileSize' => Module::t('app', 'File Size'),
'md5' => Module::t('app', 'Md5'),
'ext' => Module::t('app', 'Ext'),
'description' => Module::t('app', 'Description'),
'originalName' => Module::t('app', 'Original Name'),
'name' => Module::t('app', 'Name'),
];
}*/
public static function validateFormat($model, $attribute, $params, $fileName = 'file')
{
$fileModel = new File;
$fileModel->file = UploadedFile::getInstance($fileModel, $fileName);
if ($fileModel->file && $fileModel->file->tempName)
{
\yii\validators\Validator::createValidator('file', $fileModel, 'file', $params)->validateAttribute($fileModel, 'file');
if ($fileModel->hasErrors())
{
$model->addError($attribute, 'Не верный формат файла');
}
}
}
/**
* Папка с файлом
* @param boolian $full
* @return type
*/
public function getDir($full = false, $resize = false)
{
$dir = $resize ? 'file_resize' : 'file';
$output = '';
// $full = $full && !Yii::$app->has('s3');
if($full)
{
$output = Yii::getAlias('@app/web');
}
$output .= '/data/' . $dir . '/' . $this->module . '/' . wordwrap(mb_substr($this->md5, 0, 6), 2, '/', true) . '/';
if(Yii::$app->has('s3') && $full && $resize)
{
//если работаем с облаком не плодим папки миниатюр
$output = Yii::getAlias('@app/web/data/file_cache/');
}
if ($full && !file_exists($output))
{
mkdir($output, 0755, true);
}
if(Yii::$app->has('s3') && $full && $resize)
{
//если работаем с облаком не плодим папки миниатюр
$output .= $this->md5.'-';
}
return $output;
}
/**
* Путь к папке кеша
* @return string
*/
public function getDirCache()
{
$dir = Yii::getAlias('@app/web/data/file_cache/');
if (!file_exists($dir))
{
mkdir($dir, 0755, true);
}
return $dir;
}
public function getFilePath($type = false, $full = false)
{
$pref = $type ? ($type . '-') : '';
return $this->getDir($full, $type != false) . $pref . $this->name . '.' . $this->ext;
}
public function getFilePathCache()
{
$this->cropFile = $this->cropFile ? $this->cropFile :$this->getDirCache() . rand(1000, 999999);
return $this->cropFile;
}
public function getIsImage()
{
return in_array($this->ext, ['jpg', 'png', 'jpeg']);
}
public function getShowImage()
{
return in_array($this->ext, ['jpg', 'png', 'jpeg', 'svg']);
}
protected function getFileName()
{
$defaultName = empty($this->name) ? $this->md5 : $this->name;
$name = $defaultName;
$i = 0;
while(($model = File::find()->andWhere(['ext' => $this->ext, 'name'=>$name])->andWhere('md5 like "'. mb_substr($this->md5, 0, 6).'%"')->one()) !== null)
{
$i++;
$name = $defaultName . '-' .$i;
}
return $name;
}
/**
* {@inheritdoc}
*/
public function beforeSave($insert)
{
if ($this->file)
{
$this->ext = $this->file->extension;
$this->md5 = md5_file($this->file->tempName);
$patch = $this->getDir(true);
if ($this->isImage)
{
$this->ext = $this->convertJpg ? 'jpg' : $this->ext;
$this->name = $this->getFileName();
$fileName = $this->name . '.' . $this->ext;
$image = Image::getImagine()->open($this->file->tempName);
$size = $image->getSize();
$this->width = $size->getWidth();
$this->height = $size->getHeight();
$box = new Box($this->width, $this->height);
$palette = new \Imagine\Image\Palette\RGB();
$color = $palette->color(Image::$thumbnailBackgroundColor, Image::$thumbnailBackgroundAlpha);
$thumb = Image::getImagine()->create($box, $color);
$thumb->paste($image, new \Imagine\Image\Point(0, 0));
// var_dump($thumb); die();
$thumb->save($patch . $fileName);
$this->fileSize = filesize($patch . $fileName);
}
else
{
$this->name = $this->getFileName();
$this->fileSize = $this->file->size;
$fileName = $this->name . '.' . $this->ext;
if (is_uploaded_file($this->file->tempName))
{
$this->file->saveAs($patch . $fileName);
}
else
{
rename($this->file->tempName, $patch . $fileName);
}
}
$this->originalName = $this->file->name;
$this->date = date('Y-m-d H:i:s');
}
return parent::beforeSave($insert);
}
public function afterSave($insert, $changedAttributes)
{
if (isset($this->file))
{
$configs = Config::find()->andWhere(['module' => $this->module])->all();
foreach ($configs as $config)
{
$fileTemplate = $config->file ? [
'filePatch' => $config->file->getFilePath(false, true),
'border_top' => $config->border_top,
'border_right' => $config->border_right,
'border_left' => $config->border_left,
'border_bottom' => $config->border_bottom,
] : [];
if($config->file)
{
$config->file->downloadOriginal();
}
$quality = isset($config->quality) ? $config->quality : 90;
$this->resize($config->type, $config->height, $config->width, $config->crop, $config->cut, $config->addBorder, true, $fileTemplate, $quality);
}
if($this->cropFile)
{
@unlink($this->cropFile);
}
$this->moveToS3();
}
return parent::afterSave($insert, $changedAttributes);
}
/**
* Возврашает путь к изображению
* @param type $id
* @return string
*/
public static function getPath($id, $type = false)
{
$output = '';
if ($id > 0 && ($model = File::findOne($id)) !== null)
{
$output = $model->getFilePath($type);
}
return $output;
}
/**
* Возврашает путь к изображению
* @param type $id
* @return string
*/
public static function getPathFull($id, $type = false, $default = false)
{
$img = File::getPath($id, $type);
$fileUrlDomen = isset(Yii::$app->params['fileUrlDomen']) ? Yii::$app->params['fileUrlDomen'] : '';
$img = empty($img) ? $default : $img;
return empty($img) ? false : (preg_replace("#/$#", "", $fileUrlDomen) . $img);
}
public static function getPathsFull($id, $types = [])
{
$fileUrlDomen = isset(Yii::$app->params['fileUrlDomen']) ? Yii::$app->params['fileUrlDomen'] : '';
return self::getPaths($id, $types, $fileUrlDomen);
}
/**
* Возврашает пути к изображениям
* @param type $id
* @return array
*/
public static function getPaths($id, $types = [], $prefix = '')
{
$output = [];
if ($id > 0 && ($model = File::findOne($id)) !== null)
{
foreach($types as $type)
{
$output[$type] = $prefix. $model->getFilePath($type);
}
}
return $output;
}
protected function deleteDisckFile($deleteOriginal = false)
{
if($deleteOriginal)
{
//удаляем оригинал
$file = $this->getFilePath(false, true);
if(file_exists($file))
{
unlink($file);
$files = array_diff(scandir($this->getDir(true)), array('.', '..'));
if(empty($files))
{
Helper::delFolder($this->getDir(true));
}
}
}
//удаляем миниатюры
$dir = $this->getDir(true, true);
if(file_exists($dir))
{
$files = array_diff(scandir($dir), array('.', '..'));
foreach ($files as $key => $value)
{
if(stripos($value, $this->md5 . '.' . $this->ext) !== false)
{
unlink("$dir/$value");
unset($files[$key]);
}
}
if(empty($files))
{
Helper::delFolder($this->getDir(true, true));
}
}
}
protected function deleteS3File()
{
if(Yii::$app->has('s3'))
{
$s3 = Yii::$app->get('s3');
$s3->delete($this->getFilePath());
$configs = Config::find()->andWhere(['module' => $this->module])->all();
foreach ($configs as $config)
{
$s3->delete($this->getFilePath($config->type));
}
}
}
public function delete()
{
$this->deleteDisckFile(true);
$this->deleteS3File();
return parent::delete();
}
/**
* Создание миниатюр
* @param string $type
* @param int $height
* @param int $width
* @param boolian $crop обрезать белую рамку
* @param boolian $cut обрезать по расмеру без соблюдения пропорций сторон
* @param boolian $addBorder добавить белую рамку
* @param boolian $all обновить изображение если уже оно создано
* @param array $fileTemplate Вписать в шаблон
* @param int $quality качество (степень сжатия)
*
*/
public function resize($type, $height, $width, $crop = false, $cut = false, $addBorder = false, $all = false, $fileTemplate = [], $quality = 90)
{
$patch = $this->getDir(true);
$fileName = $this->name . '.' . $this->ext;
$filePatch = $patch . $fileName;
$quality = $quality > 100 || $quality <= 0 ? 90 : $quality;
if (file_exists($filePatch) && in_array(exif_imagetype($filePatch), [IMAGETYPE_JPEG, IMAGETYPE_PNG]))
{
$resizePatch = $this->getDir(true, true);
$resizeFileName = $type . '-' . $fileName;
if ($all || !file_exists($resizePatch . $resizeFileName))
{
if ($crop)
{
$filePatch = $this->cropFile ? $this->cropFile : $this->deleteBorder($filePatch);
}
if(!empty($fileTemplate) && isset($fileTemplate['filePatch'], $fileTemplate['border_top'], $fileTemplate['border_right'], $fileTemplate['border_left'], $fileTemplate['border_bottom']))
{
\dominion\file\Image::thumbnailFileTemplate($filePatch, $width, $height, $fileTemplate)->save($resizePatch . $resizeFileName, ['quality' => $quality]);
}
elseif ($cut || $addBorder)
{
$class = $addBorder ? '\dominion\file\Image' : '\yii\imagine\Image';
$mode = $addBorder ? ImageInterface::THUMBNAIL_INSET : ImageInterface::THUMBNAIL_OUTBOUND;
$class::thumbnail($filePatch, $width, $height, $mode)->save($resizePatch . $resizeFileName, ['quality' => $quality]);
}
else
{
Image::getImagine()->open($filePatch)->thumbnail(new Box($width, $height))->save($resizePatch . $resizeFileName, ['quality' => $quality]);
}
}
}
}
/**
* Обрезает белую рамку
* @param type $filePatch
* @return type
*/
protected function deleteBorder($filePatch)
{
$img = imagecreatefromjpeg($filePatch);
$this->width = $this->width > 0 ? $this->width : imagesx($img);
$this->height = $this->height > 0 ? $this->height : imagesy($img);
$border = array(
'left' => $this->getBorderSize($img, $this->width, $this->height, 'r'),
'right' => $this->getBorderSize($img, $this->width, $this->height, 'l'),
'top' => $this->getBorderSize($img, $this->height, $this->width, 'b'),
'bottom' => $this->getBorderSize($img, $this->height, $this->width, 't'),
);
$x = $border['left'];
$y = $border['top'];
$newWidth = $this->width - ($border['left'] + $border['right']);
$newHeight = $this->height - ($border['top'] + $border['bottom']);
if($newWidth == 0 || $newHeight == 0)
{
return $filePatch;
}
// create the working image
$workingImage = function_exists('imagecreatetruecolor') ? imagecreatetruecolor($newWidth, $newHeight) : imagecreate($newWidth, $newHeight);
// and create the newly sized image
imagecopy(
$workingImage,
$img,
0,
0,
$x,
$y,
$newWidth,
$newHeight
);
$temp = $this->getFilePathCache();
imagejpeg($workingImage, $temp, 100);
return $temp;
}
/**
* Возвращает позицию первого не белого пикселя
* @param type $image
* @param type $width
* @param type $height
* @param type $direction
* @return int
* @throws Exception
*/
protected function getBorderSize($image, $width, $height, $direction)
{
$border = 0;
$realWidth = imagesx($image);
$realHeight = imagesy($image);
for ($i = 0; $i < $width; $i++)
{
$isWhite = true;
$errors = round($height / 100);
for ($j = 0; $j < $height; $j++)
{
switch ($direction)
{
case 'l':
$x = $width - $i - 1;
$y = $j;
break;
case 'b':
$x = $j;
$y = $i;
break;
case 't':
$x = $j;
$y = $width - $i - 1;
break;
default:
$x = $i;
$y = $j;
break;
}
if ($x > $realWidth || $y > $realHeight || $x < 0 || $y < 0)
{
throw new Exception('Не верные координаты изображения');
}
// Получаем RGB пикселя по координате
$color = imageColorAt($image, $x, $y);
// Разбиваем RGB на Red,Green,Blue и записываем каждую составляющую в свою переменную
$pixel = imageColorsForIndex($image, $color);
list($r, $g, $b) = array_values($pixel);
//Сравнение белого ли цвета пиксель
if ($r > 241 && $g > 241 && $b > 241)
{
// белый
}
else
{
// не белый
$errors--;
if ($errors <= 0)
{
$isWhite = false;
break;
}
}
}
if ($isWhite)
{
$border++;
}
else
{
break;
}
}
return $border;
}
public function loadFile($module, $url, $id = false)
{
$file = @file_get_contents($url);
if ($file !== false)
{
$temp = $this->getFilePathCache();
file_put_contents($temp, $file);
unset($file);
if (!$id || ($model = File::findOne($id)) === null)
{
$model = new File();
$model->module = $module;
}
$arName = explode('/', $url);
$model->file = new UploadedFile(
[
'name' => array_pop($arName),
'tempName' => $temp,
'type' => mime_content_type($temp),
'size' => filesize($temp),
'error' => UPLOAD_ERR_OK,
]
);
$model->save();
return $model->id;
}
}
/**
* Сохранение файла из строки Base64
* @param type $module
* @param type $fileBase64
* @param type $fileName
* @param type $id
* @return type
*/
public static function saveBase64File($module, $fileBase64, $fileName, $id = false, $convertJpg = true)
{
if (!$id || ($model = File::findOne($id)) === null)
{
$model = new File();
$model->module = $module;
}
$temp = $model->getFilePathCache();
file_put_contents($temp, base64_decode($fileBase64));
$model->file = new UploadedFile(
[
'name' => $fileName,
'tempName' => $temp,
'type' => mime_content_type($temp),
'size' => filesize($temp),
'error' => UPLOAD_ERR_OK,
]
);
$model->convertJpg = $convertJpg;
$model->save();
return $model->id;
}
public function getSize()
{
$fix = array(' B', ' KB', ' MB', ' GB', ' TB');
$round = 2;
$mult = 1E3;
$range = 0;
while (1 < $this->fileSize / pow($mult, $range))
{
$range++;
}
$mult = pow($mult, $range - 1);
$postfix = $fix[$range - 1];
return round($this->fileSize / $mult, $round) . $postfix;
}
public function moveToS3()
{
if(Yii::$app->has('s3'))
{
$s3 = Yii::$app->get('s3');
if(file_exists($this->getFilePath(false, true)))
{
$s3->upload($this->getFilePath(), $this->getFilePath(false, true));
$configs = Config::find()->andWhere(['module' => $this->module])->all();
foreach ($configs as $config)
{
if(file_exists($this->getFilePath($config->type, true)))
{
$s3->upload($this->getFilePath($config->type), $this->getFilePath($config->type, true));
}
}
}
}
//var_dump($this->getFilePath());
}
public function downloadOriginal()
{
if(Yii::$app->has('s3'))
{
$filePatch = $this->getFilePath(false, true);
if (!file_exists($filePatch) && isset(Yii::$app->params['fileUrlDomen']))
{
$file = @file_get_contents(Yii::$app->params['fileUrlDomen']. $this->getFilePath());
if($file)
{
@file_put_contents($filePatch, $file);
}
}
}
}
public static function upload($oldFile, $newFile)
{
if(Yii::$app->has('s3'))
{
$s3 = Yii::$app->get('s3');
if(file_exists($oldFile))
{
$s3->upload($newFile, $oldFile);
}
}
}
}