From 7ba8ce326fe60aebb33ddc0d136e2be888db4a8b Mon Sep 17 00:00:00 2001 From: FK Date: Mon, 19 Apr 2021 13:46:28 +0200 Subject: [PATCH] Media refactoring - add configurable optional image resizing in upload (for downsizing huge uploaded images) - add configurable optional image compressing in upload - add configurable optional disabling image upsizing (enabled by default) - remove rotate method in MediaService (delegator to ImageRotateService) - partial cleanup media services --- Engine/Modules/Media/Bootstrap.php | 159 +++++++++++++++--- .../Backend/Media/AjaxController.php | 11 +- .../Media/Services/ImageCompressService.php | 68 +++++--- .../Media/Services/ImageRotateService.php | 117 +------------ .../Modules/Media/Services/MediaService.php | 42 +++-- .../Frontend/ImageUploadController.php | 26 ++- 6 files changed, 239 insertions(+), 184 deletions(-) diff --git a/Engine/Modules/Media/Bootstrap.php b/Engine/Modules/Media/Bootstrap.php index 38b817f9a..7ceed388d 100644 --- a/Engine/Modules/Media/Bootstrap.php +++ b/Engine/Modules/Media/Bootstrap.php @@ -2,27 +2,28 @@ namespace Oforge\Engine\Modules\Media; -use Doctrine\ORM\OptimisticLockException; -use Doctrine\ORM\ORMException; +use Exception; use Oforge\Engine\Modules\AdminBackend\Core\Services\BackendNavigationService; use Oforge\Engine\Modules\Core\Abstracts\AbstractBootstrap; -use Oforge\Engine\Modules\Core\Exceptions\ServiceNotFoundException; -use Oforge\Engine\Modules\Core\Exceptions\Template\TemplateNotFoundException; +use Oforge\Engine\Modules\Core\Models\Config\ConfigType; +use Oforge\Engine\Modules\Core\Services\ConfigService; +use Oforge\Engine\Modules\I18n\Helper\I18N; use Oforge\Engine\Modules\Media\Models\Media; use Oforge\Engine\Modules\Media\Services\ImageCompressService; use Oforge\Engine\Modules\Media\Services\MediaService; use Oforge\Engine\Modules\Media\Twig\MediaExtension; use Oforge\Engine\Modules\TemplateEngine\Core\Services\TemplateRenderService; -use Twig_Error_Loader; /** * Class Bootstrap * * @package Oforge\Engine\Modules\Media */ -class Bootstrap extends AbstractBootstrap { +class Bootstrap extends AbstractBootstrap +{ - public function __construct() { + public function __construct() + { $this->endpoints = [ Controller\Backend\Media\AjaxController::class, Controller\Backend\Media\MediaController::class, @@ -37,27 +38,143 @@ public function __construct() { 'media' => MediaService::class, 'image.compress' => ImageCompressService::class, ]; + + $this->setConfiguration( + 'settingGroups', + [ + [ + 'name' => 'media', + 'label' => [ + 'en' => 'Media', + 'de' => 'Medien', + ], + 'items' => [ + [ + 'name' => 'media_upload_image_adjustment_enabled', + 'type' => ConfigType::BOOLEAN, + 'default' => false, + 'label' => [ + 'en' => 'Image upload: Adjustments when uploading?', + 'de' => 'Bildupload: Anpassungen beim hochladen?', + ], + 'required' => false, + ],# media_upload_image_adjustment_enabled + [ + 'name' => 'media_upload_image_adjustment_downscaling_max_width', + 'type' => ConfigType::INTEGER, + 'default' => 0, + 'label' => [ + 'en' => 'Image upload: Down scaling to max width (deactivated if 0)', + 'de' => 'Bildupload: Verkleinern auf maximale Breite (deaktiviert wenn 0)', + ], + 'required' => false, + ],# media_upload_image_adjustment_downscaling_max_width + [ + 'name' => 'media_upload_image_adjustment_compress', + 'type' => ConfigType::BOOLEAN, + 'default' => false, + 'label' => [ + 'en' => 'Image upload: Compress?', + 'de' => 'Bildupload: Komprimieren?', + ], + 'required' => false, + ],# media_upload_image_adjustment_compress + [ + 'name' => 'media_image_upscaling_enabled', + 'type' => ConfigType::BOOLEAN, + 'default' => true, + 'label' => [ + 'en' => 'Image upscaling (of small images) ?', + 'de' => 'Bild-Upscaling (von kleinen Bildern)?', + ], + 'required' => false, + ],# media_upscaling_enabled + ], + ],# media + ] + ); + } + + /** @inheritDoc */ + public function uninstall(bool $keepData) + { + if ( !$keepData) { + $this->uninstallSettings(); + } + } + + /** @inheritDoc */ + public function install() + { + $this->installSettings(); } /** @inheritDoc */ - public function activate() { + public function activate() + { /** @var TemplateRenderService $templateRenderer */ $templateRenderer = Oforge()->Services()->get('template.render'); $templateRenderer->View()->addExtension(new MediaExtension()); /** @var BackendNavigationService $backendNavigationService */ $backendNavigationService = Oforge()->Services()->get('backend.navigation'); - $backendNavigationService->add([ - 'name' => 'module_media', - 'order' => 3, - 'position' => 'sidebar', - ]); - $backendNavigationService->add([ - 'name' => 'module_media_media', - 'parent' => 'module_media', - 'icon' => 'fa fa-picture-o', - 'path' => 'backend_media', - 'position' => 'sidebar', - 'order' => 1, - ]); + $backendNavigationService->add( + [ + 'name' => 'module_media', + 'order' => 3, + 'position' => 'sidebar', + ] + ); + $backendNavigationService->add( + [ + 'name' => 'module_media_media', + 'parent' => 'module_media', + 'icon' => 'fa fa-picture-o', + 'path' => 'backend_media', + 'position' => 'sidebar', + 'order' => 1, + ] + ); + } + + /** + * + */ + protected function installSettings() + { + try { + /** @var ConfigService $configService */ + $configService = Oforge()->Services()->get('config'); + foreach ($this->getConfiguration('settingGroups') as $settingGroup) { + I18N::translate('config_group_' . $settingGroup['name'], $settingGroup['label']); + foreach ($settingGroup['items'] as $setting) { + $labelKey = 'config_' . $setting['name']; + I18N::translate($labelKey, $setting['label']); + $setting['label'] = $labelKey; + $setting['group'] = $settingGroup['name']; + $configService->add($setting); + } + } + } catch (Exception $exception) { + Oforge()->Logger()->logException($exception); + } } + + /** + * + */ + protected function uninstallSettings() + { + try { + /** @var ConfigService $configService */ + $configService = Oforge()->Services()->get('config'); + foreach ($this->getConfiguration('settingGroups') as $settingGroup) { + foreach ($settingGroup['items'] as $setting) { + $configService->remove($setting['name']); + } + } + } catch (Exception $exception) { + Oforge()->Logger()->logException($exception); + } + } + } diff --git a/Engine/Modules/Media/Controller/Backend/Media/AjaxController.php b/Engine/Modules/Media/Controller/Backend/Media/AjaxController.php index 3ff5a7d30..167b5c001 100644 --- a/Engine/Modules/Media/Controller/Backend/Media/AjaxController.php +++ b/Engine/Modules/Media/Controller/Backend/Media/AjaxController.php @@ -65,12 +65,19 @@ public function demoAction(Request $request, Response $response) { * @throws OptimisticLockException * @EndpointAction(path="/upload") */ - public function uploadAction(Request $request, Response $response) { + public function uploadAction(Request $request, Response $response) + { if (isset($_FILES['upload-media'])) { /** @var MediaService $service */ $service = Oforge()->Services()->get('media'); $created = $service->add($_FILES['upload-media']); - Oforge()->View()->assign(['created' => $created != null ? $created->toArray() : false]); + Oforge()->View()->assign( + [ + 'json' => [ + 'created' => $created !== null ? $created->toArray() : false, + ] + ] + ); } } diff --git a/Engine/Modules/Media/Services/ImageCompressService.php b/Engine/Modules/Media/Services/ImageCompressService.php index b0df1dae0..16d801cee 100644 --- a/Engine/Modules/Media/Services/ImageCompressService.php +++ b/Engine/Modules/Media/Services/ImageCompressService.php @@ -5,9 +5,8 @@ use Doctrine\ORM\ORMException; use Imagick; use ImagickException; -use Insertion\Models\InsertionMedia; -use Oforge\Engine\Modules\Core\Abstracts\AbstractDatabaseAccess; use Oforge\Engine\Modules\Core\Exceptions\ServiceNotFoundException; +use Oforge\Engine\Modules\Core\Services\ConfigService; use Oforge\Engine\Modules\Media\Models\Media; /** @@ -15,11 +14,10 @@ * * @package Oforge\Engine\Modules\Media\Services */ -class ImageCompressService extends AbstractDatabaseAccess { - - public function __construct() { - parent::__construct(['default' => Media::class, 'insertionMedia' => InsertionMedia::class]); - } +class ImageCompressService +{ + /** @var ConfigService $configService */ + private $configService; /** * @param string|null $path @@ -29,17 +27,18 @@ public function __construct() { * @throws ORMException * @throws ServiceNotFoundException */ - public function getPath(?string $path, int $width = 0) : ?string { - if (!isset($path)) { + public function getPath(?string $path, int $width = 0) : ?string + { + if ( !isset($path)) { return null; } /** @var MediaService $mediaService */ $mediaService = Oforge()->Services()->get('media'); $media = $mediaService->getByPath($path); - if (!isset($media)) { + if ( !isset($media)) { $media = $mediaService->getById($path); - if (!isset($media)) { + if ( !isset($media)) { return $path; } } @@ -47,7 +46,7 @@ public function getPath(?string $path, int $width = 0) : ?string { if ($width > 0) { $fileExtension = $this->getFileExtension($media); - if (!empty($fileExtension)) { + if ( !empty($fileExtension)) { $cacheUrl = substr($media->getPath(), 0, -strlen($fileExtension) - 1) . '_' . $width . '.' . $fileExtension; //File is already compressed and stored if (file_exists(ROOT_PATH . $cacheUrl)) { @@ -68,24 +67,24 @@ public function getPath(?string $path, int $width = 0) : ?string { return $media->getPath(); } - public function getFileExtension(Media $media) { - $fileExtension = ''; + public function getFileExtension(Media $media) : string + { $tmpFileExtension = pathinfo($media->getPath(), PATHINFO_EXTENSION); switch ($media->getType()) { case 'image/jpeg': case 'image/jpg': case 'image/png': - $fileExtension = $tmpFileExtension; - break; + return $tmpFileExtension; + default: + return ''; } - - return $fileExtension; } /** * @param string $imagePath */ - public function compress(string $imagePath) { + public function compress(string $imagePath) + { try { if (extension_loaded('imagick')) { $imagick = new Imagick(ROOT_PATH . $imagePath); @@ -97,7 +96,7 @@ public function compress(string $imagePath) { $imagick->setImageFormat('jpeg'); $imagick->setImageCompressionQuality(40); $imagick->setSamplingFactors(['2x2', '1x1', '1x1']); - //$profiles = $imagick->getImageProfiles("icc", true); + //$profiles = $imagick->getImageProfiles('icc', true); // $imagick->stripImage(); // if (!empty($profiles)) { // $imagick->profileImage('icc', $profiles['icc']); @@ -105,7 +104,6 @@ public function compress(string $imagePath) { $imagick->setInterlaceScheme(Imagick::INTERLACE_JPEG); $imagick->setColorspace(Imagick::COLORSPACE_SRGB); - } elseif ($image_types[2] === IMAGETYPE_GIF) { $imagick->setImageFormat('gif'); } elseif ($image_types[2] === IMAGETYPE_PNG) { @@ -118,21 +116,37 @@ public function compress(string $imagePath) { $imagick->writeImage(ROOT_PATH . $imagePath); } } catch (ImagickException $e) { - Oforge()->Logger()->get()->error('ImagickException', ["imagePath" => $imagePath ]); + Oforge()->Logger()->get()->error('ImagickException', ['imagePath' => $imagePath]); } } - public function scale(Media $media, int $width, string $cacheUrl) { + public function scale(Media $media, int $targetWidth, string $cacheUrl) + { + if ( !isset($this->configService)) { + $this->configService = Oforge()->Services()->get('config'); + } try { if (extension_loaded('imagick')) { $imagick = new Imagick(ROOT_PATH . $media->getPath()); - $widthCurrent = $imagick->getImageWidth(); - $heightCurrent = $imagick->getImageHeight(); - $imagick->scaleImage($width, (int) (1.0 * $width / $widthCurrent * $heightCurrent)); + $currentWidth = $imagick->getImageWidth(); + $currentHeight = $imagick->getImageHeight(); + + if ($currentWidth > $targetWidth || $this->configService->get('media_image_upscaling_enabled')) { + $imagick->scaleImage($targetWidth, (int)(1.0 * $targetWidth / $currentWidth * $currentHeight)); + } + $imagick->writeImage(ROOT_PATH . $cacheUrl); } } catch (ImagickException $e) { - Oforge()->Logger()->get()->error('ImagickException', ["media" => $media->toArray(1),"width" => $width, "cacheUrl" => $cacheUrl ]); + Oforge()->Logger()->get()->error( + 'ImagickException', + [ + 'media' => $media->toArray(1), + 'targetWidth' => $targetWidth, + 'cacheUrl' => $cacheUrl, + ] + ); } } + } diff --git a/Engine/Modules/Media/Services/ImageRotateService.php b/Engine/Modules/Media/Services/ImageRotateService.php index 58f202fef..47ba552a1 100644 --- a/Engine/Modules/Media/Services/ImageRotateService.php +++ b/Engine/Modules/Media/Services/ImageRotateService.php @@ -2,12 +2,8 @@ namespace Oforge\Engine\Modules\Media\Services; -use Doctrine\ORM\ORMException; use Imagick; use ImagickException; -use Insertion\Models\InsertionMedia; -use Oforge\Engine\Modules\Core\Abstracts\AbstractDatabaseAccess; -use Oforge\Engine\Modules\Core\Exceptions\ServiceNotFoundException; use Oforge\Engine\Modules\Media\Models\Media; /** @@ -15,122 +11,19 @@ * * @package Oforge\Engine\Modules\Media\Services */ -class ImageRotateService extends AbstractDatabaseAccess { +class ImageRotateService +{ - public function __construct() { - parent::__construct(['default' => Media::class, 'insertionMedia' => InsertionMedia::class]); - } - - /** - * @param string|null $path - * @param int $width - * - * @return string|null - * @throws ORMException - * @throws ServiceNotFoundException - */ - public function getPath(?string $path, int $width = 0) : ?string { - if (!isset($path)) { - return null; - } - /** @var MediaService $mediaService */ - $mediaService = Oforge()->Services()->get('media'); - $media = $mediaService->getByPath($path); - - if (!isset($media)) { - $media = $mediaService->getById($path); - if (!isset($media)) { - return $path; - } - } - - if ($width > 0) { - $fileExtension = $this->getFileExtension($media); - - if (!empty($fileExtension)) { - $cacheUrl = substr($media->getPath(), 0, -strlen($fileExtension) - 1) . '_' . $width . '.' . $fileExtension; - //File is already compressed and stored - if (file_exists(ROOT_PATH . $cacheUrl)) { - return $cacheUrl; - } - //File should be compressed - if (extension_loaded('imagick')) { - $this->scale($media, $width, $cacheUrl); - $this->compress($cacheUrl); - - if (file_exists(ROOT_PATH . $cacheUrl)) { - return $cacheUrl; - } - } - } - } - - return $media->getPath(); - } - - public function getFileExtension(Media $media) { - $fileExtension = ''; - $tmpFileExtension = pathinfo($media->getPath(), PATHINFO_EXTENSION); - switch ($media->getType()) { - case 'image/jpeg': - case 'image/jpg': - case 'image/png': - $fileExtension = $tmpFileExtension; - break; - } - - return $fileExtension; - } - - /** - * @param string $imagePath - */ - public function compress(string $imagePath) { - try { - if (extension_loaded('imagick')) { - $imagick = new Imagick(ROOT_PATH . $imagePath); - $image_types = getimagesize(ROOT_PATH . $imagePath); - // Compress image - - // Set image as based its own type - if ($image_types[2] === IMAGETYPE_JPEG) { - $imagick->setImageFormat('jpeg'); - $imagick->setImageCompressionQuality(40); - $imagick->setSamplingFactors(['2x2', '1x1', '1x1']); - //$profiles = $imagick->getImageProfiles("icc", true); - // $imagick->stripImage(); - // if (!empty($profiles)) { - // $imagick->profileImage('icc', $profiles['icc']); - // } - - $imagick->setInterlaceScheme(Imagick::INTERLACE_JPEG); - $imagick->setColorspace(Imagick::COLORSPACE_SRGB); - - } elseif ($image_types[2] === IMAGETYPE_GIF) { - $imagick->setImageFormat('gif'); - } elseif ($image_types[2] === IMAGETYPE_PNG) { - // $imagick->stripImage(); - $imagick->setImageDepth(8); - } else { - // not supported file type - } - - $imagick->writeImage(ROOT_PATH . $imagePath); - } - } catch (ImagickException $e) { - Oforge()->Logger()->get()->error('ImagickException', $e->getTrace()); - } - } - - public function rotate(Media $media, int $rotate) { + public function rotate(Media $media, int $rotate) + { if (extension_loaded('imagick')) { try { $imagick = new Imagick(ROOT_PATH . $media->getPath()); $imagick->rotateImage('#00000000', $rotate); - } catch (ImagickException $e) { Oforge()->Logger()->get()->error('ImagickException', $e->getTrace()); } } } + } diff --git a/Engine/Modules/Media/Services/MediaService.php b/Engine/Modules/Media/Services/MediaService.php index 4adef953a..c9fdc1974 100644 --- a/Engine/Modules/Media/Services/MediaService.php +++ b/Engine/Modules/Media/Services/MediaService.php @@ -2,13 +2,14 @@ namespace Oforge\Engine\Modules\Media\Services; -use Doctrine\ORM\OptimisticLockException; use Doctrine\ORM\ORMException; use Doctrine\ORM\Tools\Pagination\Paginator; +use Exception; use Oforge\Engine\Modules\Core\Abstracts\AbstractDatabaseAccess; use Oforge\Engine\Modules\Core\Exceptions\ServiceNotFoundException; use Oforge\Engine\Modules\Core\Helper\FileSystemHelper; use Oforge\Engine\Modules\Core\Helper\Statics; +use Oforge\Engine\Modules\Core\Services\ConfigService; use Oforge\Engine\Modules\Media\Models\Media; /** @@ -41,25 +42,43 @@ public function add($file, $prefix = null, $owner = null) : ?Media { $filename = strtolower($prefix . '_' . $filename); } - $relativeFilePath = Statics::IMAGES_DIR . Statics::GLOBAL_SEPARATOR . substr(md5(rand()), 0, 2) . Statics::GLOBAL_SEPARATOR . substr(md5(rand()), 0, 2) - . Statics::GLOBAL_SEPARATOR . $filename; + $relativeFilePath = implode( + Statics::GLOBAL_SEPARATOR, + [ + Statics::IMAGES_DIR, + substr(md5(rand()), 0, 2), + substr(md5(rand()), 0, 2), + $filename, + ] + ); FileSystemHelper::mkdir(dirname(ROOT_PATH . $relativeFilePath)); if (move_uploaded_file($file['tmp_name'], ROOT_PATH . $relativeFilePath)) { - /** @var ImageCompressService $imageCompressService */ - $imageCompressService = Oforge()->Services()->get('image.compress'); $size = getimagesize(ROOT_PATH . $relativeFilePath); - $media = Media::create([ 'type' => $file['type'], 'name' => urlencode($filename), 'path' => str_replace('\\', '/', $relativeFilePath), 'owner' => $owner ]); - - // $media = $imageCompressService->compress($media); $this->entityManager()->create($media); + try { + /** @var ConfigService $configService */ + $configService = Oforge()->Services()->get('config'); + if ($configService->get('media_upload_image_adjustment_enabled')) { + /** @var ImageCompressService $imageCompressService */ + $imageCompressService = Oforge()->Services()->get('image.compress'); + if (($downscalingMaxWidth = $configService->get('media_upload_image_adjustment_downscaling_max_width')) > 0) { + $imageCompressService->scale($media, $downscalingMaxWidth, $media->getPath()); + } + if ($configService->get('media_upload_image_adjustment_compress')) { + $imageCompressService->compress($media->getPath()); + } + } + } catch (Exception $exception) { + Oforge()->Logger()->logException($exception); + } return $media; } @@ -155,13 +174,6 @@ public function deleteThumbnails() { } } - public function rotate(Media $media, int $angle) { - /** @var ImageRotateService $imageRotateService */ - $imageRotateService = Oforge()->Services()->get('image.rotate'); - - $imageRotateService->rotate($media, $angle); - } - public function download($photoURL, $filename, $type) { $relativeFilePath = Statics::IMAGES_DIR . Statics::GLOBAL_SEPARATOR . substr(md5(rand()), 0, 2) . Statics::GLOBAL_SEPARATOR . substr(md5(rand()), 0, 2) . Statics::GLOBAL_SEPARATOR . $filename; diff --git a/Plugins/ImageUpload/Controller/Frontend/ImageUploadController.php b/Plugins/ImageUpload/Controller/Frontend/ImageUploadController.php index cad0cb250..243059452 100644 --- a/Plugins/ImageUpload/Controller/Frontend/ImageUploadController.php +++ b/Plugins/ImageUpload/Controller/Frontend/ImageUploadController.php @@ -8,6 +8,7 @@ use Oforge\Engine\Modules\Core\Annotation\Endpoint\EndpointAction; use Oforge\Engine\Modules\Core\Annotation\Endpoint\EndpointClass; use Oforge\Engine\Modules\Core\Exceptions\ServiceNotFoundException; +use Oforge\Engine\Modules\Media\Services\ImageRotateService; use Oforge\Engine\Modules\Media\Services\MediaService; use Slim\Http\Request; use Slim\Http\Response; @@ -77,24 +78,35 @@ public function indexAction(Request $request, Response $response) { * @param Response $response * * @return Response + * @throws ServiceNotFoundException * @EndpointAction() */ - public function rotateAction(Request $request, Response $response) { + public function rotateAction(Request $request, Response $response) : Response + { if ($request->isGet()) { - $id = $_GET["id"]; - $path = $_GET["path"]; + $mediaId = $_GET['id']; + $path = $_GET['path']; /** @var MediaService $mediaService */ $mediaService = Oforge()->Services()->get('media'); + /** @var ImageRotateService $imageRotateService */ + $imageRotateService = Oforge()->Services()->get('image.rotate'); + try { + $media = $mediaService->getById($mediaId); + if ($media === null || $media->getPath() !== $path) { + return $response->withStatus(404); + } + $imageRotateService->rotate($media, 90); - $media = $mediaService->getById($id); - - if ($media->getPath() == $path) { + return $response->withStatus(204); + } catch (ORMException $exception) { + Oforge()->Logger()->logException($exception); - $mediaService->rotate($id, 90); + return $response->withStatus(500); } } return $response->withStatus(403); } + }