Passed
Push — master ( c577de...844f02 )
by
unknown
13:48
created

getConfigurationForImageCropScaleMask()   B

Complexity

Conditions 9
Paths 128

Size

Total Lines 40
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
dl 0
loc 40
rs 7.8222
c 1
b 0
f 0
cc 9
nc 128
nop 2
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\Core\Resource\Processing;
17
18
use TYPO3\CMS\Core\Core\Environment;
19
use TYPO3\CMS\Core\Imaging\ImageManipulation\Area;
20
use TYPO3\CMS\Core\Resource;
21
use TYPO3\CMS\Core\Resource\FileInterface;
22
use TYPO3\CMS\Core\Resource\ProcessedFile;
23
use TYPO3\CMS\Core\Utility\GeneralUtility;
24
use TYPO3\CMS\Frontend\Imaging\GifBuilder;
25
26
/**
27
 * Helper class to locally perform a crop/scale/mask task with the TYPO3 image processing classes.
28
 */
29
class LocalCropScaleMaskHelper
30
{
31
    /**
32
     * This method actually does the processing of files locally
33
     *
34
     * Takes the original file (for remote storages this will be fetched from the remote server),
35
     * does the IM magic on the local server by creating a temporary typo3temp/ file,
36
     * copies the typo3temp/ file to the processing folder of the target storage and
37
     * removes the typo3temp/ file.
38
     *
39
     * The returned array has the following structure:
40
     *   width => 100
41
     *   height => 200
42
     *   filePath => /some/path
43
     *
44
     * If filePath isn't set but width and height are the original file is used as ProcessedFile
45
     * with the returned width and height. This is for example useful for SVG images.
46
     *
47
     * @param TaskInterface $task
48
     * @return array|null
49
     */
50
    public function process(TaskInterface $task)
51
    {
52
        return $this->processWithLocalFile($task, $task->getSourceFile()->getForLocalProcessing(false));
53
    }
54
55
    /**
56
     * Does the heavy lifting prescribed in processTask()
57
     * except that the processing can be performed on any given local image
58
     *
59
     * @param TaskInterface $task
60
     * @param string $originalFileName
61
     * @return array|null
62
     */
63
    public function processWithLocalFile(TaskInterface $task, string $originalFileName): ?array
64
    {
65
        if (empty($GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'])) {
66
            return null;
67
        }
68
69
        $result = null;
70
        $targetFile = $task->getTargetFile();
71
72
        $gifBuilder = GeneralUtility::makeInstance(GifBuilder::class);
73
74
        $configuration = $targetFile->getProcessingConfiguration();
75
        $configuration['additionalParameters'] = $this->modifyImageMagickStripProfileParameters($configuration['additionalParameters'], $configuration);
76
77
        if (empty($configuration['fileExtension'])) {
78
            $configuration['fileExtension'] = $task->getTargetFileExtension();
79
        }
80
81
        $options = $this->getConfigurationForImageCropScaleMask($targetFile, $gifBuilder);
82
        // Normal situation (no masking)
83
        if (empty($configuration['maskImages'])) {
84
            // the result info is an array with 0=width,1=height,2=extension,3=filename
85
            $result = $gifBuilder->imageMagickConvert(
86
                $originalFileName,
87
                $configuration['fileExtension'] ?? null,
88
                $configuration['width'] ?? null,
89
                $configuration['height'] ?? null,
90
                $configuration['additionalParameters'] ?? null,
91
                $configuration['frame'] ?? null,
92
                $options
93
            );
94
        } else {
95
            $targetFileName = $this->getFilenameForImageCropScaleMask($task);
96
            $temporaryFileName = Environment::getPublicPath() . '/typo3temp/' . $targetFileName;
97
            $maskImage = $configuration['maskImages']['maskImage'];
98
            $maskBackgroundImage = $configuration['maskImages']['backgroundImage'];
99
            if ($maskImage instanceof FileInterface && $maskBackgroundImage instanceof FileInterface) {
100
                $temporaryExtension = 'png';
101
                if (!$GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_allowTemporaryMasksAsPng']) {
102
                    // If ImageMagick version 5+
103
                    $temporaryExtension = $gifBuilder->gifExtension;
104
                }
105
                $tempFileInfo = $gifBuilder->imageMagickConvert(
106
                    $originalFileName,
107
                    $temporaryExtension,
108
                    $configuration['width'] ?? null,
109
                    $configuration['height'] ?? null,
110
                    $configuration['additionalParameters'] ?? null,
111
                    $configuration['frame'] ?? null,
112
                    $options
113
                );
114
                if (is_array($tempFileInfo)) {
115
                    $maskBottomImage = $configuration['maskImages']['maskBottomImage'] ?? null;
116
                    if ($maskBottomImage instanceof FileInterface) {
117
                        $maskBottomImageMask = $configuration['maskImages']['maskBottomImageMask'] ?? null;
118
                    } else {
119
                        $maskBottomImageMask = null;
120
                    }
121
122
                    //	Scaling:	****
123
                    $tempScale = [];
124
                    $command = '-geometry ' . $tempFileInfo[0] . 'x' . $tempFileInfo[1] . '!';
125
                    $command = $this->modifyImageMagickStripProfileParameters($command, $configuration);
126
                    $tmpStr = $gifBuilder->randomName();
127
                    //	m_mask
128
                    $tempScale['m_mask'] = $tmpStr . '_mask.' . $temporaryExtension;
129
                    $gifBuilder->imageMagickExec($maskImage->getForLocalProcessing(true), $tempScale['m_mask'], $command);
130
                    //	m_bgImg
131
                    $tempScale['m_bgImg'] = $tmpStr . '_bgImg.miff';
132
                    $gifBuilder->imageMagickExec($maskBackgroundImage->getForLocalProcessing(), $tempScale['m_bgImg'], $command);
133
                    //	m_bottomImg / m_bottomImg_mask
134
                    if ($maskBottomImage instanceof FileInterface && $maskBottomImageMask instanceof FileInterface) {
135
                        $tempScale['m_bottomImg'] = $tmpStr . '_bottomImg.' . $temporaryExtension;
136
                        $gifBuilder->imageMagickExec($maskBottomImage->getForLocalProcessing(), $tempScale['m_bottomImg'], $command);
137
                        $tempScale['m_bottomImg_mask'] = ($tmpStr . '_bottomImg_mask.') . $temporaryExtension;
138
                        $gifBuilder->imageMagickExec($maskBottomImageMask->getForLocalProcessing(), $tempScale['m_bottomImg_mask'], $command);
139
                        // BEGIN combining:
140
                        // The image onto the background
141
                        $gifBuilder->combineExec($tempScale['m_bgImg'], $tempScale['m_bottomImg'], $tempScale['m_bottomImg_mask'], $tempScale['m_bgImg']);
142
                    }
143
                    // The image onto the background
144
                    $gifBuilder->combineExec($tempScale['m_bgImg'], $tempFileInfo[3], $tempScale['m_mask'], $temporaryFileName);
145
                    $tempFileInfo[3] = $temporaryFileName;
146
                    // Unlink the temp-images...
147
                    foreach ($tempScale as $tempFile) {
148
                        if (@is_file($tempFile)) {
149
                            unlink($tempFile);
150
                        }
151
                    }
152
                }
153
                $result = $tempFileInfo;
154
            }
155
        }
156
157
        // check if the processing really generated a new file (scaled and/or cropped)
158
        if ($result !== null) {
159
            if ($result[3] !== $originalFileName) {
160
                $result = [
161
                    'width' => $result[0],
162
                    'height' => $result[1],
163
                    'filePath' => $result[3],
164
                ];
165
            } else {
166
                // No file was generated
167
                $result = null;
168
            }
169
        }
170
171
        return $result;
172
    }
173
174
    /**
175
     * @param Resource\ProcessedFile $processedFile
176
     * @param \TYPO3\CMS\Frontend\Imaging\GifBuilder $gifBuilder
177
     *
178
     * @return array
179
     */
180
    protected function getConfigurationForImageCropScaleMask(ProcessedFile $processedFile, GifBuilder $gifBuilder)
181
    {
182
        $configuration = $processedFile->getProcessingConfiguration();
183
184
        if ($configuration['useSample'] ?? null) {
185
            $gifBuilder->scalecmd = '-sample';
186
        }
187
        $options = [];
188
        if ($configuration['maxWidth'] ?? null) {
189
            $options['maxW'] = $configuration['maxWidth'];
190
        }
191
        if ($configuration['maxHeight'] ?? null) {
192
            $options['maxH'] = $configuration['maxHeight'];
193
        }
194
        if ($configuration['minWidth'] ?? null) {
195
            $options['minW'] = $configuration['minWidth'];
196
        }
197
        if ($configuration['minHeight'] ?? null) {
198
            $options['minH'] = $configuration['minHeight'];
199
        }
200
        if ($configuration['crop'] ?? null) {
201
            $options['crop'] = $configuration['crop'];
202
            // This normalisation is required to preserve backwards compatibility
203
            // In the future the whole processing configuration should be a plain array or json serializable object for straightforward serialisation
204
            // The crop configuration currently can be a json string, string with comma separated values or an Area object
205
            if (is_string($configuration['crop'])) {
206
                // check if it is a json object
207
                $cropData = json_decode($configuration['crop']);
208
                if ($cropData) {
209
                    $options['crop'] = new Area($cropData->x, $cropData->y, $cropData->width, $cropData->height);
210
                } else {
211
                    [$offsetLeft, $offsetTop, $newWidth, $newHeight] = explode(',', $configuration['crop'], 4);
212
                    $options['crop'] = new Area($offsetLeft, $offsetTop, $newWidth, $newHeight);
0 ignored issues
show
Bug introduced by
$offsetLeft of type string is incompatible with the type double expected by parameter $x of TYPO3\CMS\Core\Imaging\I...ion\Area::__construct(). ( Ignorable by Annotation )

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

212
                    $options['crop'] = new Area(/** @scrutinizer ignore-type */ $offsetLeft, $offsetTop, $newWidth, $newHeight);
Loading history...
Bug introduced by
$newWidth of type string is incompatible with the type double expected by parameter $width of TYPO3\CMS\Core\Imaging\I...ion\Area::__construct(). ( Ignorable by Annotation )

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

212
                    $options['crop'] = new Area($offsetLeft, $offsetTop, /** @scrutinizer ignore-type */ $newWidth, $newHeight);
Loading history...
Bug introduced by
$newHeight of type string is incompatible with the type double expected by parameter $height of TYPO3\CMS\Core\Imaging\I...ion\Area::__construct(). ( Ignorable by Annotation )

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

212
                    $options['crop'] = new Area($offsetLeft, $offsetTop, $newWidth, /** @scrutinizer ignore-type */ $newHeight);
Loading history...
Bug introduced by
$offsetTop of type string is incompatible with the type double expected by parameter $y of TYPO3\CMS\Core\Imaging\I...ion\Area::__construct(). ( Ignorable by Annotation )

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

212
                    $options['crop'] = new Area($offsetLeft, /** @scrutinizer ignore-type */ $offsetTop, $newWidth, $newHeight);
Loading history...
213
                }
214
            }
215
        }
216
217
        $options['noScale'] = $configuration['noScale'];
218
219
        return $options;
220
    }
221
222
    /**
223
     * Returns the filename for a cropped/scaled/masked file.
224
     *
225
     * @param TaskInterface $task
226
     * @return string
227
     */
228
    protected function getFilenameForImageCropScaleMask(TaskInterface $task)
229
    {
230
        $configuration = $task->getTargetFile()->getProcessingConfiguration();
231
        $targetFileExtension = $task->getSourceFile()->getExtension();
232
        $processedFileExtension = $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'] ? 'png' : 'gif';
233
        if (is_array($configuration['maskImages']) && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'] && $task->getSourceFile()->getExtension() != $processedFileExtension) {
234
            $targetFileExtension = 'jpg';
235
        } elseif ($configuration['fileExtension']) {
236
            $targetFileExtension = $configuration['fileExtension'];
237
        }
238
239
        return $task->getTargetFile()->generateProcessedFileNameWithoutExtension() . '.' . ltrim(trim($targetFileExtension), '.');
240
    }
241
242
    /**
243
     * Modifies the parameters for ImageMagick for stripping of profile information.
244
     *
245
     * @param string $parameters The parameters to be modified (if required)
246
     * @param array $configuration The TypoScript configuration of [IMAGE].file
247
     * @return string
248
     */
249
    protected function modifyImageMagickStripProfileParameters($parameters, array $configuration)
250
    {
251
        // Strips profile information of image to save some space:
252
        if (isset($configuration['stripProfile'])) {
253
            if (
254
                $configuration['stripProfile']
255
                && $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] !== ''
256
            ) {
257
                $parameters = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_stripColorProfileCommand'] . $parameters;
258
            } else {
259
                $parameters .= '###SkipStripProfile###';
260
            }
261
        }
262
        return $parameters;
263
    }
264
}
265