1<?php 2namespace TYPO3\CMS\Core\Resource\OnlineMedia\Processing; 3 4/* 5 * This file is part of the TYPO3 CMS project. 6 * 7 * It is free software; you can redistribute it and/or modify it under 8 * the terms of the GNU General Public License, either version 2 9 * of the License, or any later version. 10 * 11 * For the full copyright and license information, please read the 12 * LICENSE.txt file that was distributed with this source code. 13 * 14 * The TYPO3 project - inspiring people to share! 15 */ 16 17use TYPO3\CMS\Core\Core\Environment; 18use TYPO3\CMS\Core\Imaging\GraphicalFunctions; 19use TYPO3\CMS\Core\Imaging\ImageMagickFile; 20use TYPO3\CMS\Core\Resource\Driver\DriverInterface; 21use TYPO3\CMS\Core\Resource\File; 22use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry; 23use TYPO3\CMS\Core\Resource\ProcessedFile; 24use TYPO3\CMS\Core\Resource\ProcessedFileRepository; 25use TYPO3\CMS\Core\Resource\Processing\LocalImageProcessor; 26use TYPO3\CMS\Core\Resource\Service\FileProcessingService; 27use TYPO3\CMS\Core\Type\File\ImageInfo; 28use TYPO3\CMS\Core\Utility\CommandUtility; 29use TYPO3\CMS\Core\Utility\GeneralUtility; 30use TYPO3\CMS\Core\Utility\PathUtility; 31use TYPO3\CMS\Frontend\Imaging\GifBuilder; 32 33/** 34 * Preview of Online Media item Processing 35 */ 36class PreviewProcessing 37{ 38 /** 39 * @var LocalImageProcessor 40 */ 41 protected $processor; 42 43 /** 44 * @param ProcessedFile $processedFile 45 * @return bool 46 */ 47 protected function needsReprocessing($processedFile) 48 { 49 return $processedFile->isNew() 50 || (!$processedFile->usesOriginalFile() && !$processedFile->exists()) 51 || $processedFile->isOutdated(); 52 } 53 54 /** 55 * Process file 56 * Create static image preview for Online Media item when possible 57 * 58 * @param FileProcessingService $fileProcessingService 59 * @param DriverInterface $driver 60 * @param ProcessedFile $processedFile 61 * @param File $file 62 * @param string $taskType 63 * @param array $configuration 64 */ 65 public function processFile(FileProcessingService $fileProcessingService, DriverInterface $driver, ProcessedFile $processedFile, File $file, $taskType, array $configuration) 66 { 67 if ($taskType !== ProcessedFile::CONTEXT_IMAGEPREVIEW && $taskType !== ProcessedFile::CONTEXT_IMAGECROPSCALEMASK) { 68 return; 69 } 70 // Check if processing is needed 71 if (!$this->needsReprocessing($processedFile)) { 72 return; 73 } 74 // Check if there is a OnlineMediaHelper registered for this file type 75 $helper = OnlineMediaHelperRegistry::getInstance()->getOnlineMediaHelper($file); 76 if ($helper === false) { 77 return; 78 } 79 // Check if helper provides a preview image 80 $temporaryFileName = $helper->getPreviewImage($file); 81 if (empty($temporaryFileName) || !file_exists($temporaryFileName)) { 82 return; 83 } 84 $temporaryFileNameForResizedThumb = uniqid(Environment::getVarPath() . '/transient/online_media_' . $file->getHashedIdentifier()) . '.jpg'; 85 $configuration = $processedFile->getProcessingConfiguration(); 86 switch ($taskType) { 87 case ProcessedFile::CONTEXT_IMAGEPREVIEW: 88 $this->resizeImage($temporaryFileName, $temporaryFileNameForResizedThumb, $configuration); 89 break; 90 91 case ProcessedFile::CONTEXT_IMAGECROPSCALEMASK: 92 $this->cropScaleImage($temporaryFileName, $temporaryFileNameForResizedThumb, $configuration); 93 break; 94 } 95 GeneralUtility::unlink_tempfile($temporaryFileName); 96 if (is_file($temporaryFileNameForResizedThumb)) { 97 $processedFile->setName($this->getTargetFileName($processedFile)); 98 $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $temporaryFileNameForResizedThumb); 99 $processedFile->updateProperties( 100 [ 101 'width' => $imageInfo->getWidth(), 102 'height' => $imageInfo->getHeight(), 103 'size' => filesize($temporaryFileNameForResizedThumb), 104 'checksum' => $processedFile->getTask()->getConfigurationChecksum() 105 ] 106 ); 107 $processedFile->updateWithLocalFile($temporaryFileNameForResizedThumb); 108 GeneralUtility::unlink_tempfile($temporaryFileNameForResizedThumb); 109 110 /** @var ProcessedFileRepository $processedFileRepository */ 111 $processedFileRepository = GeneralUtility::makeInstance(ProcessedFileRepository::class); 112 $processedFileRepository->add($processedFile); 113 } 114 } 115 116 /** 117 * @param ProcessedFile $processedFile 118 * @param string $prefix 119 * @return string 120 */ 121 protected function getTargetFileName(ProcessedFile $processedFile, $prefix = 'preview_') 122 { 123 return $prefix . $processedFile->getTask()->getConfigurationChecksum() . '_' . $processedFile->getOriginalFile()->getNameWithoutExtension() . '.jpg'; 124 } 125 126 /** 127 * @param string $originalFileName 128 * @param string $temporaryFileName 129 * @param array $configuration 130 */ 131 protected function resizeImage($originalFileName, $temporaryFileName, $configuration) 132 { 133 // Create the temporary file 134 if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'])) { 135 return; 136 } 137 138 if (file_exists($originalFileName)) { 139 $arguments = CommandUtility::escapeShellArguments([ 140 'width' => $configuration['width'], 141 'height' => $configuration['height'], 142 ]); 143 $parameters = '-sample ' . $arguments['width'] . 'x' . $arguments['height'] 144 . ' ' . ImageMagickFile::fromFilePath($originalFileName, 0) 145 . ' ' . CommandUtility::escapeShellArgument($temporaryFileName); 146 147 $cmd = CommandUtility::imageMagickCommand('convert', $parameters) . ' 2>&1'; 148 CommandUtility::exec($cmd); 149 } 150 151 if (!file_exists($temporaryFileName)) { 152 // Create a error image 153 $graphicalFunctions = $this->getGraphicalFunctionsObject(); 154 $graphicalFunctions->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', PathUtility::basename($originalFileName)); 155 } 156 } 157 158 /** 159 * cropScaleImage 160 * 161 * @param string $originalFileName 162 * @param string $temporaryFileName 163 * @param array $configuration 164 */ 165 protected function cropScaleImage($originalFileName, $temporaryFileName, $configuration) 166 { 167 if (file_exists($originalFileName)) { 168 $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class); 169 170 $options = $this->getConfigurationForImageCropScaleMask($configuration, $gifBuilder); 171 $info = $gifBuilder->getImageDimensions($originalFileName); 172 $data = $gifBuilder->getImageScale($info, $configuration['width'], $configuration['height'], $options); 173 174 $info[0] = $data[0]; 175 $info[1] = $data[1]; 176 $frame = ''; 177 $params = $gifBuilder->cmds['jpg']; 178 179 // Cropscaling: 180 if ($data['crs']) { 181 if (!$data['origW']) { 182 $data['origW'] = $data[0]; 183 } 184 if (!$data['origH']) { 185 $data['origH'] = $data[1]; 186 } 187 $offsetX = (int)(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200); 188 $offsetY = (int)(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200); 189 $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . '! '; 190 } 191 $command = $gifBuilder->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' '; 192 $gifBuilder->imageMagickExec($originalFileName, $temporaryFileName, $command, $frame); 193 } 194 if (!file_exists($temporaryFileName)) { 195 // Create a error image 196 $graphicalFunctions = $this->getGraphicalFunctionsObject(); 197 $graphicalFunctions->getTemporaryImageWithText($temporaryFileName, 'No thumb', 'generated!', PathUtility::basename($originalFileName)); 198 } 199 } 200 201 /** 202 * Get configuration for ImageCropScaleMask processing 203 * 204 * @param array $configuration 205 * @param GifBuilder $gifBuilder 206 * @return array 207 */ 208 protected function getConfigurationForImageCropScaleMask(array $configuration, GifBuilder $gifBuilder) 209 { 210 if (!empty($configuration['useSample'])) { 211 $gifBuilder->scalecmd = '-sample'; 212 } 213 $options = []; 214 if (!empty($configuration['maxWidth'])) { 215 $options['maxW'] = $configuration['maxWidth']; 216 } 217 if (!empty($configuration['maxHeight'])) { 218 $options['maxH'] = $configuration['maxHeight']; 219 } 220 if (!empty($configuration['minWidth'])) { 221 $options['minW'] = $configuration['minWidth']; 222 } 223 if (!empty($configuration['minHeight'])) { 224 $options['minH'] = $configuration['minHeight']; 225 } 226 227 $options['noScale'] = $configuration['noScale']; 228 229 return $options; 230 } 231 232 /** 233 * @return LocalImageProcessor 234 */ 235 protected function getProcessor() 236 { 237 if (!$this->processor) { 238 $this->processor = GeneralUtility::makeInstance(LocalImageProcessor::class); 239 } 240 return $this->processor; 241 } 242 243 /** 244 * @return GraphicalFunctions 245 */ 246 protected function getGraphicalFunctionsObject(): GraphicalFunctions 247 { 248 return GeneralUtility::makeInstance(GraphicalFunctions::class); 249 } 250} 251