[TASK] Use strict comparison for strings
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Imaging / GifBuilder.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Imaging;
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\File;
19 use TYPO3\CMS\Core\Resource\ProcessedFile;
20 use TYPO3\CMS\Core\Utility\ArrayUtility;
21 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
22 use TYPO3\CMS\Core\Utility\File\BasicFileUtility;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Core\Utility\MathUtility;
25 use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
26
27 /**
28 * GIFBUILDER
29 *
30 * Generating gif/png-files from TypoScript
31 * Used by the menu-objects and imgResource in TypoScript.
32 *
33 * This class allows for advanced rendering of images with various layers of images, text and graphical primitives.
34 * The concept is known from TypoScript as "GIFBUILDER" where you can define a "numerical array" (TypoScript term as well) of "GIFBUILDER OBJECTS" (like "TEXT", "IMAGE", etc.) and they will be rendered onto an image one by one.
35 * The name "GIFBUILDER" comes from the time where GIF was the only file format supported. PNG is just as well to create today (configured with TYPO3_CONF_VARS[GFX])
36 * Not all instances of this class is truely building gif/png files by layers; You may also see the class instantiated for the purpose of using the scaling functions in the parent class.
37 *
38 * Here is an example of how to use this class (from tslib_content.php, function getImgResource):
39 *
40 * $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
41 * $gifCreator->init();
42 * $theImage='';
43 * if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
44 * $gifCreator->start($fileArray, $this->data);
45 * $theImage = $gifCreator->gifBuild();
46 * }
47 * return $gifCreator->getImageDimensions($theImage);
48 */
49 class GifBuilder extends GraphicalFunctions
50 {
51 /**
52 * Contains all text strings used on this image
53 *
54 * @var array
55 */
56 public $combinedTextStrings = [];
57
58 /**
59 * Contains all filenames (basename without extension) used on this image
60 *
61 * @var array
62 */
63 public $combinedFileNames = [];
64
65 /**
66 * This is the array from which data->field: [key] is fetched. So this is the current record!
67 *
68 * @var array
69 */
70 public $data = [];
71
72 /**
73 * @var array
74 */
75 public $objBB = [];
76
77 /**
78 * @var string
79 */
80 public $myClassName = 'gifbuilder';
81
82 /**
83 * @var array
84 */
85 public $charRangeMap = [];
86
87 /**
88 * @var int[]
89 */
90 public $XY = [];
91
92 /**
93 * @var ContentObjectRenderer
94 */
95 public $cObj;
96
97 /**
98 * @var array
99 */
100 public $defaultWorkArea = [];
101
102 /**
103 * Initialization of the GIFBUILDER objects, in particular TEXT and IMAGE. This includes finding the bounding box, setting dimensions and offset values before the actual rendering is started.
104 * Modifies the ->setup, ->objBB internal arrays
105 * Should be called after the ->init() function which initializes the parent class functions/variables in general.
106 * The class \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject also uses gifbuilder and here there is an interesting use since the function findLargestDims() from that class calls the init() and start() functions to find the total dimensions before starting the rendering of the images.
107 *
108 * @param array $conf TypoScript properties for the GIFBUILDER session. Stored internally in the variable ->setup
109 * @param array $data The current data record from \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer. Stored internally in the variable ->data
110 * @return void
111 * @see ContentObjectRenderer::getImgResource(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::makeGifs(), \TYPO3\CMS\Frontend\ContentObject\Menu\GraphicalMenuContentObject::findLargestDims()
112 */
113 public function start($conf, $data)
114 {
115 if (is_array($conf)) {
116 $this->setup = $conf;
117 $this->data = $data;
118 $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
119 $this->cObj->start($this->data);
120 // Hook preprocess gifbuilder conf
121 // Added by Julle for 3.8.0
122 //
123 // Let's you pre-process the gifbuilder configuration. for
124 // example you can split a string up into lines and render each
125 // line as TEXT obj, see extension julle_gifbconf
126 if (is_array($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'])) {
127 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] as $_funcRef) {
128 $_params = $this->setup;
129 $this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $this);
130 }
131 }
132 // Initializing global Char Range Map
133 $this->charRangeMap = [];
134 if (is_array($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'])) {
135 foreach ($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'] as $cRMcfgkey => $cRMcfg) {
136 if (is_array($cRMcfg)) {
137 // Initializing:
138 $cRMkey = $GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
139 $this->charRangeMap[$cRMkey] = [];
140 $this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'];
141 $this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
142 $this->charRangeMap[$cRMkey]['multiplicator'] = (double) $cRMcfg['fontSizeMultiplicator'];
143 $this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
144 }
145 }
146 }
147 // Getting sorted list of TypoScript keys from setup.
148 $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
149 // Setting the background color, passing it through stdWrap
150 if ($conf['backColor.'] || $conf['backColor']) {
151 $this->setup['backColor'] = isset($this->setup['backColor.']) ? trim($this->cObj->stdWrap($this->setup['backColor'], $this->setup['backColor.'])) : $this->setup['backColor'];
152 }
153 if (!$this->setup['backColor']) {
154 $this->setup['backColor'] = 'white';
155 }
156 if ($conf['transparentColor.'] || $conf['transparentColor']) {
157 $this->setup['transparentColor_array'] = isset($this->setup['transparentColor.']) ? explode('|', trim($this->cObj->stdWrap($this->setup['transparentColor'], $this->setup['transparentColor.']))) : explode('|', trim($this->setup['transparentColor']));
158 }
159 if (isset($this->setup['transparentBackground.'])) {
160 $this->setup['transparentBackground'] = $this->cObj->stdWrap($this->setup['transparentBackground'], $this->setup['transparentBackground.']);
161 }
162 if (isset($this->setup['reduceColors.'])) {
163 $this->setup['reduceColors'] = $this->cObj->stdWrap($this->setup['reduceColors'], $this->setup['reduceColors.']);
164 }
165 // Set default dimensions
166 if (isset($this->setup['XY.'])) {
167 $this->setup['XY'] = $this->cObj->stdWrap($this->setup['XY'], $this->setup['XY.']);
168 }
169 if (!$this->setup['XY']) {
170 $this->setup['XY'] = '120,50';
171 }
172 // Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
173 // The Bounding Box for the objects is stored in an array
174 foreach ($sKeyArray as $theKey) {
175 $theValue = $this->setup[$theKey];
176 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
177 // Swipes through TEXT and IMAGE-objects
178 switch ($theValue) {
179 case 'TEXT':
180 if ($this->setup[$theKey . '.'] = $this->checkTextObj($conf)) {
181 // Adjust font width if max size is set:
182 $maxWidth = isset($this->setup[$theKey . '.']['maxWidth.']) ? $this->cObj->stdWrap($this->setup[$theKey . '.']['maxWidth'], $this->setup[$theKey . '.']['maxWidth.']) : $this->setup[$theKey . '.']['maxWidth'];
183 if ($maxWidth) {
184 $this->setup[$theKey . '.']['fontSize'] = $this->fontResize($this->setup[$theKey . '.']);
185 }
186 // Calculate bounding box:
187 $txtInfo = $this->calcBBox($this->setup[$theKey . '.']);
188 $this->setup[$theKey . '.']['BBOX'] = $txtInfo;
189 $this->objBB[$theKey] = $txtInfo;
190 $this->setup[$theKey . '.']['imgMap'] = 0;
191 }
192 break;
193 case 'IMAGE':
194 $fileInfo = $this->getResource($conf['file'], $conf['file.']);
195 if ($fileInfo) {
196 $this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', basename($fileInfo[3]));
197 if ($fileInfo['processedFile'] instanceof ProcessedFile) {
198 // Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
199 $this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(false);
200 } elseif (!isset($fileInfo['origFile']) && $fileInfo['originalFile'] instanceof File) {
201 // Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
202 /** @var $originalFile File */
203 $originalFile = $fileInfo['originalFile'];
204 $this->setup[$theKey . '.']['file'] = $originalFile->getForLocalProcessing(false);
205 } else {
206 // Use normal path from fileInfo if it is a non-FAL file (even non-FAL files have originalFile set, but only non-FAL files have origFile set)
207 $this->setup[$theKey . '.']['file'] = $fileInfo[3];
208 }
209 $this->setup[$theKey . '.']['BBOX'] = $fileInfo;
210 $this->objBB[$theKey] = $fileInfo;
211 if ($conf['mask']) {
212 $maskInfo = $this->getResource($conf['mask'], $conf['mask.']);
213 if ($maskInfo) {
214 // the same selection criteria as regarding fileInfo above apply here
215 if ($maskInfo['processedFile'] instanceof ProcessedFile) {
216 $this->setup[$theKey . '.']['mask'] = $maskInfo['processedFile']->getForLocalProcessing(false);
217 } elseif (!isset($maskInfo['origFile']) && $maskInfo['originalFile'] instanceof File) {
218 /** @var $originalFile File */
219 $originalFile = $maskInfo['originalFile'];
220 $this->setup[$theKey . '.']['mask'] = $originalFile->getForLocalProcessing(false);
221 } else {
222 $this->setup[$theKey . '.']['mask'] = $maskInfo[3];
223 }
224 } else {
225 $this->setup[$theKey . '.']['mask'] = '';
226 }
227 }
228 } else {
229 unset($this->setup[$theKey . '.']);
230 }
231 break;
232 }
233 // Checks if disabled is set... (this is also done in menu.php / imgmenu!!)
234 if ($conf['if.']) {
235 $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
236 $cObj->start($this->data);
237 if (!$cObj->checkIf($conf['if.'])) {
238 unset($this->setup[$theKey]);
239 unset($this->setup[$theKey . '.']);
240 unset($this->objBB[$theKey]);
241 }
242 }
243 }
244 }
245 // Calculate offsets on elements
246 $this->setup['XY'] = $this->calcOffset($this->setup['XY']);
247 if (isset($this->setup['offset.'])) {
248 $this->setup['offset'] = $this->cObj->stdWrap($this->setup['offset'], $this->setup['offset.']);
249 }
250 $this->setup['offset'] = $this->calcOffset($this->setup['offset']);
251 if (isset($this->setup['workArea.'])) {
252 $this->setup['workArea'] = $this->cObj->stdWrap($this->setup['workArea'], $this->setup['workArea.']);
253 }
254 $this->setup['workArea'] = $this->calcOffset($this->setup['workArea']);
255 foreach ($sKeyArray as $theKey) {
256 $theValue = $this->setup[$theKey];
257 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
258 switch ($theValue) {
259 case 'TEXT':
260
261 case 'IMAGE':
262 if (isset($this->setup[$theKey . '.']['offset.'])) {
263 $this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['offset'], $this->setup[$theKey . '.']['offset.']);
264 unset($this->setup[$theKey . '.']['offset.']);
265 }
266 if ($this->setup[$theKey . '.']['offset']) {
267 $this->setup[$theKey . '.']['offset'] = $this->calcOffset($this->setup[$theKey . '.']['offset']);
268 }
269 break;
270 case 'BOX':
271
272 case 'ELLIPSE':
273 if (isset($this->setup[$theKey . '.']['dimensions.'])) {
274 $this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['dimensions'], $this->setup[$theKey . '.']['dimensions.']);
275 unset($this->setup[$theKey . '.']['dimensions.']);
276 }
277 if ($this->setup[$theKey . '.']['dimensions']) {
278 $this->setup[$theKey . '.']['dimensions'] = $this->calcOffset($this->setup[$theKey . '.']['dimensions']);
279 }
280 break;
281 case 'WORKAREA':
282 if (isset($this->setup[$theKey . '.']['set.'])) {
283 $this->setup[$theKey . '.']['set'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['set'], $this->setup[$theKey . '.']['set.']);
284 unset($this->setup[$theKey . '.']['set.']);
285 }
286 if ($this->setup[$theKey . '.']['set']) {
287 $this->setup[$theKey . '.']['set'] = $this->calcOffset($this->setup[$theKey . '.']['set']);
288 }
289 break;
290 case 'CROP':
291 if (isset($this->setup[$theKey . '.']['crop.'])) {
292 $this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['crop'], $this->setup[$theKey . '.']['crop.']);
293 unset($this->setup[$theKey . '.']['crop.']);
294 }
295 if ($this->setup[$theKey . '.']['crop']) {
296 $this->setup[$theKey . '.']['crop'] = $this->calcOffset($this->setup[$theKey . '.']['crop']);
297 }
298 break;
299 case 'SCALE':
300 if (isset($this->setup[$theKey . '.']['width.'])) {
301 $this->setup[$theKey . '.']['width'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['width'], $this->setup[$theKey . '.']['width.']);
302 unset($this->setup[$theKey . '.']['width.']);
303 }
304 if ($this->setup[$theKey . '.']['width']) {
305 $this->setup[$theKey . '.']['width'] = $this->calcOffset($this->setup[$theKey . '.']['width']);
306 }
307 if (isset($this->setup[$theKey . '.']['height.'])) {
308 $this->setup[$theKey . '.']['height'] = $this->cObj->stdWrap($this->setup[$theKey . '.']['height'], $this->setup[$theKey . '.']['height.']);
309 unset($this->setup[$theKey . '.']['height.']);
310 }
311 if ($this->setup[$theKey . '.']['height']) {
312 $this->setup[$theKey . '.']['height'] = $this->calcOffset($this->setup[$theKey . '.']['height']);
313 }
314 break;
315 }
316 }
317 }
318 // Get trivial data
319 $XY = GeneralUtility::intExplode(',', $this->setup['XY']);
320 $maxWidth = isset($this->setup['maxWidth.']) ? (int)$this->cObj->stdWrap($this->setup['maxWidth'], $this->setup['maxWidth.']) : (int)$this->setup['maxWidth'];
321 $maxHeight = isset($this->setup['maxHeight.']) ? (int)$this->cObj->stdWrap($this->setup['maxHeight'], $this->setup['maxHeight.']) : (int)$this->setup['maxHeight'];
322 $XY[0] = MathUtility::forceIntegerInRange($XY[0], 1, $maxWidth ?: 2000);
323 $XY[1] = MathUtility::forceIntegerInRange($XY[1], 1, $maxHeight ?: 2000);
324 $this->XY = $XY;
325 $this->w = $XY[0];
326 $this->h = $XY[1];
327 $this->OFFSET = GeneralUtility::intExplode(',', $this->setup['offset']);
328 // this sets the workArea
329 $this->setWorkArea($this->setup['workArea']);
330 // this sets the default to the current;
331 $this->defaultWorkArea = $this->workArea;
332 }
333 }
334
335 /**
336 * Initiates the image file generation if ->setup is TRUE and if the file did not exist already.
337 * Gets filename from fileName() and if file exists in typo3temp/assets/images/ dir it will - of course - not be rendered again.
338 * Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
339 *
340 * @return string The filename for the created GIF/PNG file. The filename will be prefixed "GB_
341 * @see make(), fileName()
342 */
343 public function gifBuild()
344 {
345 if ($this->setup) {
346 // Relative to PATH_site
347 $gifFileName = $this->fileName('images/');
348 // File exists
349 if (!file_exists($gifFileName)) {
350 // Create temporary directory if not done:
351 $this->createTempSubDir('images/');
352 // Create file:
353 $this->make();
354 $this->output($gifFileName);
355 $this->destroy();
356 }
357 return $gifFileName;
358 }
359 return '';
360 }
361
362 /**
363 * The actual rendering of the image file.
364 * Basically sets the dimensions, the background color, the traverses the array of GIFBUILDER objects and finally setting the transparent color if defined.
365 * Creates a GDlib resource in $this->im and works on that
366 * Called by gifBuild()
367 *
368 * @return void
369 * @access private
370 * @see gifBuild()
371 */
372 public function make()
373 {
374 // Get trivial data
375 $XY = $this->XY;
376 // Reset internal properties
377 $this->saveAlphaLayer = false;
378 // Gif-start
379 $this->im = imagecreatetruecolor($XY[0], $XY[1]);
380 $this->w = $XY[0];
381 $this->h = $XY[1];
382 // Transparent layer as background if set and requirements are met
383 if (!empty($this->setup['backColor']) && $this->setup['backColor'] === 'transparent' && $this->png_truecolor && !$this->setup['reduceColors'] && (empty($this->setup['format']) || $this->setup['format'] === 'png')) {
384 // Set transparency properties
385 imagesavealpha($this->im, true);
386 // Fill with a transparent background
387 $transparentColor = imagecolorallocatealpha($this->im, 0, 0, 0, 127);
388 imagefill($this->im, 0, 0, $transparentColor);
389 // Set internal properties to keep the transparency over the rendering process
390 $this->saveAlphaLayer = true;
391 // Force PNG in case no format is set
392 $this->setup['format'] = 'png';
393 $BGcols = [];
394 } else {
395 // Fill the background with the given color
396 $BGcols = $this->convertColor($this->setup['backColor']);
397 $Bcolor = imagecolorallocate($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
398 imagefilledrectangle($this->im, 0, 0, $XY[0], $XY[1], $Bcolor);
399 }
400 // Traverse the GIFBUILDER objects an render each one:
401 if (is_array($this->setup)) {
402 $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
403 foreach ($sKeyArray as $theKey) {
404 $theValue = $this->setup[$theKey];
405 if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
406 // apply stdWrap to all properties, except for TEXT objects
407 // all properties of the TEXT sub-object have already been stdWrap-ped
408 // before in ->checkTextObj()
409 if ($theValue !== 'TEXT') {
410 $isStdWrapped = [];
411 foreach ($conf as $key => $value) {
412 $parameter = rtrim($key, '.');
413 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
414 $conf[$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
415 $isStdWrapped[$parameter] = 1;
416 }
417 }
418 }
419
420 switch ($theValue) {
421 case 'IMAGE':
422 if ($conf['mask']) {
423 $this->maskImageOntoImage($this->im, $conf, $this->workArea);
424 } else {
425 $this->copyImageOntoImage($this->im, $conf, $this->workArea);
426 }
427 break;
428 case 'TEXT':
429 if (!$conf['hide']) {
430 if (is_array($conf['shadow.'])) {
431 $isStdWrapped = [];
432 foreach ($conf['shadow.'] as $key => $value) {
433 $parameter = rtrim($key, '.');
434 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
435 $conf['shadow.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
436 $isStdWrapped[$parameter] = 1;
437 }
438 }
439 $this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf);
440 }
441 if (is_array($conf['emboss.'])) {
442 $isStdWrapped = [];
443 foreach ($conf['emboss.'] as $key => $value) {
444 $parameter = rtrim($key, '.');
445 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
446 $conf['emboss.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
447 $isStdWrapped[$parameter] = 1;
448 }
449 }
450 $this->makeEmboss($this->im, $conf['emboss.'], $this->workArea, $conf);
451 }
452 if (is_array($conf['outline.'])) {
453 $isStdWrapped = [];
454 foreach ($conf['outline.'] as $key => $value) {
455 $parameter = rtrim($key, '.');
456 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
457 $conf['outline.'][$parameter] = $this->cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
458 $isStdWrapped[$parameter] = 1;
459 }
460 }
461 $this->makeOutline($this->im, $conf['outline.'], $this->workArea, $conf);
462 }
463 $conf['imgMap'] = 1;
464 $this->makeText($this->im, $conf, $this->workArea);
465 }
466 break;
467 case 'OUTLINE':
468 if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
469 $this->makeOutline($this->im, $conf, $this->workArea, $txtConf);
470 }
471 break;
472 case 'EMBOSS':
473 if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
474 $this->makeEmboss($this->im, $conf, $this->workArea, $txtConf);
475 }
476 break;
477 case 'SHADOW':
478 if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) {
479 $this->makeShadow($this->im, $conf, $this->workArea, $txtConf);
480 }
481 break;
482 case 'BOX':
483 $this->makeBox($this->im, $conf, $this->workArea);
484 break;
485 case 'EFFECT':
486 $this->makeEffect($this->im, $conf);
487 break;
488 case 'ADJUST':
489 $this->adjust($this->im, $conf);
490 break;
491 case 'CROP':
492 $this->crop($this->im, $conf);
493 break;
494 case 'SCALE':
495 $this->scale($this->im, $conf);
496 break;
497 case 'WORKAREA':
498 if ($conf['set']) {
499 // this sets the workArea
500 $this->setWorkArea($conf['set']);
501 }
502 if (isset($conf['clear'])) {
503 // This sets the current to the default;
504 $this->workArea = $this->defaultWorkArea;
505 }
506 break;
507 case 'ELLIPSE':
508 $this->makeEllipse($this->im, $conf, $this->workArea);
509 break;
510 }
511 }
512 }
513 }
514 // Preserve alpha transparency
515 if (!$this->saveAlphaLayer) {
516 if ($this->setup['transparentBackground']) {
517 // Auto transparent background is set
518 $Bcolor = imagecolorclosest($this->im, $BGcols[0], $BGcols[1], $BGcols[2]);
519 imagecolortransparent($this->im, $Bcolor);
520 } elseif (is_array($this->setup['transparentColor_array'])) {
521 // Multiple transparent colors are set. This is done via the trick that all transparent colors get
522 // converted to one color and then this one gets set as transparent as png/gif can just have one
523 // transparent color.
524 $Tcolor = $this->unifyColors($this->im, $this->setup['transparentColor_array'], (int)$this->setup['transparentColor.']['closest']);
525 if ($Tcolor >= 0) {
526 imagecolortransparent($this->im, $Tcolor);
527 }
528 }
529 }
530 }
531
532 /*********************************************
533 *
534 * Various helper functions
535 *
536 ********************************************/
537 /**
538 * Initializing/Cleaning of TypoScript properties for TEXT GIFBUILDER objects
539 *
540 * 'cleans' TEXT-object; Checks fontfile and other vital setup
541 * Finds the title if its a 'variable' (instantiates a cObj and loads it with the ->data record)
542 * Performs caseshift if any.
543 *
544 * @param array $conf GIFBUILDER object TypoScript properties
545 * @return array Modified $conf array IF the "text" property is not blank
546 * @access private
547 */
548 public function checkTextObj($conf)
549 {
550 $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
551 $cObj->start($this->data);
552 $isStdWrapped = [];
553 foreach ($conf as $key => $value) {
554 $parameter = rtrim($key, '.');
555 if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) {
556 $conf[$parameter] = $cObj->stdWrap($conf[$parameter], $conf[$parameter . '.']);
557 $isStdWrapped[$parameter] = 1;
558 }
559 }
560 $conf['fontFile'] = $this->checkFile($conf['fontFile']);
561 if (!$conf['fontFile']) {
562 $conf['fontFile'] = ExtensionManagementUtility::siteRelPath('core') . 'Resources/Private/Font/nimbus.ttf';
563 }
564 if (!$conf['iterations']) {
565 $conf['iterations'] = 1;
566 }
567 if (!$conf['fontSize']) {
568 $conf['fontSize'] = 12;
569 }
570 // If any kind of spacing applys, we cannot use angles!!
571 if ($conf['spacing'] || $conf['wordSpacing']) {
572 $conf['angle'] = 0;
573 }
574 if (!isset($conf['antiAlias'])) {
575 $conf['antiAlias'] = 1;
576 }
577 $conf['fontColor'] = trim($conf['fontColor']);
578 // Strip HTML
579 if (!$conf['doNotStripHTML']) {
580 $conf['text'] = strip_tags($conf['text']);
581 }
582 $this->combinedTextStrings[] = strip_tags($conf['text']);
583 // Max length = 100 if automatic line braks are not defined:
584 if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
585 $tlen = (int)$conf['textMaxLength'] ?: 100;
586 $conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
587 }
588 if ((string)$conf['text'] != '') {
589 // Char range map thingie:
590 $fontBaseName = basename($conf['fontFile']);
591 if (is_array($this->charRangeMap[$fontBaseName])) {
592 // Initialize splitRendering array:
593 if (!is_array($conf['splitRendering.'])) {
594 $conf['splitRendering.'] = [];
595 }
596 $cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
597 // Do not impose settings if a splitRendering object already exists:
598 if (!isset($conf['splitRendering.'][$cfgK])) {
599 // Set configuration:
600 $conf['splitRendering.'][$cfgK] = 'charRange';
601 $conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
602 // Multiplicator of fontsize:
603 if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
604 $conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
605 }
606 // Multiplicator of pixelSpace:
607 if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
608 $travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
609 foreach ($travKeys as $pxKey) {
610 if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
611 $conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
612 }
613 }
614 }
615 }
616 }
617 if (is_array($conf['splitRendering.'])) {
618 foreach ($conf['splitRendering.'] as $key => $value) {
619 if (is_array($conf['splitRendering.'][$key])) {
620 if (isset($conf['splitRendering.'][$key]['fontFile'])) {
621 $conf['splitRendering.'][$key]['fontFile'] = $this->checkFile($conf['splitRendering.'][$key]['fontFile']);
622 }
623 }
624 }
625 }
626 return $conf;
627 }
628 return null;
629 }
630
631 /**
632 * Calculation of offset using "splitCalc" and insertion of dimensions from other GIFBUILDER objects.
633 *
634 * Example:
635 * Input: 2+2, 2*3, 123, [10.w]
636 * Output: 4,6,123,45 (provided that the width of object in position 10 was 45 pixels wide)
637 *
638 * @param string $string The string to resolve/calculate the result of. The string is divided by a comma first and each resulting part is calculated into an integer.
639 * @return string The resolved string with each part (separated by comma) returned separated by comma
640 * @access private
641 */
642 public function calcOffset($string)
643 {
644 $value = [];
645 $numbers = GeneralUtility::trimExplode(',', $this->calculateFunctions($string));
646 foreach ($numbers as $key => $val) {
647 if ((string)$val == (string)(int)$val) {
648 $value[$key] = (int)$val;
649 } else {
650 $value[$key] = $this->calculateValue($val);
651 }
652 }
653 $string = implode(',', $value);
654 return $string;
655 }
656
657 /**
658 * Returns an "imgResource" creating an instance of the ContentObjectRenderer class and calling ContentObjectRenderer::getImgResource
659 *
660 * @param string $file Filename value OR the string "GIFBUILDER", see documentation in TSref for the "datatype" called "imgResource
661 * @param array $fileArray TypoScript properties passed to the function. Either GIFBUILDER properties or imgResource properties, depending on the value of $file (whether that is "GIFBUILDER" or a file reference)
662 * @return array|NULL Returns an array with file information from ContentObjectRenderer::getImgResource()
663 * @access private
664 * @see ContentObjectRenderer::getImgResource()
665 */
666 public function getResource($file, $fileArray)
667 {
668 if (!GeneralUtility::inList($this->imageFileExt, $fileArray['ext'])) {
669 $fileArray['ext'] = $this->gifExtension;
670 }
671 /** @var ContentObjectRenderer $cObj */
672 $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
673 $cObj->start($this->data);
674 return $cObj->getImgResource($file, $fileArray);
675 }
676
677 /**
678 * Returns the reference to a "resource" in TypoScript.
679 *
680 * @param string $file The resource value.
681 * @return string Returns the relative filepath
682 * @access private
683 * @see TemplateService::getFileName()
684 */
685 public function checkFile($file)
686 {
687 return $GLOBALS['TSFE']->tmpl->getFileName($file);
688 }
689
690 /**
691 * Calculates the GIFBUILDER output filename/path based on a serialized, hashed value of this->setup
692 * and prefixes the original filename
693 * also, the filename gets an additional prefix (max 100 characters),
694 * something like "GB_MD5HASH_myfilename_is_very_long_and_such.jpg"
695 *
696 * @param string $pre Filename prefix, eg. "GB_
697 * @return string The relative filepath (relative to PATH_site)
698 * @access private
699 */
700 public function fileName($pre)
701 {
702 /** @var $basicFileFunctions \TYPO3\CMS\Core\Utility\File\BasicFileUtility */
703 $basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
704 $filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
705 $filePrefix = $basicFileFunctions->cleanFileName($filePrefix);
706
707 return $this->tempPath . $pre . $filePrefix . '_' . GeneralUtility::shortMD5(serialize($this->setup)) . '.' . $this->extension();
708 }
709
710 /**
711 * Returns the file extension used in the filename
712 *
713 * @return string Extension; "jpg" or "gif"/"png
714 * @access private
715 */
716 public function extension()
717 {
718 switch (strtolower($this->setup['format'])) {
719 case 'jpg':
720
721 case 'jpeg':
722 return 'jpg';
723 break;
724 case 'png':
725 return 'png';
726 break;
727 case 'gif':
728 return 'gif';
729 break;
730 default:
731 return $this->gifExtension;
732 }
733 }
734
735 /**
736 * Calculates the value concerning the dimensions of objects.
737 *
738 * @param string $string The string to be calculated (e.g. "[20.h]+13")
739 * @return int The calculated value (e.g. "23")
740 * @see calcOffset()
741 */
742 protected function calculateValue($string)
743 {
744 $calculatedValue = 0;
745 $parts = GeneralUtility::splitCalc($string, '+-*/%');
746 foreach ($parts as $part) {
747 $theVal = $part[1];
748 $sign = $part[0];
749 if (((string)(int)$theVal) == ((string)$theVal)) {
750 $theVal = (int)$theVal;
751 } elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
752 $objParts = explode('.', substr($theVal, 1, -1));
753 $theVal = 0;
754 if (isset($this->objBB[$objParts[0]])) {
755 if ($objParts[1] === 'w') {
756 $theVal = $this->objBB[$objParts[0]][0];
757 } elseif ($objParts[1] === 'h') {
758 $theVal = $this->objBB[$objParts[0]][1];
759 } elseif ($objParts[1] === 'lineHeight') {
760 $theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
761 }
762 $theVal = (int)$theVal;
763 }
764 } elseif ((float)$theVal) {
765 $theVal = (float)$theVal;
766 } else {
767 $theVal = 0;
768 }
769 if ($sign === '-') {
770 $calculatedValue -= $theVal;
771 } elseif ($sign === '+') {
772 $calculatedValue += $theVal;
773 } elseif ($sign === '/' && $theVal) {
774 $calculatedValue = $calculatedValue / $theVal;
775 } elseif ($sign === '*') {
776 $calculatedValue = $calculatedValue * $theVal;
777 } elseif ($sign === '%' && $theVal) {
778 $calculatedValue %= $theVal;
779 }
780 }
781 return round($calculatedValue);
782 }
783
784 /**
785 * Calculates special functions:
786 * + max([10.h], [20.h]) -> gets the maximum of the given values
787 *
788 * @param string $string The raw string with functions to be calculated
789 * @return string The calculated values
790 */
791 protected function calculateFunctions($string)
792 {
793 if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
794 foreach ($matches[1] as $index => $maxExpression) {
795 $string = str_replace($matches[0][$index], $this->calculateMaximum($maxExpression), $string);
796 }
797 }
798 return $string;
799 }
800
801 /**
802 * Calculates the maximum of a set of values defined like "[10.h],[20.h],1000"
803 *
804 * @param string $string The string to be used to calculate the maximum (e.g. "[10.h],[20.h],1000")
805 * @return int The maxium value of the given comma separated and calculated values
806 */
807 protected function calculateMaximum($string)
808 {
809 $parts = GeneralUtility::trimExplode(',', $this->calcOffset($string), true);
810 $maximum = !empty($parts) ? max($parts) : 0;
811 return $maximum;
812 }
813 }