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