HEX
Server: Microsoft-IIS/8.5
System: Windows NT YDAWBH120 6.3 build 9600 (Windows Server 2012 R2 Standard Edition) AMD64
User: tentjecom_web (0)
PHP: 7.4.14
Disabled: NONE
Upload Files
File: D:/HostingSpaces/SBogers10/structura.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)
    {

    }
}