Total Complexity | 168 |
Total Lines | 783 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GifBuilder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GifBuilder, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
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)) { |
||
|
|||
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) { |
||
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); |
||
424 | } else { |
||
425 | $this->copyImageOntoImage($this->im, $conf, $this->workArea); |
||
426 | } |
||
427 | break; |
||
428 | case 'TEXT': |
||
429 | if (!$conf['hide']) { |
||
430 | if (is_array($conf['shadow.'])) { |
||
431 | $isStdWrapped = []; |
||
432 | foreach ($conf['shadow.'] as $key => $value) { |
||
433 | $parameter = rtrim($key, '.'); |
||
434 | if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) { |
||
435 | $conf['shadow.'][$parameter] = $this->cObj->stdWrapValue($parameter, $conf); |
||
436 | $isStdWrapped[$parameter] = 1; |
||
437 | } |
||
438 | } |
||
439 | $this->makeShadow($this->im, $conf['shadow.'], $this->workArea, $conf); |
||
440 | } |
||
441 | if (is_array($conf['emboss.'])) { |
||
442 | $isStdWrapped = []; |
||
443 | foreach ($conf['emboss.'] as $key => $value) { |
||
444 | $parameter = rtrim($key, '.'); |
||
445 | if (!$isStdWrapped[$parameter] && isset($conf[$parameter . '.'])) { |
||
446 | $conf['emboss.'][$parameter] = $this->cObj->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); |
||
470 | } |
||
471 | break; |
||
472 | case 'EMBOSS': |
||
473 | if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) { |
||
474 | $this->makeEmboss($this->im, $conf, $this->workArea, $txtConf); |
||
475 | } |
||
476 | break; |
||
477 | case 'SHADOW': |
||
478 | if ($this->setup[$conf['textObjNum']] === 'TEXT' && ($txtConf = $this->checkTextObj($this->setup[$conf['textObjNum'] . '.']))) { |
||
479 | $this->makeShadow($this->im, $conf, $this->workArea, $txtConf); |
||
480 | } |
||
481 | break; |
||
482 | case 'BOX': |
||
483 | $this->makeBox($this->im, $conf, $this->workArea); |
||
484 | break; |
||
485 | case 'EFFECT': |
||
486 | $this->makeEffect($this->im, $conf); |
||
487 | break; |
||
488 | case 'ADJUST': |
||
489 | $this->adjust($this->im, $conf); |
||
490 | break; |
||
491 | case 'CROP': |
||
492 | $this->crop($this->im, $conf); |
||
493 | break; |
||
494 | case 'SCALE': |
||
495 | $this->scale($this->im, $conf); |
||
496 | break; |
||
497 | case 'WORKAREA': |
||
498 | if ($conf['set']) { |
||
499 | // this sets the workArea |
||
500 | $this->setWorkArea($conf['set']); |
||
501 | } |
||
502 | if (isset($conf['clear'])) { |
||
503 | // This sets the current to the default; |
||
504 | $this->workArea = $this->defaultWorkArea; |
||
505 | } |
||
506 | break; |
||
507 | case 'ELLIPSE': |
||
508 | $this->makeEllipse($this->im, $conf, $this->workArea); |
||
509 | break; |
||
510 | } |
||
511 | } |
||
512 | } |
||
513 | } |
||
514 | // Preserve alpha transparency |
||
515 | if (!$this->saveAlphaLayer) { |
||
516 | if ($this->setup['transparentBackground']) { |
||
517 | // Auto transparent background is set |
||
518 | $Bcolor = imagecolorclosest($this->im, $BGcols[0], $BGcols[1], $BGcols[2]); |
||
519 | imagecolortransparent($this->im, $Bcolor); |
||
520 | } elseif (is_array($this->setup['transparentColor_array'])) { |
||
521 | // Multiple transparent colors are set. This is done via the trick that all transparent colors get |
||
522 | // converted to one color and then this one gets set as transparent as png/gif can just have one |
||
523 | // transparent color. |
||
524 | $Tcolor = $this->unifyColors($this->im, $this->setup['transparentColor_array'], (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) |
||
838 | } |
||
839 | } |
||
840 |