ORIENT/plugins/toughdeveloper/imageresizer/classes/Image.php

327 lines
9.3 KiB
PHP
Raw Permalink Normal View History

2021-06-02 11:07:53 +00:00
<?php namespace ToughDeveloper\ImageResizer\Classes;
use ToughDeveloper\ImageResizer\Models\Settings;
use October\Rain\Database\Attach\File;
use Tinify\Tinify;
use Tinify\Source;
class Image
{
/**
* File path of image
*/
protected $filePath;
/**
* Image Resizer Settings
*/
protected $settings;
/**
* File Object
*/
protected $file;
/**
* Options Array
*/
protected $options;
/**
* Thumb filename
*/
protected $thumbFilename;
public function __construct($filePath = false)
{
// Settings are needed often, so offset to variable
$this->settings = Settings::instance();
// Create a new file object
$this->file = new File;
if ($filePath instanceof File) {
$this->filePath = $filePath->getLocalPath();
return;
}
$this->filePath = (file_exists($filePath))
? $filePath
: $this->parseFileName($filePath);
}
/**
* Resizes an Image
*
* @param integer $width The target width
* @param integer $height The target height
* @param array $options The options
*
* @return string
*/
public function resize($width = false, $height = false, $options = [])
{
// Parse the default settings
$this->options = $this->parseDefaultSettings($options);
// Not a file? Display the not found image
if (!is_file($this->filePath)) {
return $this->notFoundImage($width, $height);
}
// If extension is auto, set the actual extension
if (strtolower($this->options['extension']) == 'auto') {
$this->options['extension'] = pathinfo($this->filePath)['extension'];
}
// Set a disk name, this enables caching
$this->file->disk_name = $this->diskName();
// Set the thumbfilename to save passing variables to many functions
$this->thumbFilename = $this->getThumbFilename($width, $height);
// If the image is cached, don't try resized it.
if (! $this->isImageCached()) {
// Set the file to be created from another file
$this->file->fromFile($this->filePath);
// Resize it
$thumb = $this->file->getThumb($width, $height, $this->options);
// Not a gif file? Compress with tinyPNG
if ($this->isCompressionEnabled()) {
$this->compressWithTinyPng();
}
// Touch the cached image with the original mtime to align them
touch($this->getCachedImagePath(), filemtime($this->filePath));
$this->deleteTempFile();
}
// Return the URL
return $this;
}
/**
* Gets the path for the thumbnail
* @return string
*/
public function getCachedImagePath($public = false)
{
$filePath = $this->file->getStorageDirectory() . $this->getPartitionDirectory() . $this->thumbFilename;
if ($public === true) {
return url('/storage/app/' . $filePath);
}
return storage_path('app/' . $filePath);
}
protected function deleteTempFile()
{
$path = storage_path('app/' . $this->file->getStorageDirectory() . $this->getPartitionDirectory() . $this->file->disk_name);
if (file_exists($path)) {
unlink($path);
}
}
/**
* Parse the file name to get a relative path for the file
* This is mostly required for scenarios where a twig filter, e.g. theme has been applied.
* @return string
*/
protected function parseFileName($filePath)
{
$path = urldecode(parse_url($filePath, PHP_URL_PATH));
// Create array of commonly used folders
// These will be used to try capture the actual file path to an image without the sub-directory path
$folders = [
config('cms.themesPath'),
config('cms.pluginsPath'),
config('cms.storage.uploads.path'),
config('cms.storage.media.path')
];
foreach($folders as $folder)
{
if (str_contains($path, $folder))
{
$paths = explode($folder, $path, 2);
return base_path($folder . end($paths));
}
}
return base_path($path);
}
/**
* Works out the default settings
* @return string
*/
protected function parseDefaultSettings($options = [])
{
if (!isset($options['mode']) && $this->settings->default_mode) {
$options['mode'] = $this->settings->default_mode;
}
if (!isset($options['offset']) && is_int($this->settings->default_offset_x) && is_int($this->settings->default_offset_y)) {
$options['offset'] = [$this->settings->default_offset_x, $this->settings->default_offset_y];
}
if (!isset($options['extension']) && $this->settings->default_extension) {
$options['extension'] = $this->settings->default_extension;
}
if (!isset($options['quality']) && is_int($this->settings->default_quality)) {
$options['quality'] = $this->settings->default_quality;
}
if (!isset($options['sharpen']) && is_int($this->settings->default_sharpen)) {
$options['sharpen'] = $this->settings->default_sharpen;
}
if (!isset($options['compress'])) {
$options['compress'] = true;
}
return $options;
}
/**
* Creates a unique disk name for an image
* @return string
*/
protected function diskName()
{
$diskName = $this->filePath;
// Ensures a unique filepath when tinypng compression is enabled
if ($this->isCompressionEnabled()) {
$diskName .= 'tinypng';
}
return md5($diskName);
}
/**
* Serves a not found image
* @return string
*/
protected function notFoundImage($width, $height)
{
// Have we got a custom not found image? If so, serve this.
if ($this->settings->not_found_image) {
$imagePath = base_path() . config('cms.storage.media.path') . $this->settings->not_found_image;
}
// If we do not have an existing custom not found image, use the default from this plugin
if (!isset($imagePath) || !file_exists($imagePath)) {
$imagePath = plugins_path('toughdeveloper/imageresizer/assets/default-not-found.jpg');
}
// Create a new Image object to resize
$file = new Self($imagePath);
// Return in the specified dimensions
return $file->resize($width, $height, [
'mode' => 'crop'
]);
}
/**
* Compresses a png image using tinyPNG
* @return string
*/
protected function compressWithTinyPng()
{
try {
Tinify::setKey($this->settings->tinypng_developer_key);
$filePath = $this->getCachedImagePath();
$source = Source::fromFile($filePath);
$source->toFile($filePath);
}
catch (\Exception $e) {
// Log error - may help debug
\Log::error('Tiny PNG compress failed', [
'message' => $e->getMessage(),
'code' => $e->getCode()
]);
}
}
/**
* Checks if the requested resize/compressed image is already cached.
* Removes the cached image if the original image has a different mtime.
*
* @return bool
*/
protected function isImageCached()
{
// if there is no cached image return false
if (!is_file($cached_img = $this->getCachedImagePath())) {
return false;
}
// if cached image mtime match, the image is already cached
if (filemtime($this->filePath) === filemtime($cached_img)) {
return true;
}
// delete older cached file
unlink($cached_img);
// generate new cache file
return false;
}
/**
* Checks if image compression is enabled for this image.
* @return bool
*/
protected function isCompressionEnabled()
{
return ($this->options['extension'] != 'gif' && $this->settings->enable_tinypng && $this->options['compress']);
}
/**
* Generates a partition for the file.
* return /ABC/DE1/234 for an name of ABCDE1234.
* @param Attachment $attachment
* @param string $styleName
* @return mixed
*/
protected function getPartitionDirectory()
{
return implode('/', array_slice(str_split($this->diskName(), 3), 0, 3)) . '/';
}
/**
* Generates a thumbnail filename.
* @return string
*/
protected function getThumbFilename($width, $height)
{
$width = (integer) $width;
$height = (integer) $height;
return 'thumb__' . $width . '_' . $height . '_' . $this->options['offset'][0] . '_' . $this->options['offset'][1] . '_' . $this->options['mode'] . '.' . $this->options['extension'];
}
/**
* Render an image tag
* @return string
*/
public function render()
{
return '<img src="' . $this . '" />';
}
/**
* Magic method to return the file path
* @return string
*/
public function __toString()
{
return $this->getCachedImagePath(true);
}
}