[BUGFIX] Do not overwrite imagecaption in content adapter
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Resource / Service / ImageProcessingService.php
1 <?php
2 namespace TYPO3\CMS\Core\Resource\Service;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2011 Andreas Wolf <andreas.wolf@typo3.org>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 // TODO should this be a singleton?
30 /**
31 * Thumbnail service
32 *
33 * @author Andreas Wolf <andreas.wolf@typo3.org>
34 */
35 class ImageProcessingService {
36
37 /**
38 * Renders the actual image
39 *
40 * @param \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject
41 * @param $file
42 * @param array $fileConfiguration
43 * @return array
44 */
45 public function getImgResource(\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer $contentObject, $file, array $fileConfiguration) {
46 if ($fileConfiguration['import.']) {
47 $ifile = $contentObject->stdWrap('', $fileConfiguration['import.']);
48 if ($ifile) {
49 $file = $fileConfiguration['import'] . $ifile;
50 }
51 }
52 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($file)) {
53 $file = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\ResourceFactory')->getFileObject($file);
54 }
55 if ($file instanceof \TYPO3\CMS\Core\Resource\FileInterface) {
56 $theImage = $file->getForLocalProcessing(FALSE);
57 } else {
58 // clean ../ sections of the path and resolve to proper string.
59 // This is necessary for the \TYPO3\CMS\Core\Resource\Service\FrontendContentAdapterService to work.
60 $file = \TYPO3\CMS\Core\Utility\GeneralUtility::resolveBackPath($file);
61 $theImage = $GLOBALS['TSFE']->tmpl->getFileName($file);
62 if (!$theImage) {
63 return array();
64 }
65 }
66 $fileConfiguration = $this->processFileConfiguration($fileConfiguration, $contentObject);
67 $maskArray = $fileConfiguration['m.'];
68 $maskImages = array();
69 // Must render mask images and include in hash-calculating - else we
70 // cannot be sure the filename is unique for the setup!
71 if (is_array($maskArray)) {
72 $maskImages['m_mask'] = $this->getImgResource($contentObject, $maskArray['mask'], $maskArray['mask.']);
73 $maskImages['m_bgImg'] = $this->getImgResource($contentObject, $maskArray['bgImg'], $maskArray['bgImg.']);
74 $maskImages['m_bottomImg'] = $this->getImgResource($contentObject, $maskArray['bottomImg'], $maskArray['bottomImg.']);
75 $maskImages['m_bottomImg_mask'] = $this->getImgResource($contentObject, $maskArray['bottomImg_mask'], $maskArray['bottomImg_mask.']);
76 }
77 // TODO use \TYPO3\CMS\Core\Resource\FileInterface here
78 if ($file instanceof \TYPO3\CMS\Core\Resource\FileReference) {
79 $hash = $file->getOriginalFile()->calculateChecksum();
80 } else {
81 $hash = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($theImage . serialize($fileConfiguration) . serialize($maskImages));
82 }
83 if (isset($GLOBALS['TSFE']->tmpl->fileCache[$hash])) {
84 return $GLOBALS['TSFE']->tmpl->fileCache[$hash];
85 }
86 /** @var $gifCreator \TYPO3\CMS\Frontend\Imaging\GifBuilder */
87 $gifCreator = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Frontend\\Imaging\\GifBuilder');
88 $gifCreator->init();
89 if ($GLOBALS['TSFE']->config['config']['meaningfulTempFilePrefix']) {
90 $filename = basename($theImage);
91 // Remove extension
92 $filename = substr($filename, 0, strrpos($filename, '.'));
93 $tempFilePrefixLength = intval($GLOBALS['TSFE']->config['config']['meaningfulTempFilePrefix']);
94 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['UTF8filesystem']) {
95 /** @var $t3libCsInstance \TYPO3\CMS\Core\Charset\CharsetConverter */
96 $t3libCsInstance = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
97 $filenamePrefix = $t3libCsInstance->substr('utf-8', $filename, 0, $tempFilePrefixLength);
98 } else {
99 // Strip everything non-ascii
100 $filename = preg_replace('/[^A-Za-z0-9_-]/', '', trim($filename));
101 $filenamePrefix = substr($filename, 0, $tempFilePrefixLength);
102 }
103 $gifCreator->filenamePrefix = $filenamePrefix . '_';
104 unset($filename);
105 }
106 if ($fileConfiguration['sample']) {
107 $gifCreator->scalecmd = '-sample';
108 $GLOBALS['TT']->setTSlogMessage('Sample option: Images are scaled with -sample.');
109 }
110 if ($fileConfiguration['alternativeTempPath'] && \TYPO3\CMS\Core\Utility\GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['FE']['allowedTempPaths'], $fileConfiguration['alternativeTempPath'])) {
111 $gifCreator->tempPath = $fileConfiguration['alternativeTempPath'];
112 $GLOBALS['TT']->setTSlogMessage('Set alternativeTempPath: ' . $fileConfiguration['alternativeTempPath']);
113 }
114 if (!trim($fileConfiguration['ext'])) {
115 $fileConfiguration['ext'] = 'web';
116 }
117 $options = array();
118 if ($fileConfiguration['maxW']) {
119 $options['maxW'] = $fileConfiguration['maxW'];
120 }
121 if ($fileConfiguration['maxH']) {
122 $options['maxH'] = $fileConfiguration['maxH'];
123 }
124 if ($fileConfiguration['minW']) {
125 $options['minW'] = $fileConfiguration['minW'];
126 }
127 if ($fileConfiguration['minH']) {
128 $options['minH'] = $fileConfiguration['minH'];
129 }
130 if ($fileConfiguration['noScale']) {
131 $options['noScale'] = $fileConfiguration['noScale'];
132 }
133 $fileInformation = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($theImage);
134 $imgExt = strtolower($fileInformation['fileext']) == $gifCreator->gifExtension ? $gifCreator->gifExtension : 'jpg';
135 // If no mask is used or ImageMagick is disabled, processing is quite simple
136 if (!is_array($maskArray) || !$GLOBALS['TYPO3_CONF_VARS']['GFX']['im']) {
137 $fileConfiguration['params'] = $this->modifyImageMagickStripProfileParameters($fileConfiguration['params'], $fileConfiguration);
138 $GLOBALS['TSFE']->tmpl->fileCache[$hash] = $gifCreator->imageMagickConvert($theImage, $fileConfiguration['ext'], $fileConfiguration['width'], $fileConfiguration['height'], $fileConfiguration['params'], $fileConfiguration['frame'], $options);
139 if (($fileConfiguration['reduceColors'] || $imgExt === 'png' && !$gifCreator->png_truecolor) && is_file($GLOBALS['TSFE']->tmpl->fileCache[$hash][3])) {
140 $reduced = $gifCreator->IMreduceColors($GLOBALS['TSFE']->tmpl->fileCache[$hash][3], \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($fileConfiguration['reduceColors'], 256, $gifCreator->truecolorColors, 256));
141 if (is_file($reduced)) {
142 unlink($GLOBALS['TSFE']->tmpl->fileCache[$hash][3]);
143 rename($reduced, $GLOBALS['TSFE']->tmpl->fileCache[$hash][3]);
144 }
145 }
146 } else {
147 // Filename:
148 $fileDestination = $gifCreator->tempPath . $hash . '.' . $imgExt;
149 // Generate!
150 if (!file_exists($fileDestination)) {
151 $this->processMask($maskImages, $gifCreator, $theImage, $fileConfiguration, $options, $fileDestination);
152 }
153 // Finish off
154 if (($fileConfiguration['reduceColors'] || $imgExt === 'png' && !$gifCreator->png_truecolor) && is_file($fileDestination)) {
155 $reduced = $gifCreator->IMreduceColors($fileDestination, \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($fileConfiguration['reduceColors'], 256, $gifCreator->truecolorColors, 256));
156 if (is_file($reduced)) {
157 unlink($fileDestination);
158 rename($reduced, $fileDestination);
159 }
160 }
161 $GLOBALS['TSFE']->tmpl->fileCache[$hash] = $gifCreator->getImageDimensions($fileDestination);
162 }
163 $GLOBALS['TSFE']->tmpl->fileCache[$hash]['origFile'] = $theImage;
164 // This is needed by \TYPO3\CMS\Frontend\Imaging\GifBuilder, in order for the setup-array to create a unique filename hash.
165 $GLOBALS['TSFE']->tmpl->fileCache[$hash]['origFile_mtime'] = @filemtime($theImage);
166 $GLOBALS['TSFE']->tmpl->fileCache[$hash]['fileCacheHash'] = $hash;
167 if ($file instanceof \TYPO3\CMS\Core\Resource\FileInterface && \TYPO3\CMS\Core\Utility\GeneralUtility::isAbsPath($GLOBALS['TSFE']->tmpl->fileCache[$hash][3])) {
168 $GLOBALS['TSFE']->tmpl->fileCache[$hash][3] = $file->getPublicUrl();
169 }
170 $imageResource = $GLOBALS['TSFE']->tmpl->fileCache[$hash];
171 return $imageResource;
172 }
173
174 /**
175 * Renders the mask configuration
176 *
177 * @param $maskImages
178 * @param $gifCreator
179 * @param $theImage
180 * @param $fileConfiguration
181 * @param $options
182 * @param $dest
183 */
184 protected function processMask($maskImages, $gifCreator, $theImage, $fileConfiguration, $options, $dest) {
185 $m_mask = $maskImages['m_mask'];
186 $m_bgImg = $maskImages['m_bgImg'];
187 if ($m_mask && $m_bgImg) {
188 $negate = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_negate_mask'] ? ' -negate' : '';
189 $temp_ext = 'png';
190 // If ImageMagick version 5+
191 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_gif']) {
192 $temp_ext = $gifCreator->gifExtension;
193 }
194 $tempFileInfo = $gifCreator->imageMagickConvert($theImage, $temp_ext, $fileConfiguration['width'], $fileConfiguration['height'], $fileConfiguration['params'], $fileConfiguration['frame'], $options);
195 if (is_array($tempFileInfo)) {
196 $m_bottomImg = $maskImages['m_bottomImg'];
197 if ($m_bottomImg) {
198 $m_bottomImg_mask = $maskImages['m_bottomImg_mask'];
199 }
200 // Scaling:
201 $tempScale = array();
202 $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
203 $command = $this->modifyImageMagickStripProfileParameters($command, $fileConfiguration);
204 $tmpStr = $gifCreator->randomName();
205 // m_mask
206 $tempScale['m_mask'] = $tmpStr . '_mask.' . $temp_ext;
207 $gifCreator->imageMagickExec($m_mask[3], $tempScale['m_mask'], $command . $negate);
208 // m_bgImg
209 $tempScale['m_bgImg'] = $tmpStr . '_bgImg.' . trim($GLOBALS['TYPO3_CONF_VARS']['GFX']['im_mask_temp_ext_noloss']);
210 $gifCreator->imageMagickExec($m_bgImg[3], $tempScale['m_bgImg'], $command);
211 // m_bottomImg / m_bottomImg_mask
212 if ($m_bottomImg && $m_bottomImg_mask) {
213 $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temp_ext;
214 $gifCreator->imageMagickExec($m_bottomImg[3], $tempScale['m_bottomImg'], $command);
215 $tempScale['m_bottomImg_mask'] = $tmpStr . '_bottomImg_mask.' . $temp_ext;
216 $gifCreator->imageMagickExec($m_bottomImg_mask[3], $tempScale['m_bottomImg_mask'], $command . $negate);
217 // BEGIN combining:
218 // The image onto the background (including the mask here)
219 $gifCreator->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
220 }
221 // The image onto the background
222 $gifCreator->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $dest);
223 // Remove the temporary images
224 foreach ($tempScale as $file) {
225 if (@is_file($file)) {
226 unlink($file);
227 }
228 }
229 }
230 }
231 }
232
233 /**
234 * Cleans and sets-up the image-processing configuration array
235 *
236 * @param $fileConfiguration
237 * @param $contentObject
238 * @return array
239 */
240 protected function processFileConfiguration($fileConfiguration, $contentObject) {
241 $fileConfiguration['width'] = isset($fileConfiguration['width.']) ? $contentObject->stdWrap($fileConfiguration['width'], $fileConfiguration['width.']) : $fileConfiguration['width'];
242 $fileConfiguration['height'] = isset($fileConfiguration['height.']) ? $contentObject->stdWrap($fileConfiguration['height'], $fileConfiguration['height.']) : $fileConfiguration['height'];
243 $fileConfiguration['ext'] = isset($fileConfiguration['ext.']) ? $contentObject->stdWrap($fileConfiguration['ext'], $fileConfiguration['ext.']) : $fileConfiguration['ext'];
244 $fileConfiguration['maxW'] = isset($fileConfiguration['maxW.']) ? intval($contentObject->stdWrap($fileConfiguration['maxW'], $fileConfiguration['maxW.'])) : intval($fileConfiguration['maxW']);
245 $fileConfiguration['maxH'] = isset($fileConfiguration['maxH.']) ? intval($contentObject->stdWrap($fileConfiguration['maxH'], $fileConfiguration['maxH.'])) : intval($fileConfiguration['maxH']);
246 $fileConfiguration['minW'] = isset($fileConfiguration['minW.']) ? intval($contentObject->stdWrap($fileConfiguration['minW'], $fileConfiguration['minW.'])) : intval($fileConfiguration['minW']);
247 $fileConfiguration['minH'] = isset($fileConfiguration['minH.']) ? intval($contentObject->stdWrap($fileConfiguration['minH'], $fileConfiguration['minH.'])) : intval($fileConfiguration['minH']);
248 $fileConfiguration['noScale'] = isset($fileConfiguration['noScale.']) ? $contentObject->stdWrap($fileConfiguration['noScale'], $fileConfiguration['noScale.']) : $fileConfiguration['noScale'];
249 $fileConfiguration['params'] = isset($fileConfiguration['params.']) ? $contentObject->stdWrap($fileConfiguration['params'], $fileConfiguration['params.']) : $fileConfiguration['params'];
250 return $fileConfiguration;
251 }
252
253 /**
254 * Modifies the parameters for ImageMagick for stripping of profile information.
255 *
256 * @param string $parameters: The parameters to be modified (if required)
257 * @param array $configuration: The TypoScript configuration of [IMAGE].file
258 * @return string ImageMagick parameters
259 */
260 protected function modifyImageMagickStripProfileParameters($parameters, array $configuration) {
261 // Strips profile information of image to save some space:
262 if (isset($configuration['stripProfile'])) {
263 if ($configuration['stripProfile']) {
264 $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['im_stripProfileCommand'] . $parameters;
265 } else {
266 $parameters .= '###SkipStripProfile###';
267 }
268 }
269 return $parameters;
270 }
271
272 }
273
274
275 ?>