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