custom/plugins/HatslogicSwProductReviewImageUpload/src/Subscriber/ProductReviewImageUpload.php line 105

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * (c) 2Hats Logic Solutions <info@2hatslogic.com>
  5.  */
  6. namespace Hatslogic\Sw\ProductReviewImageUpload\Subscriber;
  7. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  8. use Doctrine\DBAL\Connection;
  9. use Shopware\Core\Framework\Uuid\Uuid;
  10. use Symfony\Component\DependencyInjection\ContainerInterface;
  11. use Shopware\Core\Content\Product\ProductEvents;
  12. use Shopware\Core\Framework\DataAbstractionLayer\Event\EntityWrittenEvent;
  13. use Symfony\Component\HttpFoundation\File\UploadedFile;
  14. use Symfony\Component\HttpFoundation\FileBag;
  15. use Shopware\Core\Framework\Util\Random;
  16. use Shopware\Core\System\SystemConfig\SystemConfigService;
  17. use Symfony\Component\HttpFoundation\RequestStack;
  18. use Shopware\Core\PlatformRequest;
  19. use League\Flysystem\Filesystem;
  20. use League\Flysystem\Adapter\Local;
  21. use Shopware\Core\Framework\Context;
  22. use Shopware\Storefront\Page\Product\ProductPageLoadedEvent;
  23. use Shopware\Core\Framework\Struct\ArrayEntity;
  24. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  25. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  26. use Shopware\Storefront\Event\StorefrontRenderEvent;
  27. use Hatslogic\Sw\ProductReviewImageUpload\HatslogicSwProductReviewImageUpload;
  28. use Shopware\Core\Content\Media\MediaType\ImageType;
  29. use Shopware\Core\Content\Media\MediaType\MediaType;
  30. use Symfony\Component\HttpKernel\KernelEvents;
  31. use Symfony\Component\HttpKernel\Event\RequestEvent;
  32. class ProductReviewImageUpload implements EventSubscriberInterface
  33. {
  34.     public const MAX_WIDTH 800;
  35.     public const MAX_HEIGHT 800;
  36.     /**
  37.      * @var ContainerInterface
  38.      */
  39.     protected $container;
  40.     /**
  41.      * @var Connection
  42.      */
  43.     private $connection;
  44.     /**
  45.      * @var SystemConfigService
  46.      */
  47.     private $systemConfigService;
  48.     /**
  49.      * @var RequestStack
  50.      */
  51.     private $requestStack;
  52.     /**
  53.      * @var Filesystem
  54.      */
  55.     private $filesystem;
  56.     public function __construct(
  57.         ContainerInterface $container,
  58.         Connection $connection,
  59.         SystemConfigService $systemConfigService,
  60.         RequestStack $requestStack
  61.     ) {
  62.         $this->container $container;
  63.         $this->connection $connection;
  64.         $this->systemConfigService $systemConfigService;
  65.         $this->requestStack $requestStack;
  66.         $kernal $this->container->get('kernel');
  67.         $this->filesystem = new Filesystem(new Local($kernal->getProjectDir()));
  68.     }
  69.     public static function getSubscribedEvents(): array
  70.     {
  71.         return [
  72.             ProductEvents::PRODUCT_REVIEW_WRITTEN_EVENT => 'onProductReviewSave',
  73.             KernelEvents::REQUEST => 'kernelEventsRequest',
  74.             ProductPageLoadedEvent::class => 'onPageLoaded',
  75.             StorefrontRenderEvent::class => 'onStorefrontRender'
  76.         ];
  77.     }
  78.     public function getReviewImagesById($productReviewId)
  79.     {
  80.         $criteria = (new Criteria())
  81.             ->addFilter(new EqualsFilter(
  82.                 's_plugin_hatslogic_product_review_image_upload_file.productReviewId',
  83.                 $productReviewId
  84.             ));
  85.         $result $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->search($criteriaContext::createDefaultContext());
  86.         $productReviewImages $result->getEntities();
  87.         return ($productReviewImages) ? $productReviewImages null;
  88.     }
  89.     public function onStorefrontRender(StorefrontRenderEvent $event)
  90.     {
  91.         $route $event->getRequest()->get('_route');
  92.         $config['productReviewImageUpload'] = $this->systemConfigService->get('HatslogicSwProductReviewImageUpload.config');
  93.         $event->setParameter('hatslogic'$config);
  94.         if ($route == 'frontend.product.reviews') {
  95.             $parameters $event->getParameters();
  96.             $reviews = (isset($parameters['reviews'])) ? $parameters['reviews'] : '';
  97.             if (!empty($reviews)) {
  98.                 $productReviewImagesArray $this->getCustomerReviewImages($reviews);
  99.                 $event->setParameter('page', array('extensions' => $productReviewImagesArray));
  100.             }
  101.         }
  102.     }
  103.     public function getCustomerReviewImages($reviews)
  104.     {
  105.         if (!empty($reviews)) {
  106.             $customerReviewId '';
  107.             $customerReview $reviews->getCustomerReview();
  108.             if (!empty($customerReview)) {
  109.                 $customerReviewId $customerReview->getId();
  110.             }
  111.             $activeReviews $reviews->getEntities();
  112.             $productReviewImagesArray = [];
  113.             if (!empty($activeReviews)) {
  114.                 foreach ($activeReviews as $review) {
  115.                     if ($review->getId() != $customerReviewId) {
  116.                         $productReviewImages $this->getReviewImagesById($review->getId());
  117.                         if (!empty($productReviewImages)) {
  118.                             foreach ($productReviewImages as $productReviewImage) {
  119.                                 $productReviewImagesArray[$review->getId()][] = [
  120.                                     'id'       => $productReviewImage->getId(),
  121.                                     'fileName' => $productReviewImage->getFileName(),
  122.                                     'mimeType' => $productReviewImage->getMimeType()
  123.                                 ];
  124.                             }
  125.                         }
  126.                     }
  127.                 }
  128.             }
  129.             //Customer review
  130.             if (!empty($customerReview)) {
  131.                 $productReviewImages $this->getReviewImagesById($customerReviewId);
  132.                 if (!empty($productReviewImages)) {
  133.                     foreach ($productReviewImages as $productReviewImage) {
  134.                         $productReviewImagesArray[$customerReviewId][] = [
  135.                             'id'       => $productReviewImage->getId(),
  136.                             'fileName' => $productReviewImage->getFileName(),
  137.                             'mimeType' => $productReviewImage->getMimeType()
  138.                         ];
  139.                     }
  140.                 }
  141.             }
  142.             if (!empty($productReviewImagesArray)) {
  143.                 return array('productReviewImagesArray' => new ArrayEntity($productReviewImagesArray));
  144.             }
  145.         }
  146.         return false;
  147.     }
  148.     public function onPageLoaded(ProductPageLoadedEvent $event)
  149.     {
  150.         $reviews $event->getPage()->getReviews();
  151.         if (!empty($reviews)) {
  152.             $productReviewImagesArray $this->getCustomerReviewImages($reviews);
  153.             if ($productReviewImagesArray) {
  154.                 $event->getPage()->addExtension('productReviewImagesArray'$productReviewImagesArray['productReviewImagesArray']);
  155.                 return true;
  156.             }
  157.         }
  158.         return false;
  159.     }
  160.     public function onProductReviewSave(EntityWrittenEvent $event)
  161.     {
  162.         $ids $event->getIds();
  163.         $reviewFiles = [];
  164.         $sErrorFlag = [];
  165.         $fileUploadError false;
  166.         if (!empty($_FILES)) {
  167.             $fileBag = new FileBag($_FILES);
  168.             $reviewFiles $fileBag->get('reviewFiles');
  169.         }
  170.         $master $this->requestStack->getMasterRequest();
  171.         $salesChannelId $master->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_ID);
  172.         $config $this->systemConfigService->get('HatslogicSwProductReviewImageUpload.config'$salesChannelId);
  173.         $reviewFilesCount count($reviewFiles);
  174.         if ($reviewFilesCount $config['productReviewImageUploadMaxFiles']) {
  175.             $sErrorFlag['numberOfFilesExceed'] = true;
  176.             $fileUploadError true;
  177.         }
  178.         if (!empty($reviewFiles) && !$fileUploadError) {
  179.             foreach ($reviewFiles as $uploadedFile) {
  180.                 $validationInfo $this->validateUploadedFile($uploadedFile$config);
  181.                 if (!$validationInfo['validStatus']) {
  182.                     if ($validationInfo['fileExtensionError']) {
  183.                         $sErrorFlag['fileExtensionError'] = true;
  184.                     }
  185.                     if ($validationInfo['fileSizeError']) {
  186.                         $sErrorFlag['fileSizeError'] = true;
  187.                     }
  188.                     if (!empty($validationInfo['errorMessage'])) {
  189.                         $sErrorFlag['errorMessage'] = $validationInfo['errorMessage'];
  190.                     }
  191.                     $fileUploadError true;
  192.                     break;
  193.                 }
  194.             }
  195.         }
  196.         if (!$fileUploadError && !empty($reviewFiles)) {
  197.             //save files
  198.             $filesUploaded = [];
  199.             foreach ($reviewFiles as $uploadedFile) {
  200.                 $uploadedNewFileName $this->uploadFile($uploadedFile$config);
  201.                 if ($uploadedNewFileName) {
  202.                     $filesUploaded[] = $uploadedNewFileName;
  203.                 }
  204.             }
  205.             if (!empty($filesUploaded)) {
  206.                 $this->saveImageNamesToDb($ids[0], $filesUploaded);
  207.             }
  208.             return true;
  209.         }
  210.         return false;
  211.     }
  212.     public function saveImageNamesToDb($productReviewId$filesUploaded)
  213.     {
  214.         if (!empty($filesUploaded) && !empty($productReviewId)) {
  215.             foreach ($filesUploaded as $file) {
  216.                 $productReviewImageUploadFileId Uuid::randomHex();
  217.                 $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->create(
  218.                     [
  219.                         [
  220.                             'id'              => $productReviewImageUploadFileId,
  221.                             'productReviewId' => $productReviewId,
  222.                             'fileName'        => $file['fileName'],
  223.                             'mimeType'        => $file['mimeType']
  224.                         ],
  225.                     ],
  226.                     Context::createDefaultContext()
  227.                 );
  228.             }
  229.             return true;
  230.         }
  231.         return false;
  232.     }
  233.     public function validateUploadedFile(UploadedFile $uploadedFile$config)
  234.     {
  235.         $fileExtensionError false;
  236.         $fileSizeError false;
  237.         $validStatus true;
  238.         $errorMessage '';
  239.         $allowedFileTypes $this->getAllowedFileTypes($config);
  240.         // check file type
  241.         if (!in_array(strtolower($uploadedFile->getClientOriginalExtension()), $allowedFileTypes)) {
  242.             $fileExtensionError true;
  243.             $validStatus false;
  244.         }
  245.         // file size from plugin config
  246.         $maxFileSize = (($config['productReviewImageUploadMaxFileSize'] * 1024) * 1024);
  247.         // check file size
  248.         if ($uploadedFile->getSize() > $maxFileSize) {
  249.             $fileSizeError true;
  250.             $validStatus false;
  251.         }
  252.         if ($uploadedFile->getError()) {
  253.             $validStatus  false;
  254.             $errorMessage $uploadedFile->getErrorMessage();
  255.         }
  256.         return array(
  257.             'validStatus'        => $validStatus,
  258.             'fileExtensionError' => $fileExtensionError,
  259.             'fileSizeError'      => $fileSizeError,
  260.             'errorMessage'       => $errorMessage
  261.         );
  262.     }
  263.     protected function uploadFile(UploadedFile $uploadedFile, array $config)
  264.     {
  265.         // move the file to the upload directory
  266.         if (!empty($uploadedFile) && !$uploadedFile->getError()) {
  267.             $kernal $this->container->get('kernel');
  268.             $reviewImagesFolderPath $kernal->getProjectDir() . '/' HatslogicSwProductReviewImageUpload::FILE_UPLOAD_PATH;
  269.             $newFileName Random::getAlphanumericString(13) . '_' $uploadedFile->getClientOriginalName();
  270.             $uploadedFile->move($reviewImagesFolderPath$newFileName);
  271.             
  272.             if (isset($config['productReviewImageResize']) && $config['productReviewImageResize']) {
  273.                 $this->resizeImage($uploadedFile$reviewImagesFolderPath."/".$newFileName);
  274.             }
  275.             if (file_exists($uploadedFile->getPathname())) {
  276.                 unlink($uploadedFile->getPathname());
  277.             }
  278.             return [
  279.                 'mimeType' => $uploadedFile->getClientMimeType(),
  280.                 'fileName' => $newFileName
  281.             ];
  282.         }
  283.         return false;
  284.     }
  285.     private function getAllowedFileTypes($config)
  286.     {
  287.         $fileTypes $config['productReviewImageUploadAllowedFileTypes'];
  288.         $fileTypes explode(';'$fileTypes);
  289.         // allow both JPEG file extensions if one is given
  290.         if (in_array('jpg'$fileTypes) && !in_array('jpeg'$fileTypes)) {
  291.             $fileTypes[] = 'jpeg';
  292.         } else if (in_array('jpeg'$fileTypes) && !in_array('jpg'$fileTypes)) {
  293.             $fileTypes[] = 'jpg';
  294.         }
  295.         return $fileTypes;
  296.     }
  297.     public function kernelEventsRequest(RequestEvent $event)
  298.     {
  299.         $request $event->getRequest();
  300.         $attributes $request->attributes;
  301.         $route $attributes->get('_route');
  302.         if ($route == "api.product_review.delete") {
  303.             $productReviewId $attributes->get('path');
  304.             if ($this->checkValidUuid($productReviewId)) {
  305.                 $productReviewImages $this->getProductReviewImages($productReviewId);
  306.                 $productReviewImageids = [];
  307.                 if (!empty($productReviewImages)) {
  308.                     foreach ($productReviewImages as $productReviewImage) {
  309.                         $productReviewImageids[] = ['id' => $productReviewImage->getId()];
  310.                     }
  311.                 }
  312.                 if (!empty($productReviewImageids)) {
  313.                     $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->delete($productReviewImageidsContext::createDefaultContext());
  314.                 }
  315.             }
  316.         }
  317.         return;
  318.     }
  319.     public function getProductReviewImages($productReviewId)
  320.     {
  321.         if (!empty($productReviewId)) {
  322.             $criteria = (new Criteria())
  323.                 ->addFilter(new EqualsFilter(
  324.                     'productReviewId',
  325.                     $productReviewId
  326.                 ));
  327.             $productReviewImages $this->container->get('s_plugin_hatslogic_product_review_image_upload_file.repository')->search($criteriaContext::createDefaultContext())->getEntities();
  328.             return ($productReviewImages) ? $productReviewImages null;
  329.         }
  330.         return null;
  331.     }
  332.     public function checkValidUuid($id)
  333.     {
  334.         if (!empty($id)) {
  335.             if (Uuid::isValid($id)) {
  336.                 return true;
  337.             }
  338.         }
  339.         return false;
  340.     }
  341.     private function resizeImage($uploadedFile$imagePath)
  342.     {
  343.         list($width$height$type$attr) = getimagesize($imagePath);
  344.         if ($width self::MAX_WIDTH || $height self::MAX_HEIGHT) {
  345.             $resolution $this->calculateResolution($width$height);
  346.             $sourceImage imagecreatefromstring(file_get_contents($imagePath));
  347.             $destinatedImage imagescale($sourceImage$resolution['width'], $resolution['height']);
  348.             
  349.             $imageType $uploadedFile->getClientOriginalExtension();
  350.             switch ($imageType) {
  351.                 case 'png':
  352.                     imagepng($destinatedImage$imagePath);
  353.     
  354.                     break;
  355.                 case 'gif':
  356.                     imagegif($destinatedImage$imagePath);
  357.     
  358.                     break;
  359.                 case 'jpg':
  360.                 case 'jpeg':
  361.                     imagejpeg($destinatedImage$imagePath);
  362.     
  363.                     break;
  364.             }
  365.         }
  366.     }
  367.     private function calculateResolution(int $widthint $height): array
  368.     {
  369.         $aspectRatio $width $height;
  370.         
  371.         $maxHeight self::MAX_HEIGHT;
  372.         $maxWidth self::MAX_WIDTH;
  373.         
  374.         if ($aspectRatio <= 1) {
  375.             $maxWidth self::MAX_HEIGHT $aspectRatio;
  376.         } else {
  377.             $maxHeight self::MAX_WIDTH $aspectRatio;
  378.         }
  379.         return ['width' => intval(round($maxWidth)), 'height' => intval(round($maxHeight))];
  380.     }
  381. }