GifBuilder::checkTextObj()   F
last analyzed

Complexity

Conditions 27
Paths 17664

Size

Total Lines 84
Code Lines 50

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 50
nc 17664
nop 1
dl 0
loc 84
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Frontend\Imaging;
17
18
use TYPO3\CMS\Core\Context\Context;
19
use TYPO3\CMS\Core\Context\FileProcessingAspect;
20
use TYPO3\CMS\Core\Core\Environment;
21
use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
22
use TYPO3\CMS\Core\Resource\Exception;
23
use TYPO3\CMS\Core\Resource\File;
24
use TYPO3\CMS\Core\Resource\ProcessedFile;
25
use TYPO3\CMS\Core\Utility\ArrayUtility;
26
use TYPO3\CMS\Core\Utility\File\BasicFileUtility;
27
use TYPO3\CMS\Core\Utility\GeneralUtility;
28
use TYPO3\CMS\Core\Utility\MathUtility;
29
use TYPO3\CMS\Core\Utility\PathUtility;
30
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
31
use TYPO3\CMS\Frontend\Resource\FilePathSanitizer;
32
33
/**
34
 * GIFBUILDER
35
 *
36
 * Generating gif/png-files from TypoScript
37
 * Used by the menu-objects and imgResource in TypoScript.
38
 *
39
 * This class allows for advanced rendering of images with various layers of images, text and graphical primitives.
40
 * 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.
41
 * 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])
42
 * 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.
43
 *
44
 * Here is an example of how to use this class (from tslib_content.php, function getImgResource):
45
 *
46
 * $gifCreator = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Imaging\GifBuilder::class);
47
 * $gifCreator->init();
48
 * $theImage='';
49
 * if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib']) {
50
 * $gifCreator->start($fileArray, $this->data);
51
 * $theImage = $gifCreator->gifBuild();
52
 * }
53
 * return $gifCreator->getImageDimensions($theImage);
54
 */
55
class GifBuilder extends GraphicalFunctions
56
{
57
    /**
58
     * Contains all text strings used on this image
59
     *
60
     * @var array
61
     */
62
    public $combinedTextStrings = [];
63
64
    /**
65
     * Contains all filenames (basename without extension) used on this image
66
     *
67
     * @var array
68
     */
69
    public $combinedFileNames = [];
70
71
    /**
72
     * This is the array from which data->field: [key] is fetched. So this is the current record!
73
     *
74
     * @var array
75
     */
76
    public $data = [];
77
78
    /**
79
     * @var array
80
     */
81
    public $objBB = [];
82
83
    /**
84
     * @var string
85
     */
86
    public $myClassName = 'gifbuilder';
87
88
    /**
89
     * @var array
90
     */
91
    public $charRangeMap = [];
92
93
    /**
94
     * @var int[]
95
     */
96
    public $XY = [];
97
98
    /**
99
     * @var ContentObjectRenderer
100
     */
101
    public $cObj;
102
103
    /**
104
     * @var array
105
     */
106
    public $defaultWorkArea = [];
107
108
    /**
109
     * 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.
110
     * Modifies the ->setup, ->objBB internal arrays
111
     * Should be called after the ->init() function which initializes the parent class functions/variables in general.
112
     *
113
     * @param array $conf TypoScript properties for the GIFBUILDER session. Stored internally in the variable ->setup
114
     * @param array $data The current data record from \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer. Stored internally in the variable ->data
115
     * @see ContentObjectRenderer::getImgResource()
116
     */
117
    public function start($conf, $data)
118
    {
119
        if (is_array($conf)) {
0 ignored issues
show
introduced by
The condition is_array($conf) is always true.
Loading history...
120
            $this->setup = $conf;
121
            $this->data = $data;
122
            $this->cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
123
            $this->cObj->start($this->data);
124
            // Hook preprocess gifbuilder conf
125
            // Added by Julle for 3.8.0
126
            //
127
            // Let's you pre-process the gifbuilder configuration. for
128
            // example you can split a string up into lines and render each
129
            // line as TEXT obj, see extension julle_gifbconf
130
            foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['tslib/class.tslib_gifbuilder.php']['gifbuilder-ConfPreProcess'] ?? [] as $_funcRef) {
131
                $_params = $this->setup;
132
                $ref = $this; // introduced for phpstan to not lose type information when passing $this into callUserFunction
133
                $this->setup = GeneralUtility::callUserFunction($_funcRef, $_params, $ref);
134
            }
135
            // Initializing global Char Range Map
136
            $this->charRangeMap = [];
137
            if (is_array($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'])) {
138
                foreach ($GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'] as $cRMcfgkey => $cRMcfg) {
139
                    if (is_array($cRMcfg)) {
140
                        // Initializing:
141
                        $cRMkey = $GLOBALS['TSFE']->tmpl->setup['_GIFBUILDER.']['charRangeMap.'][substr($cRMcfgkey, 0, -1)];
142
                        $this->charRangeMap[$cRMkey] = [];
143
                        $this->charRangeMap[$cRMkey]['charMapConfig'] = $cRMcfg['charMapConfig.'];
144
                        $this->charRangeMap[$cRMkey]['cfgKey'] = substr($cRMcfgkey, 0, -1);
145
                        $this->charRangeMap[$cRMkey]['multiplicator'] = (double)$cRMcfg['fontSizeMultiplicator'];
146
                        $this->charRangeMap[$cRMkey]['pixelSpace'] = (int)$cRMcfg['pixelSpaceFontSizeRef'];
147
                    }
148
                }
149
            }
150
            // Getting sorted list of TypoScript keys from setup.
151
            $sKeyArray = ArrayUtility::filterAndSortByNumericKeys($this->setup);
152
            // Setting the background color, passing it through stdWrap
153
            $this->setup['backColor'] = $this->cObj->stdWrapValue('backColor', $this->setup ?? []);
154
            if (!$this->setup['backColor']) {
155
                $this->setup['backColor'] = 'white';
156
            }
157
            $this->setup['transparentColor_array'] = explode('|', trim((string)$this->cObj->stdWrapValue('transparentColor', $this->setup ?? [])));
158
            $this->setup['transparentBackground'] = $this->cObj->stdWrapValue('transparentBackground', $this->setup ?? []);
159
            $this->setup['reduceColors'] = $this->cObj->stdWrapValue('reduceColors', $this->setup ?? []);
160
            // Set default dimensions
161
            $this->setup['XY'] = $this->cObj->stdWrapValue('XY', $this->setup ?? []);
162
            if (!$this->setup['XY']) {
163
                $this->setup['XY'] = '120,50';
164
            }
165
            // Checking TEXT and IMAGE objects for files. If any errors the objects are cleared.
166
            // The Bounding Box for the objects is stored in an array
167
            foreach ($sKeyArray as $theKey) {
168
                $theValue = $this->setup[$theKey];
169
                if ((int)$theKey && ($conf = $this->setup[$theKey . '.'])) {
170
                    // Swipes through TEXT and IMAGE-objects
171
                    switch ($theValue) {
172
                        case 'TEXT':
173
                            if ($this->setup[$theKey . '.'] = $this->checkTextObj($conf)) {
174
                                // Adjust font width if max size is set:
175
                                $maxWidth = $this->cObj->stdWrapValue('maxWidth', $this->setup[$theKey . '.'] ?? []);
176
                                if ($maxWidth) {
177
                                    $this->setup[$theKey . '.']['fontSize'] = $this->fontResize($this->setup[$theKey . '.']);
178
                                }
179
                                // Calculate bounding box:
180
                                $txtInfo = $this->calcBBox($this->setup[$theKey . '.']);
181
                                $this->setup[$theKey . '.']['BBOX'] = $txtInfo;
182
                                $this->objBB[$theKey] = $txtInfo;
183
                                $this->setup[$theKey . '.']['imgMap'] = 0;
184
                            }
185
                            break;
186
                        case 'IMAGE':
187
                            $fileInfo = $this->getResource($conf['file'], $conf['file.']);
188
                            if ($fileInfo) {
189
                                $this->combinedFileNames[] = preg_replace('/\\.[[:alnum:]]+$/', '', PathUtility::basename($fileInfo[3]));
190
                                if ($fileInfo['processedFile'] instanceof ProcessedFile) {
191
                                    // Use processed file, if a FAL file has been processed by GIFBUILDER (e.g. scaled/cropped)
192
                                    $this->setup[$theKey . '.']['file'] = $fileInfo['processedFile']->getForLocalProcessing(false);
193
                                } elseif (!isset($fileInfo['origFile']) && $fileInfo['originalFile'] instanceof File) {
194
                                    // Use FAL file with getForLocalProcessing to circumvent problems with umlauts, if it is a FAL file (origFile not set)
195
                                    /** @var File $originalFile */
196
                                    $originalFile = $fileInfo['originalFile'];
197
                                    $this->setup[$theKey . '.']['file'] = $originalFile->getForLocalProcessing(false);
198
                                } else {
199
                                    // 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)
200
                                    $this->setup[$theKey . '.']['file'] = $fileInfo[3];
201
                                }
202
203
                                // only pass necessary parts of fileInfo further down, to not incorporate facts as
204
                                // CropScaleMask runs in this request, that may not occur in subsequent calls and change
205
                                // the md5 of the generated file name
206
                                $essentialFileInfo = $fileInfo;
207
                                unset($essentialFileInfo['originalFile'], $essentialFileInfo['processedFile']);
208
209
                                $this->setup[$theKey . '.']['BBOX'] = $essentialFileInfo;
210
                                $this->objBB[$theKey] = $essentialFileInfo;
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 File $originalFile */
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
            $this->setup['offset'] = (string)$this->cObj->stdWrapValue('offset', $this->setup ?? []);
248
            $this->setup['offset'] = $this->calcOffset($this->setup['offset']);
249
            $this->setup['workArea'] = (string)$this->cObj->stdWrapValue('workArea', $this->setup ?? []);
250
            $this->setup['workArea'] = $this->calcOffset($this->setup['workArea']);
251
            foreach ($sKeyArray as $theKey) {
252
                $theValue = $this->setup[$theKey];
253
                if ((int)$theKey && $this->setup[$theKey . '.']) {
254
                    switch ($theValue) {
255
                        case 'TEXT':
256
257
                        case 'IMAGE':
258
                            if (isset($this->setup[$theKey . '.']['offset.'])) {
259
                                $this->setup[$theKey . '.']['offset'] = $this->cObj->stdWrapValue('offset', $this->setup[$theKey . '.']);
260
                                unset($this->setup[$theKey . '.']['offset.']);
261
                            }
262
                            if ($this->setup[$theKey . '.']['offset']) {
263
                                $this->setup[$theKey . '.']['offset'] = $this->calcOffset($this->setup[$theKey . '.']['offset']);
264
                            }
265
                            break;
266
                        case 'BOX':
267
268
                        case 'ELLIPSE':
269
                            if (isset($this->setup[$theKey . '.']['dimensions.'])) {
270
                                $this->setup[$theKey . '.']['dimensions'] = $this->cObj->stdWrapValue('dimensions', $this->setup[$theKey . '.']);
271
                                unset($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->stdWrapValue('set', $this->setup[$theKey . '.']);
280
                                unset($this->setup[$theKey . '.']['set.']);
281
                            }
282
                            if ($this->setup[$theKey . '.']['set']) {
283
                                $this->setup[$theKey . '.']['set'] = $this->calcOffset($this->setup[$theKey . '.']['set']);
284
                            }
285
                            break;
286
                        case 'CROP':
287
                            if (isset($this->setup[$theKey . '.']['crop.'])) {
288
                                $this->setup[$theKey . '.']['crop'] = $this->cObj->stdWrapValue('crop', $this->setup[$theKey . '.']);
289
                                unset($this->setup[$theKey . '.']['crop.']);
290
                            }
291
                            if ($this->setup[$theKey . '.']['crop']) {
292
                                $this->setup[$theKey . '.']['crop'] = $this->calcOffset($this->setup[$theKey . '.']['crop']);
293
                            }
294
                            break;
295
                        case 'SCALE':
296
                            if (isset($this->setup[$theKey . '.']['width.'])) {
297
                                $this->setup[$theKey . '.']['width'] = $this->cObj->stdWrapValue('width', $this->setup[$theKey . '.']);
298
                                unset($this->setup[$theKey . '.']['width.']);
299
                            }
300
                            if ($this->setup[$theKey . '.']['width']) {
301
                                $this->setup[$theKey . '.']['width'] = $this->calcOffset($this->setup[$theKey . '.']['width']);
302
                            }
303
                            if (isset($this->setup[$theKey . '.']['height.'])) {
304
                                $this->setup[$theKey . '.']['height'] = $this->cObj->stdWrapValue('height', $this->setup[$theKey . '.']);
305
                                unset($this->setup[$theKey . '.']['height.']);
306
                            }
307
                            if ($this->setup[$theKey . '.']['height']) {
308
                                $this->setup[$theKey . '.']['height'] = $this->calcOffset($this->setup[$theKey . '.']['height']);
309
                            }
310
                            break;
311
                    }
312
                }
313
            }
314
            // Get trivial data
315
            $XY = GeneralUtility::intExplode(',', $this->setup['XY']);
316
            $maxWidth = (int)$this->cObj->stdWrapValue('maxWidth', $this->setup ?? []);
317
            $maxHeight = (int)$this->cObj->stdWrapValue('maxHeight', $this->setup ?? []);
318
            $XY[0] = MathUtility::forceIntegerInRange($XY[0], 1, $maxWidth ?: 2000);
319
            $XY[1] = MathUtility::forceIntegerInRange($XY[1], 1, $maxHeight ?: 2000);
320
            $this->XY = $XY;
321
            $this->w = $XY[0];
322
            $this->h = $XY[1];
323
            $this->OFFSET = GeneralUtility::intExplode(',', $this->setup['offset']);
324
            // this sets the workArea
325
            $this->setWorkArea($this->setup['workArea']);
326
            // this sets the default to the current;
327
            $this->defaultWorkArea = $this->workArea;
328
        }
329
    }
330
331
    /**
332
     * Initiates the image file generation if ->setup is TRUE and if the file did not exist already.
333
     * Gets filename from fileName() and if file exists in typo3temp/assets/images/ dir it will - of course - not be rendered again.
334
     * Otherwise rendering means calling ->make(), then ->output(), then ->destroy()
335
     *
336
     * @return string The filename for the created GIF/PNG file. The filename will be prefixed "GB_
337
     * @see make()
338
     * @see fileName()
339
     */
340
    public function gifBuild()
341
    {
342
        if ($this->setup) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->setup of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
343
            // Relative to Environment::getPublicPath()
344
            $gifFileName = $this->fileName('assets/images/');
345
            // File exists
346
            if (!file_exists($gifFileName)) {
347
                // Create temporary directory if not done:
348
                GeneralUtility::mkdir_deep(Environment::getPublicPath() . '/typo3temp/assets/images/');
349
                // Create file:
350
                $this->make();
351
                $this->output($gifFileName);
352
                $this->destroy();
353
            }
354
            return $gifFileName;
355
        }
356
        return '';
357
    }
358
359
    /**
360
     * The actual rendering of the image file.
361
     * Basically sets the dimensions, the background color, the traverses the array of GIFBUILDER objects and finally setting the transparent color if defined.
362
     * Creates a GDlib resource in $this->im and works on that
363
     * Called by gifBuild()
364
     *
365
     * @internal
366
     * @see gifBuild()
367
     */
368
    public function make()
369
    {
370
        // Get trivial data
371
        $XY = $this->XY;
372
        // Reset internal properties
373
        $this->saveAlphaLayer = false;
374
        // Gif-start
375
        $im = imagecreatetruecolor($XY[0], $XY[1]);
376
        if ($im === false) {
377
            throw new \RuntimeException('imagecreatetruecolor returned false', 1598350445);
378
        }
379
        $this->im = $im;
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->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 and 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->stdWrapValue($parameter, $conf);
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);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...s::maskImageOntoImage() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

423
                                $this->maskImageOntoImage(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea);
Loading history...
424
                            } else {
425
                                $this->copyImageOntoImage($this->im, $conf, $this->workArea);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...s::copyImageOntoImage() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
                                $this->copyImageOntoImage(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea);
Loading history...
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->stdWrapValue($parameter, $conf);
436
                                            $isStdWrapped[$parameter] = 1;
437
                                        }
438
                                    }
439
                                    $this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...Functions::makeShadow() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

439
                                    $this->makeShadow(/** @scrutinizer ignore-type */ $this->im, $conf['shadow.'], $this->workArea, $conf);
Loading history...
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->stdWrapValue($parameter, $conf);
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->stdWrapValue($parameter, $conf);
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);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...unctions::makeOutline() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

469
                                $this->makeOutline(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea, $txtConf);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...Functions::makeEmboss() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

474
                                $this->makeEmboss(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea, $txtConf);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...calFunctions::makeBox() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

483
                            $this->makeBox(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea);
Loading history...
484
                            break;
485
                        case 'EFFECT':
486
                            $this->makeEffect($this->im, $conf);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...Functions::makeEffect() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

486
                            $this->makeEffect(/** @scrutinizer ignore-type */ $this->im, $conf);
Loading history...
487
                            break;
488
                        case 'ADJUST':
489
                            $this->adjust($this->im, $conf);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...icalFunctions::adjust() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

489
                            $this->adjust(/** @scrutinizer ignore-type */ $this->im, $conf);
Loading history...
490
                            break;
491
                        case 'CROP':
492
                            $this->crop($this->im, $conf);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\GraphicalFunctions::crop() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

492
                            $this->crop(/** @scrutinizer ignore-type */ $this->im, $conf);
Loading history...
493
                            break;
494
                        case 'SCALE':
495
                            $this->scale($this->im, $conf);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\GraphicalFunctions::scale() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

495
                            $this->scale(/** @scrutinizer ignore-type */ $this->im, $conf);
Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $this->im can also be of type GdImage; however, parameter $im of TYPO3\CMS\Core\Imaging\G...unctions::makeEllipse() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

508
                            $this->makeEllipse(/** @scrutinizer ignore-type */ $this->im, $conf, $this->workArea);
Loading history...
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'], (bool)$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
     * @internal
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($parameter, $conf);
557
                $isStdWrapped[$parameter] = 1;
558
            }
559
        }
560
561
        if (!is_null($conf['fontFile'])) {
562
            $conf['fontFile'] = $this->checkFile($conf['fontFile']);
563
        }
564
        if (!$conf['fontFile']) {
565
            $conf['fontFile'] = $this->checkFile('EXT:core/Resources/Private/Font/nimbus.ttf');
566
        }
567
        if (!$conf['iterations']) {
568
            $conf['iterations'] = 1;
569
        }
570
        if (!$conf['fontSize']) {
571
            $conf['fontSize'] = 12;
572
        }
573
        // If any kind of spacing applies, we cannot use angles!!
574
        if ($conf['spacing'] || $conf['wordSpacing']) {
575
            $conf['angle'] = 0;
576
        }
577
        if (!isset($conf['antiAlias'])) {
578
            $conf['antiAlias'] = 1;
579
        }
580
        $conf['fontColor'] = trim($conf['fontColor']);
581
        // Strip HTML
582
        if (!$conf['doNotStripHTML']) {
583
            $conf['text'] = strip_tags($conf['text']);
584
        }
585
        $this->combinedTextStrings[] = strip_tags($conf['text']);
586
        // Max length = 100 if automatic line braks are not defined:
587
        if (!isset($conf['breakWidth']) || !$conf['breakWidth']) {
588
            $tlen = (int)$conf['textMaxLength'] ?: 100;
589
            $conf['text'] = mb_substr($conf['text'], 0, $tlen, 'utf-8');
590
        }
591
        if ((string)$conf['text'] != '') {
592
            // Char range map thingie:
593
            $fontBaseName = PathUtility::basename($conf['fontFile']);
594
            if (is_array($this->charRangeMap[$fontBaseName])) {
595
                // Initialize splitRendering array:
596
                if (!is_array($conf['splitRendering.'])) {
597
                    $conf['splitRendering.'] = [];
598
                }
599
                $cfgK = $this->charRangeMap[$fontBaseName]['cfgKey'];
600
                // Do not impose settings if a splitRendering object already exists:
601
                if (!isset($conf['splitRendering.'][$cfgK])) {
602
                    // Set configuration:
603
                    $conf['splitRendering.'][$cfgK] = 'charRange';
604
                    $conf['splitRendering.'][$cfgK . '.'] = $this->charRangeMap[$fontBaseName]['charMapConfig'];
605
                    // Multiplicator of fontsize:
606
                    if ($this->charRangeMap[$fontBaseName]['multiplicator']) {
607
                        $conf['splitRendering.'][$cfgK . '.']['fontSize'] = round($conf['fontSize'] * $this->charRangeMap[$fontBaseName]['multiplicator']);
608
                    }
609
                    // Multiplicator of pixelSpace:
610
                    if ($this->charRangeMap[$fontBaseName]['pixelSpace']) {
611
                        $travKeys = ['xSpaceBefore', 'xSpaceAfter', 'ySpaceBefore', 'ySpaceAfter'];
612
                        foreach ($travKeys as $pxKey) {
613
                            if (isset($conf['splitRendering.'][$cfgK . '.'][$pxKey])) {
614
                                $conf['splitRendering.'][$cfgK . '.'][$pxKey] = round($conf['splitRendering.'][$cfgK . '.'][$pxKey] * ($conf['fontSize'] / $this->charRangeMap[$fontBaseName]['pixelSpace']));
615
                            }
616
                        }
617
                    }
618
                }
619
            }
620
            if (is_array($conf['splitRendering.'])) {
621
                foreach ($conf['splitRendering.'] as $key => $value) {
622
                    if (is_array($conf['splitRendering.'][$key])) {
623
                        if (isset($conf['splitRendering.'][$key]['fontFile'])) {
624
                            $conf['splitRendering.'][$key]['fontFile'] = $this->checkFile($conf['splitRendering.'][$key]['fontFile']);
625
                        }
626
                    }
627
                }
628
            }
629
            return $conf;
630
        }
631
        return null;
632
    }
633
634
    /**
635
     * Calculation of offset using "splitCalc" and insertion of dimensions from other GIFBUILDER objects.
636
     *
637
     * Example:
638
     * Input: 2+2, 2*3, 123, [10.w]
639
     * Output: 4,6,123,45  (provided that the width of object in position 10 was 45 pixels wide)
640
     *
641
     * @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.
642
     * @return string The resolved string with each part (separated by comma) returned separated by comma
643
     * @internal
644
     */
645
    public function calcOffset($string)
646
    {
647
        $value = [];
648
        $numbers = GeneralUtility::trimExplode(',', $this->calculateFunctions($string));
649
        foreach ($numbers as $key => $val) {
650
            if ((string)$val == (string)(int)$val) {
651
                $value[$key] = (int)$val;
652
            } else {
653
                $value[$key] = $this->calculateValue($val);
654
            }
655
        }
656
        $string = implode(',', $value);
657
        return $string;
658
    }
659
660
    /**
661
     * Returns an "imgResource" creating an instance of the ContentObjectRenderer class and calling ContentObjectRenderer::getImgResource
662
     *
663
     * @param string $file Filename value OR the string "GIFBUILDER", see documentation in TSref for the "datatype" called "imgResource
664
     * @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)
665
     * @return array|null Returns an array with file information from ContentObjectRenderer::getImgResource()
666
     * @internal
667
     * @see ContentObjectRenderer::getImgResource()
668
     */
669
    public function getResource($file, $fileArray)
670
    {
671
        $context = GeneralUtility::makeInstance(Context::class);
672
        $deferProcessing = !$context->hasAspect('fileProcessing') || $context->getPropertyFromAspect('fileProcessing', 'deferProcessing');
673
        $context->setAspect('fileProcessing', new FileProcessingAspect(false));
674
        try {
675
            if (!in_array($fileArray['ext'], $this->imageFileExt, true)) {
676
                $fileArray['ext'] = $this->gifExtension;
677
            }
678
            /** @var ContentObjectRenderer $cObj */
679
            $cObj = GeneralUtility::makeInstance(ContentObjectRenderer::class);
680
            $cObj->start($this->data);
681
            return $cObj->getImgResource($file, $fileArray);
682
        } finally {
683
            $context->setAspect('fileProcessing', new FileProcessingAspect($deferProcessing));
684
        }
685
    }
686
687
    /**
688
     * Returns the reference to a "resource" in TypoScript.
689
     *
690
     * @param string $file The resource value.
691
     * @return string|null Returns the relative filepath or null if it's invalid
692
     * @internal
693
     * @see TemplateService::getFileName()
694
     */
695
    public function checkFile($file)
696
    {
697
        try {
698
            return GeneralUtility::makeInstance(FilePathSanitizer::class)->sanitize($file);
699
        } catch (Exception $e) {
700
            return null;
701
        }
702
    }
703
704
    /**
705
     * Calculates the GIFBUILDER output filename/path based on a serialized, hashed value of this->setup
706
     * and prefixes the original filename
707
     * also, the filename gets an additional prefix (max 100 characters),
708
     * something like "GB_MD5HASH_myfilename_is_very_long_and_such.jpg"
709
     *
710
     * @param string $pre Filename prefix, eg. "GB_
711
     * @return string The filepath, relative to Environment::getPublicPath()
712
     * @internal
713
     */
714
    public function fileName($pre)
715
    {
716
        $basicFileFunctions = GeneralUtility::makeInstance(BasicFileUtility::class);
717
        $filePrefix = implode('_', array_merge($this->combinedTextStrings, $this->combinedFileNames));
718
        $filePrefix = $basicFileFunctions->cleanFileName(ltrim($filePrefix, '.'));
719
720
        // shorten prefix to avoid overly long file names
721
        $filePrefix = substr($filePrefix, 0, 100);
722
723
        // Only take relevant parameters to ease the pain for json_encode and make the final string short
724
        // so shortMD5 is not as slow. see https://forge.typo3.org/issues/64158
725
        $hashInputForFileName = [
726
            array_keys($this->setup),
727
            $filePrefix,
728
            $this->im,
729
            $this->w,
730
            $this->h,
731
            $this->map,
732
            $this->workArea,
733
            $this->combinedTextStrings,
734
            $this->combinedFileNames,
735
            $this->data
736
        ];
737
        return 'typo3temp/' . $pre . $filePrefix . '_' . GeneralUtility::shortMD5((string)json_encode($hashInputForFileName)) . '.' . $this->extension();
738
    }
739
740
    /**
741
     * Returns the file extension used in the filename
742
     *
743
     * @return string Extension; "jpg" or "gif"/"png
744
     * @internal
745
     */
746
    public function extension()
747
    {
748
        switch (strtolower($this->setup['format'])) {
749
            case 'jpg':
750
            case 'jpeg':
751
                return 'jpg';
752
            case 'png':
753
                return 'png';
754
            case 'gif':
755
                return 'gif';
756
            default:
757
                return $this->gifExtension;
758
        }
759
    }
760
761
    /**
762
     * Calculates the value concerning the dimensions of objects.
763
     *
764
     * @param string $string The string to be calculated (e.g. "[20.h]+13")
765
     * @return int The calculated value (e.g. "23")
766
     * @see calcOffset()
767
     */
768
    protected function calculateValue($string)
769
    {
770
        $calculatedValue = 0;
771
        $parts = GeneralUtility::splitCalc($string, '+-*/%');
772
        foreach ($parts as $part) {
773
            $theVal = $part[1];
774
            $sign = $part[0];
775
            if (((string)(int)$theVal) == ((string)$theVal)) {
776
                $theVal = (int)$theVal;
777
            } elseif ('[' . substr($theVal, 1, -1) . ']' == $theVal) {
778
                $objParts = explode('.', substr($theVal, 1, -1));
779
                $theVal = 0;
780
                if (isset($this->objBB[$objParts[0]])) {
781
                    if ($objParts[1] === 'w') {
782
                        $theVal = $this->objBB[$objParts[0]][0];
783
                    } elseif ($objParts[1] === 'h') {
784
                        $theVal = $this->objBB[$objParts[0]][1];
785
                    } elseif ($objParts[1] === 'lineHeight') {
786
                        $theVal = $this->objBB[$objParts[0]][2]['lineHeight'];
787
                    }
788
                    $theVal = (int)$theVal;
789
                }
790
            } elseif ((float)$theVal) {
791
                $theVal = (float)$theVal;
792
            } else {
793
                $theVal = 0;
794
            }
795
            if ($sign === '-') {
796
                $calculatedValue -= $theVal;
797
            } elseif ($sign === '+') {
798
                $calculatedValue += $theVal;
799
            } elseif ($sign === '/' && $theVal) {
800
                $calculatedValue = $calculatedValue / $theVal;
801
            } elseif ($sign === '*') {
802
                $calculatedValue = $calculatedValue * $theVal;
803
            } elseif ($sign === '%' && $theVal) {
804
                $calculatedValue %= $theVal;
805
            }
806
        }
807
        return round($calculatedValue);
808
    }
809
810
    /**
811
     * Calculates special functions:
812
     * + max([10.h], [20.h])	-> gets the maximum of the given values
813
     *
814
     * @param string $string The raw string with functions to be calculated
815
     * @return string The calculated values
816
     */
817
    protected function calculateFunctions($string)
818
    {
819
        if (preg_match_all('#max\\(([^)]+)\\)#', $string, $matches)) {
820
            foreach ($matches[1] as $index => $maxExpression) {
821
                $string = str_replace($matches[0][$index], (string)$this->calculateMaximum($maxExpression), $string);
822
            }
823
        }
824
        return $string;
825
    }
826
827
    /**
828
     * Calculates the maximum of a set of values defined like "[10.h],[20.h],1000"
829
     *
830
     * @param string $string The string to be used to calculate the maximum (e.g. "[10.h],[20.h],1000")
831
     * @return int The maximum value of the given comma separated and calculated values
832
     */
833
    protected function calculateMaximum($string)
834
    {
835
        $parts = GeneralUtility::trimExplode(',', $this->calcOffset($string), true);
836
        $maximum = !empty($parts) ? max($parts) : 0;
837
        return $maximum;
838
    }
839
}
840