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