File: D:/HostingSpaces/SBogers10/ehboledensysteem.komma.pro/app/KommaApp/Images/ImageService.php
<?php
namespace App\KommaApp\Images;
/**
* Short description for the file.
*
* @author Komma <support@komma.pro>
* @copyright (c) 2012-2015, Komma
*/
use App\KommaApp\Kms\Core\Attributes\ImageableAttribute;
use App\KommaApp\Kms\Core\Attributes\Models\AbstractImageProperty;
use App\KommaApp\Kms\Core\Attributes\Models\ImageProperty;
use Illuminate\Database\Eloquent\Model;
use App\KommaApp\Images\Models\Image;
use App\KommaApp\Pages\Models\Page;
use App\KommaApp\Users\Models\User;
class ImageService
{
/**
* The subfolder in the uploads dir
*
* @var string
*/
public $subfolder = 'images';
/**
* The attribute_key makes it possible
* to save multiple images fields per model
*
* @var string
*/
public $attribute_key = '';
/**
* Image properties for the upload
*
* @var array
*/
public $imageProperties = [];
/**
* @var
*/
protected $originalFilename;
/**
* Check if the upload is comming from a dynamic page
*
* @var bool
*/
protected $dynamic = false;
/**
* @var string $imageableType
*/
private $imageableType;
/**
* @var CropperInterface $imageCropper
*/
private $imageCropper;
/**
* ImageService constructor.
* Set the default upload option
*
*/
public function __construct()
{
$this->imageProperties = [
// (new ImageProperty())->setName('original')->setCropMethod(ImageProperty::CropFit)->setWidth(2000)->setHeight(8000), //exhausts php memory on some installations and causes errors like this: Allowed memory size of 134217728 bytes exhausted (tried to allocate 38400001 bytes or internal server errors
(new ImageProperty())->setName('original')->setCropMethod(ImageProperty::Resize)->setWidth(2000),
(new ImageProperty())->setName('large')->setCropMethod(ImageProperty::Resize)->setWidth(1440),
(new ImageProperty())->setName('medium')->setCropMethod(ImageProperty::Resize)->setWidth(800),
(new ImageProperty())->setName('small')->setCropMethod(ImageProperty::Resize)->setWidth(425),
(new ImageProperty())->setName('thumb')->setCropMethod(ImageProperty::Fit)->setWidth(128)
];
$this->imageCropper = app()->make(CropperInterface::class);
}
/**
* Must be a classname the extends an eloquent model (Illuminate\Database\Eloquent)
*/
public function setImageableType($className)
{
$testModel = new $className;
if (!is_a($testModel, Model::class)) throw new \InvalidArgumentException("The imageable type should reference to a class that is an extension of \Illuminate\Database\Eloquent. But this wasn't the case for class '".get_class($className)."'");
$this->imageableType = $className;
}
/**
* Set a value to an variable
*
* @param $variable name of the variable
* @param null $value
*/
public function setUp($variable, $value = null)
{
if ($value) $this->$variable = $value;
}
/**
* Returns the absolute filesystem path on where to upload images
* Also takes the subfolder into account.
*
* @see ImageService::setSubFolder()
* @return string $absoluteFileSystemPath
*/
public function getAbsoluteFileSystemPath()
{
return $this->subfolder == null ? \Config::get('kms.paths.full_path_images') : \Config::get('kms.paths.full_path_images') . '/' . $this->subfolder;
}
/**
* Returns the relative path on where to upload images.
* It is relative to the domain name.
* Also takes the subfolder into account.
*
* @see ImageService::setSubFolder()
*/
public function getRelativePath()
{
return $this->subfolder == null ? \Config::get('kms.paths.path_images') : \Config::get('kms.paths.path_images') . '/' . $this->subfolder;
}
/**
* Move the uploaded images to their destination
*
* @param array $files
* @param ImageProperty[] $imageProperties
* @return array
* @throws \Exception
*/
public function uploadImages($files, array $imageProperties = [])
{
//Validate that each of the values of $fileSizes is an ImageProperty
foreach($imageProperties as $fileSize) if(is_a($fileSize, ImageProperty::class) === false) throw new \InvalidArgumentException("The fileSizes must be an array of AbstractImageProperty instances but wasn't");
//If filesizes is empty use the default from $this->options
if (!empty($imageProperties)) $this->imageProperties = $imageProperties;
//if( $files == null || count(array_filter($files)) == 0)return array();
$images = [];
foreach ($files as $key => $file) {
//If it is an empty file, continue
if ($file == null) continue;
//Get the original filename
$this->originalFilename = \Str::slug(basename($file->getClientOriginalName(), $file->getClientOriginalExtension())) . '.' . $file->getClientOriginalExtension();
//
// if (isset($file->tmpName) && $file->tmpName == 'file') {
// $images[$key]['thumb'] = $this->formatImage('thumb', $file);
// continue;
// }
//Move uploaded file to a temporary place where from we can format (crop etc) it.
$tempDir = $this->getTempDir();
$tempName = str_random(10).'.'.$file->getClientOriginalExtension();
$recreate = true;
while($recreate)
{
if(file_exists($tempDir.$tempName) === false) {
$recreate = false;
} else {
$tempName = $tempName = str_random(10).'.'.$file->getClientOriginalExtension();
}
}
$file->move($tempDir, $tempName);
$file = $tempDir.$tempName;
foreach($this->imageProperties as $imageProperty) {
// if($sizeName == "original") continue;
$path = $this->formatImage($imageProperty, $file);
if(!$path) break;
$images[$key][$imageProperty->getName()] = $path;
}
if (file_exists($file)) unlink($file);
}
//Return the paths for the uploaded images so that we can link them further in the flow in the database
return $images;
}
/**
* Resize and save an image to the server
*
* @param ImageProperty $imageProperty
* @param $file string|\Symfony\Component\HttpFoundation\File\UploadedFile
* @return string
* @throws \Exception
*/
// protected function formatImage($name, $file)
protected function formatImage(ImageProperty $imageProperty, $file)
{
//If there is no name, return empty string
if ($imageProperty->getName() == '') return '';
if (!is_string($file)) {
$wasA = (is_object($file)) ? $wasA = get_class($file) : gettype($file);
throw new \InvalidArgumentException("The image must be a string but wasn't. It was a: " . $wasA);
}
//NOTE: if you get an exception like this: UnexpectedValueException in Common.php line 196. Unable to open file (filename).
//You must set gd.jpeg_ignore_warning in php ini to 1.
if(!$this->imageCropper->open($file)) return false;
$this->imageCropper->enableProgressive(true);
// $image->useFallback(false); //don't use a fallback. to make sure we never hit it by fixing bugs that trigger the fallback
//Create an unique filename
$filename = time() . '-' . $imageProperty->getName() . '-' . $this->originalFilename;
//switch between the methods
switch ($imageProperty->getCropMethod()) {
case ImageProperty::Fit:
//This will fit the image between the set with and height, but keep de aspect ratio, and saves it to the server.
$this->imageCropper->fit($imageProperty->getWidth(), $imageProperty->getHeight() == 0 ? null : $imageProperty->getHeight());
$this->imageCropper->save($this->getAbsoluteFileSystemPath() . '/' . $filename);
$this->imageCropper->destroy();
// dd('fit: '.$this->options[$name]['width']." x ".$this->options[$name]['height']);
break;
case ImageProperty::Crop:
//This will crop the image between the set with and height, and saves it to the server.
$this->imageCropper->crop($imageProperty->getWidth(), $imageProperty->getHeight());
$this->imageCropper->save($this->getAbsoluteFileSystemPath() . '/' . $filename);
$this->imageCropper->destroy();
break;
case ImageProperty::Resize:
//This will crop the image between the set with and height, and saves it to the server.
$this->imageCropper->resize($imageProperty->getWidth(), $imageProperty->getHeight());
$this->imageCropper->save($this->getAbsoluteFileSystemPath() . '/' . $filename);
$this->imageCropper->destroy();
break;
}
return $this->getRelativePath() . '/' . $filename;
}
/***
* Sets the subfolder in where to upload the images in.
* This folder must be a child of wwwwroot/uploads/images/
* and wil be created if it does not exist
*
* @param string $value
*/
public function setSubFolder($value) {
if(file_exists($this->getAbsoluteFileSystemPath()) === false) @mkdir($this->getAbsoluteFileSystemPath(), 0775, true);
$this->subfolder = $value;
}
/**
* Gets a temp directory to temporary store files in. You are responsible for cleaning it when you are done.
*
* @throws \InvalidArgumentException when the temp directory did not exist and could not be created.
* @return string
*/
public function getTempDir()
{
$tempDir = storage_path().DIRECTORY_SEPARATOR.'app'.DIRECTORY_SEPARATOR.'temp'.DIRECTORY_SEPARATOR;
if(file_exists($tempDir) === false) {
if (mkdir($tempDir, 0775, true) === false)
throw new \InvalidArgumentException("The image service needed a temporary directory and tried creating it because it did not exist. But it could not be created. Check folder permissions. Tried creating: ".$tempDir);
}
return $tempDir;
}
/**
* Attaches images to a model and saves it to the database. Or detaches images from a model and saves that to the database
* Used for when you've uploaded images but need database entries to link them to imageable models
*
* @param $images array An array with keys 'uploaded' and / or 'deleted' containing arrays containing one or more keys named like this:
* original, large, medium, small, thumb representing uploaded and deleted files that need to be updated in the database.
* @param $model ImageableAttribute An eloquent model where to save the images for. If you don't specify it it won't be linked
* to a model but will be added to the database without the link (imageable_id). In this case it will add the id of the image id key to
* the correct uploaded file array.
*
* @return array $images the input array with ids if no model was specified
*/
public function updateImageReferencesInDatabase($images, $model = null):array
{
if($model && !is_a($model,Model::class)) {
throw new \InvalidArgumentException("The model must be a child of a eloquent model(".Model::class."). But was a: ".get_class($model));
}
if(!is_array($images)) {
dd(debug_backtrace()); //debugging helper
throw new \InvalidArgumentException("The images must be an array containing keys 'uploaded' and / or 'deleted' containing other arrays with one or more keys like: id, original, large, medium, small, thumb.");
}
if(isset($images['uploaded'])) {
//Uploaded images need to be linked
foreach ($images['uploaded'] as $index => $imageData) {
$image = new Image();
if (isset($imageData['original'])) $image->original_image_url = $imageData['original'];
if (isset($imageData['large'])) $image->large_image_url = $imageData['large'];
if (isset($imageData['medium'])) $image->medium_image_url = $imageData['medium'];
if (isset($imageData['small'])) $image->small_image_url = $imageData['small'];
if (isset($imageData['thumb'])) $image->thumb_image_url = $imageData['thumb'];
$image->imageable_type = get_class($model);
if($model) $image->imageable_id = $model->id;
$image->save();
$images['uploaded'][$index]['id'] = $image->id;
}
}
elseif(isset($images['deleted']))
{
foreach ($images['deleted'] as $imageData) {
if(!isset($imageData->id)) throw new \InvalidArgumentException("A image representing an deleted image must have an id");
Image::destroy($imageData->id);
}
}
return $images;
}
/**
* This method links the images with an model
*
* @param $input_key
* @param $model
* @param null $attributeKey
* @return bool
*/
public function linkImagesToModel($input_key, $model, $attributeKey = null)
{
//check if the the input for the $input_key exists
if (!\Input::has($input_key)) return null;
//Get decode the json from the input
$imagesIds = json_decode(\Input::get($input_key));
if (!$attributeKey) $attributeKey = $input_key;
//Load the images for an given model and the chosen attribute_key
$modelImages = Image::where('imageable_id', $model->id)
->where('imageable_type', get_class($model))
->where('attribute_key', '=', $attributeKey)
->get()
->keyBy('id');
//IF there are no images, stop
if (!$images = Image::whereIn('id', $imagesIds)->get()) return false;
//Loop trougth the images
foreach ($imagesIds as $key => $imagesId) {
if (!$image = $images->find($imagesId)) continue;
//Set model id
$image->imageable_id = $model->id;
//Set model class
$image->imageable_type = get_class($model);
//attribute_key
$image->attribute_key = $attributeKey;
//set the sort order, the key +1
$image->sort_order = $key + 1;
//save
$image->save();
if (isset($modelImages[$imagesId])) unset($modelImages[$imagesId]);
}
//Disable old images
$this->disableOldImage($modelImages, $attributeKey);
return $images;
}
/**
* This links the images to an dynamic page
*
* @param $imagesIds
* @param $blockId
* @return array|bool
*/
public function linkDynamicPageImages($imagesIds, $blockId)
{
if (!$images = Image::whereIn('id', $imagesIds)->get()) return false;
$listImages = [];
foreach ($imagesIds as $key => $imagesId) {
if (!$image = $images->find($imagesId)) continue;
$image->sort_order = $key + 1;
$image->imageable_id = $blockId;
$image->save();
$listImages[] = $image;
}
return $listImages;
}
/**
* Disable the old images, and filter
*
* @param $images
* @param null $attributeKey
*/
public function disableOldImage($images, $attributeKey = null)
{
$this->filterOldImages($attributeKey);
Image::whereIn('id', $images->modelKeys())->update(['imageable_id' => 0]);
}
/**
* This filters old images and deletes these
*
* @param null $attributeKey
*/
public function filterOldImages($attributeKey = null)
{
$images = Image::where('attribute_key', '=', $attributeKey)
->where(function ($query) {
$query->where('imageable_id', '=', 0)
->orWhere('imageable_id', '=', '');
})
->get();
$this->deleteImages($images);
}
/**
* Delete all imageSizes for a given image from the server
* and return an numeric indexed array containing arrays representing images.
* These arrays have keys original large medium small thumb
*
* @param array $images
* @return array an array of deleted files
*/
public function deleteImages($images)
{
$deletedImages = [];
foreach ($images as $image) {
$index = count($deletedImages);
$deletedImages[$index] = [];
if(isset($image->original_image_url)) {
$deletedImages[$index]['original'] = $image->original_image_url;
$this->deleteFile(public_path() . '/' . $image->original_image_url);
}
if(isset($image->large_image_url)) {
$deletedImages[$index]['large'] = $image->large_image_url;
$this->deleteFile(public_path() . '/' . $image->large_image_url);
}
if(isset($image->medium_image_url)) {
$deletedImages[$index]['medium'] = $image->medium_image_url;
$this->deleteFile(public_path() . '/' . $image->medium_image_url);
}
if(isset($image->small_image_url)) {
$deletedImages[$index]['small'] = $image->small_image_url;
$this->deleteFile(public_path() . '/' . $image->small_image_url);
}
if(isset($image->thumb_image_url)) {
$deletedImages[$index]['thumb'] = $image->thumb_image_url;
$this->deleteFile(public_path() . '/' . $image->thumb_image_url);
}
Image::destroy($image->id);
}
return $deletedImages;
}
/**
* Check if the file exist, and delete this
*
* @param $location
* @return bool
*/
public function deleteFile($location)
{
if (!is_file($location)) return false;
unlink($location);
}
/**
* Clean the images that belong to an dynamic page
*
* @param $fileIds
* @param $blockId
*/
public function cleanDynamicPageImages($fileIds, $blockId)
{
$images = Image::where('imageable_id', '=', $blockId)
->where('imageable_type', '=', 'dynamic_page')
->whereNotIn('id', $fileIds)
->get();
$this->deleteImages($images);
}
/**
* Does have a look at value of a given Input variable from Input::get() and checks if it needs to upload or delete images.
* And returns an array telling you what happened to the inputted files.
*
* @param $input array Must be a numeric indexed array.
* Each item must have an id that references to an image id or null if it is an image that i just uploaded
* Items that have a 'delete' property indicate that the image already exists and that it must or must not be deleted.
* Items that don't have a 'deleted' property are considered not be changed at all.
*
* @param AbstractImageProperty[] $ImageProperties
* @return array containing keys: uploaded, deleted and notChanged. Each containing arrays representing images.
* the keys of the image arrays can be: original large medium small thumb
* @throws \Exception
*/
public function uploadAndDeleteImagesFromInput($input, array $ImageProperties)
{
$images = json_decode($input);
if(!$images) return;
if (!$images) {
$this->value = '';
return;
}
//Put images to upload in the $filesToUpload array. And images to delete in the $filesToDelete array. And images that not changed in the notChanged array. This latter one isn't used but added for completion
$notChangedFiles = [];
$filesToUpload = [];
$filesToDelete = [];
foreach($images as $imageObject) {
if(!property_exists($imageObject, 'id')) continue;
if(property_exists($imageObject, "delete")) {
//The image is a modified one.
if ($imageObject->delete == false) {
$currentImage = \Input::file($imageObject->name);
$filesToUpload[] = $currentImage;
} else {
$currentImage = Image::find($imageObject->id);
$filesToDelete[] = $currentImage;
}
} else {
//The image did not change so we should not do anything with it.
$currentImage = Image::find($imageObject->id);
$notChangedFiles[] = $currentImage;
}
}
// $imageService->setSubFolder($this->subFolder);
$uploadedImages = $this->uploadImages($filesToUpload, $ImageProperties); //returns an numeric indexed array containing arrays representing images. the keys of the image arrays can be: original large medium small thumb
$deletedImages = $this->deleteImages($filesToDelete); //returns an numeric indexed array containing arrays representing images. the keys of the image arrays can be: original large medium small thumb
return [
'uploaded' => $uploadedImages,
'deleted' => $deletedImages,
'notChanged' => $notChangedFiles
];
}
/**
* Deletes all images that are linked to the eloquent model
*
* @param Model $model
*/
public function deleteModelImages(Model $model)
{
$imageableType = get_class($model);
$imageableId = $model->id;
if(!is_int($imageableId)) return;
//TODO: Also delete real files
$images = Image::where(['imageable_type' => $imageableType, 'imageable_id' => $imageableId])->get();
$images->each(function($image) {
/** @var $image Image */
$image->delete();
});
}
/**
* Get the images of a dynamic block
*
* @param $fileIds
* @return array
*/
public function getDynamicBlockImages($fileIds)
{
$imageData = [];
foreach($fileIds as $fileId)
{
//containing arrays containing one or more keys named like this: original, large, medium, small, thumb representing uploaded and deleted files that need to be updated in the database.
$image = Image::find($fileId);
if(!$image) continue;
$imageToPutInArray = [
'original' => $image->original_image_url,
'large' => $image->large_image_url,
'medium' => $image->medium_image_url,
'small' => $image->small_image_url,
'thumb' => $image->thumb_image_url,
'id' => $image->id
];
$imageData[] = $imageToPutInArray;
}
return $imageData;
}
private function getImagesForModel(Model $model)
{
}
}