<?php
declare(strict_types=1);
/**
* (c) 2Hats Logic Solutions <info@2hatslogic.com>
*/
namespace Hatslogic\Sw\ProductReviewImageUpload\Subscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Uuid\Uuid;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Shopware\Core\Content\Product\ProductEvents;
use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\FileBag;
use Shopware\Core\Framework\Util\Random;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Symfony\Component\HttpFoundation\RequestStack;
use Shopware\Core\PlatformRequest;
use League\Flysystem\Filesystem;
use League\Flysystem\Adapter\Local;
use Shopware\Core\Framework\Context;
use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
use Shopware\Core\Framework\Struct\ArrayEntity;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
use Shopware\Storefront\Event\StorefrontRenderEvent;
use Hatslogic\Sw\ProductReviewImageUpload\HatslogicSwProductReviewImageUpload;
use Shopware\Core\Content\Media\MediaType\ImageType;
use Shopware\Core\Content\Media\MediaType\MediaType;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;
class ProductReviewImageUpload implements EventSubscriberInterface
{
public const MAX_WIDTH = 800;
public const MAX_HEIGHT = 800;
/**
* @var ContainerInterface
*/
protected $container;
/**
* @var Connection
*/
private $connection;
/**
* @var SystemConfigService
*/
private $systemConfigService;
/**
* @var RequestStack
*/
private $requestStack;
/**
* @var Filesystem
*/
private $filesystem;
public function __construct(
ContainerInterface $container,
Connection $connection,
SystemConfigService $systemConfigService,
RequestStack $requestStack
) {
$this->container = $container;
$this->connection = $connection;
$this->systemConfigService = $systemConfigService;
$this->requestStack = $requestStack;
$kernal = $this->container->get('kernel');
$this->filesystem = new Filesystem(new Local($kernal->getProjectDir()));
}
public static function getSubscribedEvents(): array
{
return [
ProductEvents::PRODUCT_REVIEW_WRITTEN_EVENT => 'onProductReviewSave',
KernelEvents::REQUEST => 'kernelEventsRequest',
ProductPageLoadedEvent::class => 'onPageLoaded',
StorefrontRenderEvent::class => 'onStorefrontRender'
];
}
public function getReviewImagesById($productReviewId)
{
$criteria = (new Criteria())
->addFilter(new EqualsFilter(
's_plugin_hatslogic_product_review_image_upload_file.productReviewId',
$productReviewId
));
$result = $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->search($criteria, Context::createDefaultContext());
$productReviewImages = $result->getEntities();
return ($productReviewImages) ? $productReviewImages : null;
}
public function onStorefrontRender(StorefrontRenderEvent $event)
{
$route = $event->getRequest()->get('_route');
$config['productReviewImageUpload'] = $this->systemConfigService->get('HatslogicSwProductReviewImageUpload.config');
$event->setParameter('hatslogic', $config);
if ($route == 'frontend.product.reviews') {
$parameters = $event->getParameters();
$reviews = (isset($parameters['reviews'])) ? $parameters['reviews'] : '';
if (!empty($reviews)) {
$productReviewImagesArray = $this->getCustomerReviewImages($reviews);
$event->setParameter('page', array('extensions' => $productReviewImagesArray));
}
}
}
public function getCustomerReviewImages($reviews)
{
if (!empty($reviews)) {
$customerReviewId = '';
$customerReview = $reviews->getCustomerReview();
if (!empty($customerReview)) {
$customerReviewId = $customerReview->getId();
}
$activeReviews = $reviews->getEntities();
$productReviewImagesArray = [];
if (!empty($activeReviews)) {
foreach ($activeReviews as $review) {
if ($review->getId() != $customerReviewId) {
$productReviewImages = $this->getReviewImagesById($review->getId());
if (!empty($productReviewImages)) {
foreach ($productReviewImages as $productReviewImage) {
$productReviewImagesArray[$review->getId()][] = [
'id' => $productReviewImage->getId(),
'fileName' => $productReviewImage->getFileName(),
'mimeType' => $productReviewImage->getMimeType()
];
}
}
}
}
}
//Customer review
if (!empty($customerReview)) {
$productReviewImages = $this->getReviewImagesById($customerReviewId);
if (!empty($productReviewImages)) {
foreach ($productReviewImages as $productReviewImage) {
$productReviewImagesArray[$customerReviewId][] = [
'id' => $productReviewImage->getId(),
'fileName' => $productReviewImage->getFileName(),
'mimeType' => $productReviewImage->getMimeType()
];
}
}
}
if (!empty($productReviewImagesArray)) {
return array('productReviewImagesArray' => new ArrayEntity($productReviewImagesArray));
}
}
return false;
}
public function onPageLoaded(ProductPageLoadedEvent $event)
{
$reviews = $event->getPage()->getReviews();
if (!empty($reviews)) {
$productReviewImagesArray = $this->getCustomerReviewImages($reviews);
if ($productReviewImagesArray) {
$event->getPage()->addExtension('productReviewImagesArray', $productReviewImagesArray['productReviewImagesArray']);
return true;
}
}
return false;
}
public function onProductReviewSave(EntityWrittenEvent $event)
{
$ids = $event->getIds();
$reviewFiles = [];
$sErrorFlag = [];
$fileUploadError = false;
if (!empty($_FILES)) {
$fileBag = new FileBag($_FILES);
$reviewFiles = $fileBag->get('reviewFiles');
}
$master = $this->requestStack->getMasterRequest();
$salesChannelId = $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
$config = $this->systemConfigService->get('HatslogicSwProductReviewImageUpload.config', $salesChannelId);
$reviewFilesCount = count($reviewFiles);
if ($reviewFilesCount > $config['productReviewImageUploadMaxFiles']) {
$sErrorFlag['numberOfFilesExceed'] = true;
$fileUploadError = true;
}
if (!empty($reviewFiles) && !$fileUploadError) {
foreach ($reviewFiles as $uploadedFile) {
$validationInfo = $this->validateUploadedFile($uploadedFile, $config);
if (!$validationInfo['validStatus']) {
if ($validationInfo['fileExtensionError']) {
$sErrorFlag['fileExtensionError'] = true;
}
if ($validationInfo['fileSizeError']) {
$sErrorFlag['fileSizeError'] = true;
}
if (!empty($validationInfo['errorMessage'])) {
$sErrorFlag['errorMessage'] = $validationInfo['errorMessage'];
}
$fileUploadError = true;
break;
}
}
}
if (!$fileUploadError && !empty($reviewFiles)) {
//save files
$filesUploaded = [];
foreach ($reviewFiles as $uploadedFile) {
$uploadedNewFileName = $this->uploadFile($uploadedFile, $config);
if ($uploadedNewFileName) {
$filesUploaded[] = $uploadedNewFileName;
}
}
if (!empty($filesUploaded)) {
$this->saveImageNamesToDb($ids[0], $filesUploaded);
}
return true;
}
return false;
}
public function saveImageNamesToDb($productReviewId, $filesUploaded)
{
if (!empty($filesUploaded) && !empty($productReviewId)) {
foreach ($filesUploaded as $file) {
$productReviewImageUploadFileId = Uuid::randomHex();
$this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->create(
[
[
'id' => $productReviewImageUploadFileId,
'productReviewId' => $productReviewId,
'fileName' => $file['fileName'],
'mimeType' => $file['mimeType']
],
],
Context::createDefaultContext()
);
}
return true;
}
return false;
}
public function validateUploadedFile(UploadedFile $uploadedFile, $config)
{
$fileExtensionError = false;
$fileSizeError = false;
$validStatus = true;
$errorMessage = '';
$allowedFileTypes = $this->getAllowedFileTypes($config);
// check file type
if (!in_array(strtolower($uploadedFile->getClientOriginalExtension()), $allowedFileTypes)) {
$fileExtensionError = true;
$validStatus = false;
}
// file size from plugin config
$maxFileSize = (($config['productReviewImageUploadMaxFileSize'] * 1024) * 1024);
// check file size
if ($uploadedFile->getSize() > $maxFileSize) {
$fileSizeError = true;
$validStatus = false;
}
if ($uploadedFile->getError()) {
$validStatus = false;
$errorMessage = $uploadedFile->getErrorMessage();
}
return array(
'validStatus' => $validStatus,
'fileExtensionError' => $fileExtensionError,
'fileSizeError' => $fileSizeError,
'errorMessage' => $errorMessage
);
}
protected function uploadFile(UploadedFile $uploadedFile, array $config)
{
// move the file to the upload directory
if (!empty($uploadedFile) && !$uploadedFile->getError()) {
$kernal = $this->container->get('kernel');
$reviewImagesFolderPath = $kernal->getProjectDir() . '/' . HatslogicSwProductReviewImageUpload::FILE_UPLOAD_PATH;
$newFileName = Random::getAlphanumericString(13) . '_' . $uploadedFile->getClientOriginalName();
$uploadedFile->move($reviewImagesFolderPath, $newFileName);
if (isset($config['productReviewImageResize']) && $config['productReviewImageResize']) {
$this->resizeImage($uploadedFile, $reviewImagesFolderPath."/".$newFileName);
}
if (file_exists($uploadedFile->getPathname())) {
unlink($uploadedFile->getPathname());
}
return [
'mimeType' => $uploadedFile->getClientMimeType(),
'fileName' => $newFileName
];
}
return false;
}
private function getAllowedFileTypes($config)
{
$fileTypes = $config['productReviewImageUploadAllowedFileTypes'];
$fileTypes = explode(';', $fileTypes);
// allow both JPEG file extensions if one is given
if (in_array('jpg', $fileTypes) && !in_array('jpeg', $fileTypes)) {
$fileTypes[] = 'jpeg';
} else if (in_array('jpeg', $fileTypes) && !in_array('jpg', $fileTypes)) {
$fileTypes[] = 'jpg';
}
return $fileTypes;
}
public function kernelEventsRequest(RequestEvent $event)
{
$request = $event->getRequest();
$attributes = $request->attributes;
$route = $attributes->get('_route');
if ($route == "api.product_review.delete") {
$productReviewId = $attributes->get('path');
if ($this->checkValidUuid($productReviewId)) {
$productReviewImages = $this->getProductReviewImages($productReviewId);
$productReviewImageids = [];
if (!empty($productReviewImages)) {
foreach ($productReviewImages as $productReviewImage) {
$productReviewImageids[] = ['id' => $productReviewImage->getId()];
}
}
if (!empty($productReviewImageids)) {
$this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->delete($productReviewImageids, Context::createDefaultContext());
}
}
}
return;
}
public function getProductReviewImages($productReviewId)
{
if (!empty($productReviewId)) {
$criteria = (new Criteria())
->addFilter(new EqualsFilter(
'productReviewId',
$productReviewId
));
$productReviewImages = $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->search($criteria, Context::createDefaultContext())->getEntities();
return ($productReviewImages) ? $productReviewImages : null;
}
return null;
}
public function checkValidUuid($id)
{
if (!empty($id)) {
if (Uuid::isValid($id)) {
return true;
}
}
return false;
}
private function resizeImage($uploadedFile, $imagePath)
{
list($width, $height, $type, $attr) = getimagesize($imagePath);
if ($width > self::MAX_WIDTH || $height > self::MAX_HEIGHT) {
$resolution = $this->calculateResolution($width, $height);
$sourceImage = imagecreatefromstring(file_get_contents($imagePath));
$destinatedImage = imagescale($sourceImage, $resolution['width'], $resolution['height']);
$imageType = $uploadedFile->getClientOriginalExtension();
switch ($imageType) {
case 'png':
imagepng($destinatedImage, $imagePath);
break;
case 'gif':
imagegif($destinatedImage, $imagePath);
break;
case 'jpg':
case 'jpeg':
imagejpeg($destinatedImage, $imagePath);
break;
}
}
}
private function calculateResolution(int $width, int $height): array
{
$aspectRatio = $width / $height;
$maxHeight = self::MAX_HEIGHT;
$maxWidth = self::MAX_WIDTH;
if ($aspectRatio <= 1) {
$maxWidth = self::MAX_HEIGHT * $aspectRatio;
} else {
$maxHeight = self::MAX_WIDTH / $aspectRatio;
}
return ['width' => intval(round($maxWidth)), 'height' => intval(round($maxHeight))];
}
}