From 29f099df6078bb9be1541f6972b619433d612817 Mon Sep 17 00:00:00 2001 From: Kamil Madejski Date: Thu, 28 Jun 2018 11:12:34 +0200 Subject: [PATCH] EZP-29238: As an Administrator I want to resize original images (#2357) * EZP-29238: As an Administrator I want to resize original images * Refactored to use autowiring * Added confirmation question * Added logic for updating image XML attributes * CS fix * Set filter option required * fixup! CS fix --- .../Command/ResizeOriginalImagesCommand.php | 323 ++++++++++++++++++ .../Resources/config/services.yml | 8 + 2 files changed, 331 insertions(+) create mode 100644 eZ/Bundle/EzPublishCoreBundle/Command/ResizeOriginalImagesCommand.php diff --git a/eZ/Bundle/EzPublishCoreBundle/Command/ResizeOriginalImagesCommand.php b/eZ/Bundle/EzPublishCoreBundle/Command/ResizeOriginalImagesCommand.php new file mode 100644 index 00000000000..3d683ad87f2 --- /dev/null +++ b/eZ/Bundle/EzPublishCoreBundle/Command/ResizeOriginalImagesCommand.php @@ -0,0 +1,323 @@ +permissionResolver = $permissionResolver; + $this->userService = $userService; + $this->contentTypeService = $contentTypeService; + $this->contentService = $contentService; + $this->searchService = $searchService; + $this->filterManager = $filterManager; + $this->ioService = $ioService; + $this->extensionGuesser = $extensionGuesser; + $this->imagine = $imagine; + + parent::__construct(); + } + + protected function initialize(InputInterface $input, OutputInterface $output) + { + parent::initialize($input, $output); + + $this->permissionResolver->setCurrentUserReference( + $this->userService->loadUserByLogin($input->getOption('user')) + ); + } + + protected function configure() + { + $this->setName('ezplatform:images:resize-original')->setDefinition( + [ + new InputArgument('contentTypeIdentifier', InputArgument::REQUIRED, + 'Indentifier of ContentType which has ezimage FieldType.'), + new InputArgument('imageFieldIdentifier', InputArgument::REQUIRED, + 'Identifier of field of ezimage type.'), + ] + ) + ->addOption( + 'filter', + 'f', + InputOption::VALUE_REQUIRED, + 'Filter which will be used for original images.' + ) + ->addOption( + 'iteration-count', + 'i', + InputOption::VALUE_OPTIONAL, + 'Iteration count. Number of images to be recreated in a single iteration, for avoiding using too much memory.', + self::DEFAULT_ITERATION_COUNT + ) + ->addOption( + 'user', + 'u', + InputOption::VALUE_OPTIONAL, + 'eZ Platform username (with Role containing at least Content policies: read, versionread, edit, publish)', + self::DEFAULT_REPOSITORY_USER + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $contentTypeIdentifier = $input->getArgument('contentTypeIdentifier'); + $imageFieldIdentifier = $input->getArgument('imageFieldIdentifier'); + $filter = $input->getOption('filter'); + $iterationCount = (int)$input->getOption('iteration-count'); + + $contentType = $this->contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier); + $fieldType = $contentType->getFieldDefinition($imageFieldIdentifier); + if (!$fieldType || $fieldType->fieldTypeIdentifier !== 'ezimage') { + $output->writeln( + sprintf( + "FieldType of identifier '%s' of ContentType '%s' has to be 'ezimage', '%s' given.", + $imageFieldIdentifier, + $contentType->identifier, + $fieldType ? $fieldType->fieldTypeIdentifier : '' + ) + ); + + return; + } + + try { + $this->filterManager->getFilterConfiguration()->get($filter); + } catch (NonExistingFilterException $e) { + $output->writeln( + sprintf( + '%s', + $e->getMessage() + ) + ); + + return; + } + + $query = new Query(); + $query->filter = new Query\Criterion\ContentTypeIdentifier($contentType->identifier); + + $totalCount = $this->searchService->findContent($query)->totalCount; + $query->limit = $iterationCount; + + if ($totalCount > 0) { + $output->writeln( + sprintf( + 'Found %d images matching given criteria.', + $totalCount + ) + ); + } else { + $output->writeln( + sprintf( + 'No images matching given criteria (ContentType: %s, FieldType %s) found. Exiting.', + $contentTypeIdentifier, + $imageFieldIdentifier + ) + ); + + return; + } + + $helper = $this->getHelper('question'); + $question = new ConfirmationQuestion('The changes you are going to perform cannot be undone. Please remember to do a proper backup before. Would you like to continue? ', false); + if (!$helper->ask($input, $output, $question)) { + return; + } + + $progressBar = new ProgressBar($output, $totalCount); + $progressBar->start(); + + while ($query->offset <= $totalCount) { + $results = $this->searchService->findContent($query); + + /** @var \eZ\Publish\API\Repository\Values\Content\Search\SearchHit $hit */ + foreach ($results->searchHits as $hit) { + $this->resize($output, $hit, $imageFieldIdentifier, $filter); + $progressBar->advance(); + } + + $query->offset += $iterationCount; + } + + $progressBar->finish(); + $output->writeln(''); + $output->writeln( + sprintf( + "All images have been successfully resized using '%s' filter.", + $filter + ) + ); + } + + /** + * @param \Symfony\Component\Console\Output\OutputInterface $output + * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchHit $hit + * @param string $imageFieldIdentifier + * @param string $filter + */ + private function resize(OutputInterface $output, SearchHit $hit, string $imageFieldIdentifier, string $filter): void + { + try { + /** @var \eZ\Publish\Core\FieldType\Image\Value $field */ + foreach ($hit->valueObject->fields[$imageFieldIdentifier] as $language => $field) { + if (null === $field->id) { + continue; + } + $binaryFile = $this->ioService->loadBinaryFile($field->id); + $mimeType = $this->ioService->getMimeType($field->id); + $binary = new Binary( + $this->ioService->getFileContents($binaryFile), + $mimeType, + $this->extensionGuesser->guess($mimeType) + ); + + $resizedImageBinary = $this->filterManager->applyFilter($binary, $filter); + $newBinaryFile = $this->store($resizedImageBinary, $field); + $image = $this->imagine->load($this->ioService->getFileContents($newBinaryFile)); + $dimensions = $image->getSize(); + + $contentDraft = $this->contentService->createContentDraft($hit->valueObject->getVersionInfo()->getContentInfo(), $hit->valueObject->getVersionInfo()); + $contentUpdateStruct = $this->contentService->newContentUpdateStruct(); + $contentUpdateStruct->setField($imageFieldIdentifier, [ + 'id' => $field->id, + 'alternativeText' => $field->alternativeText, + 'fileName' => $field->fileName, + 'fileSize' => $newBinaryFile->size, + 'imageId' => $field->imageId, + 'width' => $dimensions->getWidth(), + 'height' => $dimensions->getHeight(), + ]); + $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct); + $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct); + $this->contentService->publishVersion($contentDraft->versionInfo); + } + } catch (Exception $e) { + $output->writeln( + sprintf( + 'Can not resize image ID: %s, error message: %s.', + $field->imageId, + $e->getMessage() + ) + ); + } + } + + /** + * Copy of eZ\Bundle\EzPublishCoreBundle\Imagine\IORepositoryResolver::store() + * Original one cannot be used since original method uses eZ\Bundle\EzPublishCoreBundle\Imagine\IORepositoryResolver::getFilePath() + * so ends-up with image stored in _aliases instead of overwritten original image. + * + * @param \Liip\ImagineBundle\Binary\BinaryInterface $binary + * @param \eZ\Publish\Core\FieldType\Image\Value $image + * + * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException + * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue + * + * @return \eZ\Publish\Core\IO\Values\BinaryFile + */ + private function store(BinaryInterface $binary, Value $image): BinaryFile + { + $tmpFile = tmpfile(); + fwrite($tmpFile, $binary->getContent()); + $tmpMetadata = stream_get_meta_data($tmpFile); + $binaryCreateStruct = $this->ioService->newBinaryCreateStructFromLocalFile($tmpMetadata['uri']); + $binaryCreateStruct->id = $image->id; + $newBinaryFile = $this->ioService->createBinaryFile($binaryCreateStruct); + fclose($tmpFile); + + return $newBinaryFile; + } +} diff --git a/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml b/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml index 2ee07a31284..7ba4294e293 100644 --- a/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml +++ b/eZ/Bundle/EzPublishCoreBundle/Resources/config/services.yml @@ -253,3 +253,11 @@ services: class: eZ\Bundle\EzPublishCoreBundle\Command\CopySubtreeCommand autowire: true autoconfigure: true + + ezplatform.core.command.resize_original_images: + class: eZ\Bundle\EzPublishCoreBundle\Command\ResizeOriginalImagesCommand + autowire: true + autoconfigure: true + arguments: + $ioService: '@ezpublish.fieldType.ezimage.io_service.published' + $imagine: '@liip_imagine'