EnvironmentController.php 47.8 KB
Newer Older
1
<?php
2

3
declare(strict_types=1);
4
5
6
7
8
9
10
11
12
13
14
15
16
17

/*
 * This file is part of the TYPO3 CMS project.
 *
 * It is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, either version 2
 * of the License, or any later version.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * The TYPO3 project - inspiring people to share!
 */

18
19
namespace TYPO3\CMS\Install\Controller;

20
21
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
22
use Symfony\Component\Mime\Address;
23
use Symfony\Component\Mime\Exception\RfcComplianceException;
24
use TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus;
25
use TYPO3\CMS\Core\Core\Environment;
26
27
28
29
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\FormProtection\FormProtectionFactory;
use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection;
use TYPO3\CMS\Core\Http\JsonResponse;
30
use TYPO3\CMS\Core\Imaging\GraphicalFunctions;
31
32
use TYPO3\CMS\Core\Mail\FluidEmail;
use TYPO3\CMS\Core\Mail\Mailer;
33
34
use TYPO3\CMS\Core\Messaging\FlashMessage;
use TYPO3\CMS\Core\Messaging\FlashMessageQueue;
35
use TYPO3\CMS\Core\Utility\CommandUtility;
36
37
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
38
use TYPO3\CMS\Core\Utility\MathUtility;
39
use TYPO3\CMS\Core\Utility\StringUtility;
40
41
use TYPO3\CMS\Install\FolderStructure\DefaultFactory;
use TYPO3\CMS\Install\FolderStructure\DefaultPermissionsCheck;
42
use TYPO3\CMS\Install\Service\LateBootService;
43
44
use TYPO3\CMS\Install\SystemEnvironment\Check;
use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck;
45
use TYPO3\CMS\Install\SystemEnvironment\ServerResponse\ServerResponseCheck;
46
use TYPO3\CMS\Install\SystemEnvironment\SetupCheck;
47
48

/**
49
 * Environment controller
50
 * @internal This class is a specific controller implementation and is not considered part of the Public TYPO3 API.
51
 */
52
class EnvironmentController extends AbstractController
53
{
54
    private const IMAGE_FILE_EXT = ['gif', 'jpg', 'png', 'tif', 'ai', 'pdf', 'webp'];
55
    private const TEST_REFERENCE_PATH = __DIR__ . '/../../Resources/Public/Images/TestReference';
56

57
58
59
60
61
62
63
64
65
66
67
    /**
     * @var LateBootService
     */
    private $lateBootService;

    public function __construct(
        LateBootService $lateBootService
    ) {
        $this->lateBootService = $lateBootService;
    }

68
    /**
69
     * Main "show the cards" view
70
     *
71
72
     * @param ServerRequestInterface $request
     * @return ResponseInterface
73
     */
74
    public function cardsAction(ServerRequestInterface $request): ResponseInterface
75
    {
76
        $view = $this->initializeView($request);
77
78
        return new JsonResponse([
            'success' => true,
79
            'html' => $view->render('Environment/Cards'),
80
81
82
        ]);
    }

Philipp Hamid's avatar
Philipp Hamid committed
83
84
85
    /**
     * System Information Get Data action
     *
86
     * @param ServerRequestInterface $request
Philipp Hamid's avatar
Philipp Hamid committed
87
88
89
90
     * @return ResponseInterface
     */
    public function systemInformationGetDataAction(ServerRequestInterface $request): ResponseInterface
    {
91
        $view = $this->initializeView($request);
Philipp Hamid's avatar
Philipp Hamid committed
92
        $view->assignMultiple([
93
            'systemInformationCgiDetected' => Environment::isRunningOnCgiServer(),
Philipp Hamid's avatar
Philipp Hamid committed
94
            'systemInformationDatabaseConnections' => $this->getDatabaseConnectionInformation(),
95
            'systemInformationOperatingSystem' => Environment::isWindows() ? 'Windows' : 'Unix',
96
            'systemInformationApplicationContext' => $this->getApplicationContextInformation(),
97
            'phpVersion' => PHP_VERSION,
Philipp Hamid's avatar
Philipp Hamid committed
98
99
100
        ]);
        return new JsonResponse([
            'success' => true,
101
            'html' => $view->render('Environment/SystemInformation'),
Philipp Hamid's avatar
Philipp Hamid committed
102
103
104
105
106
107
        ]);
    }

    /**
     * System Information Get Data action
     *
108
     * @param ServerRequestInterface $request
Philipp Hamid's avatar
Philipp Hamid committed
109
110
111
112
     * @return ResponseInterface
     */
    public function phpInfoGetDataAction(ServerRequestInterface $request): ResponseInterface
    {
113
        $view = $this->initializeView($request);
Philipp Hamid's avatar
Philipp Hamid committed
114
115
        return new JsonResponse([
            'success' => true,
116
            'html' => $view->render('Environment/PhpInfo'),
Philipp Hamid's avatar
Philipp Hamid committed
117
118
119
        ]);
    }

120
121
122
    /**
     * Get environment status
     *
123
     * @param ServerRequestInterface $request
124
125
     * @return ResponseInterface
     */
Philipp Hamid's avatar
Philipp Hamid committed
126
    public function environmentCheckGetStatusAction(ServerRequestInterface $request): ResponseInterface
127
    {
128
        $view = $this->initializeView($request);
129
130
131
132
        $messageQueue = new FlashMessageQueue('install');
        $checkMessages = (new Check())->getStatus();
        foreach ($checkMessages as $message) {
            $messageQueue->enqueue($message);
133
        }
134
135
136
        $setupMessages = (new SetupCheck())->getStatus();
        foreach ($setupMessages as $message) {
            $messageQueue->enqueue($message);
137
        }
138
139
140
        $databaseMessages = (new DatabaseCheck())->getStatus();
        foreach ($databaseMessages as $message) {
            $messageQueue->enqueue($message);
141
        }
142
143
144
145
        $serverResponseMessages = (new ServerResponseCheck(false))->getStatus();
        foreach ($serverResponseMessages as $message) {
            $messageQueue->enqueue($message);
        }
146
147
148
149
150
151
152
153
154
        return new JsonResponse([
            'success' => true,
            'status' => [
                'error' => $messageQueue->getAllMessages(FlashMessage::ERROR),
                'warning' => $messageQueue->getAllMessages(FlashMessage::WARNING),
                'ok' => $messageQueue->getAllMessages(FlashMessage::OK),
                'information' => $messageQueue->getAllMessages(FlashMessage::INFO),
                'notice' => $messageQueue->getAllMessages(FlashMessage::NOTICE),
            ],
155
            'html' => $view->render('Environment/EnvironmentCheck'),
156
157
158
159
160
161
            'buttons' => [
                [
                    'btnClass' => 'btn-default t3js-environmentCheck-execute',
                    'text' => 'Run tests again',
                ],
            ],
162
163
164
165
166
167
        ]);
    }

    /**
     * Get folder structure status
     *
168
     * @param ServerRequestInterface $request
169
170
     * @return ResponseInterface
     */
Philipp Hamid's avatar
Philipp Hamid committed
171
    public function folderStructureGetStatusAction(ServerRequestInterface $request): ResponseInterface
172
    {
173
        $view = $this->initializeView($request);
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
        $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
        $structureFacade = $folderStructureFactory->getStructure();

        $structureMessages = $structureFacade->getStatus();
        $errorQueue = new FlashMessageQueue('install');
        $okQueue = new FlashMessageQueue('install');
        foreach ($structureMessages as $message) {
            if ($message->getSeverity() === FlashMessage::ERROR
                || $message->getSeverity() === FlashMessage::WARNING
            ) {
                $errorQueue->enqueue($message);
            } else {
                $okQueue->enqueue($message);
            }
        }

        $permissionCheck = GeneralUtility::makeInstance(DefaultPermissionsCheck::class);

192
193
        $view->assign('publicPath', Environment::getPublicPath());

194
195
196
197
198
199
200
201
        $buttons = [];
        if ($errorQueue->count() > 0) {
            $buttons[] = [
                'btnClass' => 'btn-default t3js-folderStructure-errors-fix',
                'text' => 'Try to fix file and folder permissions',
            ];
        }

202
203
204
205
206
207
        return new JsonResponse([
            'success' => true,
            'errorStatus' => $errorQueue,
            'okStatus' => $okQueue,
            'folderStructureFilePermissionStatus' => $permissionCheck->getMaskStatus('fileCreateMask'),
            'folderStructureDirectoryPermissionStatus' => $permissionCheck->getMaskStatus('folderCreateMask'),
208
            'html' => $view->render('Environment/FolderStructure'),
209
            'buttons' => $buttons,
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
        ]);
    }

    /**
     * Try to fix folder structure errors
     *
     * @return ResponseInterface
     */
    public function folderStructureFixAction(): ResponseInterface
    {
        $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class);
        $structureFacade = $folderStructureFactory->getStructure();
        $fixedStatusObjects = $structureFacade->fix();
        return new JsonResponse([
            'success' => true,
            'fixedStatus' => $fixedStatusObjects,
        ]);
    }

Philipp Hamid's avatar
Philipp Hamid committed
229
230
231
    /**
     * System Information Get Data action
     *
232
     * @param ServerRequestInterface $request
Philipp Hamid's avatar
Philipp Hamid committed
233
234
235
236
     * @return ResponseInterface
     */
    public function mailTestGetDataAction(ServerRequestInterface $request): ResponseInterface
    {
237
        $view = $this->initializeView($request);
Philipp Hamid's avatar
Philipp Hamid committed
238
239
240
241
242
243
244
        $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class);
        $view->assignMultiple([
            'mailTestToken' => $formProtection->generateToken('installTool', 'mailTest'),
            'mailTestSenderAddress' => $this->getSenderEmailAddress(),
        ]);
        return new JsonResponse([
            'success' => true,
245
            'html' => $view->render('Environment/MailTest'),
246
247
248
249
250
251
            'buttons' => [
                [
                    'btnClass' => 'btn-default t3js-mailTest-execute',
                    'text' => 'Send test mail',
                ],
            ],
Philipp Hamid's avatar
Philipp Hamid committed
252
253
254
        ]);
    }

255
256
257
    /**
     *  Send a test mail
     *
258
     * @param ServerRequestInterface $request
259
260
261
262
     * @return ResponseInterface
     */
    public function mailTestAction(ServerRequestInterface $request): ResponseInterface
    {
263
264
        $container = $this->lateBootService->getContainer();
        $backup = $this->lateBootService->makeCurrent($container);
265
266
267
268
269
270
271
272
273
        $messages = new FlashMessageQueue('install');
        $recipient = $request->getParsedBody()['install']['email'];
        if (empty($recipient) || !GeneralUtility::validEmail($recipient)) {
            $messages->enqueue(new FlashMessage(
                'Given address is not a valid email address.',
                'Mail not sent',
                FlashMessage::ERROR
            ));
        } else {
274
            try {
275
276
277
                $variables = [
                    'headline' => 'TYPO3 Test Mail',
                    'introduction' => 'Hey TYPO3 Administrator',
278
                    'content' => 'Seems like your favorite TYPO3 installation can send out emails!',
279
280
                ];
                $mailMessage = GeneralUtility::makeInstance(FluidEmail::class);
281
282
                $mailMessage
                    ->to($recipient)
283
                    ->from(new Address($this->getSenderEmailAddress(), $this->getSenderEmailName()))
284
                    ->subject($this->getEmailSubject())
285
286
287
288
                    ->setRequest($request)
                    ->assignMultiple($variables);

                GeneralUtility::makeInstance(Mailer::class)->send($mailMessage);
289
290
291
292
                $messages->enqueue(new FlashMessage(
                    'Recipient: ' . $recipient,
                    'Test mail sent'
                ));
293
            } catch (RfcComplianceException $exception) {
294
295
296
297
298
299
300
301
302
303
304
305
306
307
                $messages->enqueue(new FlashMessage(
                    'Please verify $GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'defaultMailFromAddress\'] is a valid mail address.'
                    . ' Error message: ' . $exception->getMessage(),
                    'RFC compliance problem',
                    FlashMessage::ERROR
                ));
            } catch (\Throwable $throwable) {
                $messages->enqueue(new FlashMessage(
                    'Please verify $GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][*] settings are valid.'
                    . ' Error message: ' . $throwable->getMessage(),
                    'Could not deliver mail',
                    FlashMessage::ERROR
                ));
            }
308
        }
309
        $this->lateBootService->makeCurrent(null, $backup);
310
311
312
313
        return new JsonResponse([
            'success' => true,
            'status' => $messages,
        ]);
314
315
    }

Philipp Hamid's avatar
Philipp Hamid committed
316
317
318
    /**
     * System Information Get Data action
     *
319
     * @param ServerRequestInterface $request
Philipp Hamid's avatar
Philipp Hamid committed
320
321
322
323
     * @return ResponseInterface
     */
    public function imageProcessingGetDataAction(ServerRequestInterface $request): ResponseInterface
    {
324
        $view = $this->initializeView($request);
Philipp Hamid's avatar
Philipp Hamid committed
325
326
327
328
329
330
331
332
333
334
335
336
        $view->assignMultiple([
            'imageProcessingProcessor' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor'] === 'GraphicsMagick' ? 'GraphicsMagick' : 'ImageMagick',
            'imageProcessingEnabled' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'],
            'imageProcessingPath' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path'],
            'imageProcessingVersion' => $this->determineImageMagickVersion(),
            'imageProcessingEffects' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_effects'],
            'imageProcessingGdlibEnabled' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'],
            'imageProcessingGdlibPng' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'],
            'imageProcessingFileFormats' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
        ]);
        return new JsonResponse([
            'success' => true,
337
            'html' => $view->render('Environment/ImageProcessing'),
338
339
340
341
342
343
            'buttons' => [
                [
                    'btnClass' => 'btn-default disabled t3js-imageProcessing-execute',
                    'text' => 'Run image tests again',
                ],
            ],
Philipp Hamid's avatar
Philipp Hamid committed
344
345
346
        ]);
    }

347
348
349
    /**
     * Create true type font test image
     *
350
     * @return ResponseInterface
351
     */
352
    public function imageProcessingTrueTypeAction(): ResponseInterface
353
354
355
356
357
358
    {
        $image = @imagecreate(200, 50);
        imagecolorallocate($image, 255, 255, 55);
        $textColor = imagecolorallocate($image, 233, 14, 91);
        @imagettftext(
            $image,
359
            20 / 96.0 * 72, // As in compensateFontSizeBasedOnFreetypeDpi
360
361
362
363
364
365
366
            0,
            10,
            20,
            $textColor,
            ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf',
            'Testing true type'
        );
367
        $outputFile = Environment::getPublicPath() . '/typo3temp/assets/images/installTool-' . StringUtility::getUniqueId('createTrueTypeFontTestImage') . '.gif';
368
        @imagegif($image, $outputFile);
369
370
371
372
        $fileExists = file_exists($outputFile);
        if ($fileExists) {
            GeneralUtility::fixPermissions($outputFile);
        }
373
        $result = [
374
            'fileExists' => $fileExists,
375
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Font.gif',
376
377
378
379
380
        ];
        if ($fileExists) {
            $result['outputFile'] = $outputFile;
        }
        return $this->getImageTestResponse($result);
381
382
383
384
385
    }

    /**
     * Convert to jpg from jpg
     *
386
     * @return ResponseInterface
387
     */
388
    public function imageProcessingReadJpgAction(): ResponseInterface
389
390
391
392
393
394
395
    {
        return $this->convertImageFormatsToJpg('jpg');
    }

    /**
     * Convert to jpg from gif
     *
396
     * @return ResponseInterface
397
     */
398
    public function imageProcessingReadGifAction(): ResponseInterface
399
400
401
402
403
404
405
    {
        return $this->convertImageFormatsToJpg('gif');
    }

    /**
     * Convert to jpg from png
     *
406
     * @return ResponseInterface
407
     */
408
    public function imageProcessingReadPngAction(): ResponseInterface
409
410
411
412
413
414
415
    {
        return $this->convertImageFormatsToJpg('png');
    }

    /**
     * Convert to jpg from tif
     *
416
     * @return ResponseInterface
417
     */
418
    public function imageProcessingReadTifAction(): ResponseInterface
419
420
421
422
423
424
425
    {
        return $this->convertImageFormatsToJpg('tif');
    }

    /**
     * Convert to jpg from pdf
     *
426
     * @return ResponseInterface
427
     */
428
    public function imageProcessingReadPdfAction(): ResponseInterface
429
430
431
432
433
434
435
    {
        return $this->convertImageFormatsToJpg('pdf');
    }

    /**
     * Convert to jpg from ai
     *
436
     * @return ResponseInterface
437
     */
438
    public function imageProcessingReadAiAction(): ResponseInterface
439
440
441
442
443
444
445
    {
        return $this->convertImageFormatsToJpg('ai');
    }

    /**
     * Writing gif test
     *
446
     * @return ResponseInterface
447
     */
448
    public function imageProcessingWriteGifAction(): ResponseInterface
449
450
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
451
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
452
                'status' => [$this->imageMagickDisabledMessage()],
453
            ]);
454
455
456
457
458
459
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $inputFile = $imageBasePath . 'TestInput/Test.gif';
        $imageProcessor = $this->initializeImageProcessor();
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-gif');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'gif', '300', '', '', '', [], true);
460
        $messages = new FlashMessageQueue('install');
461
462
463
        if ($imResult !== null && is_file($imResult[3])) {
            if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gif_compress']) {
                clearstatcache();
464
                $previousSize = GeneralUtility::formatSize((int)filesize($imResult[3]));
465
466
                $methodUsed = GraphicalFunctions::gifCompress($imResult[3], '');
                clearstatcache();
467
                $compressedSize = GeneralUtility::formatSize((int)filesize($imResult[3]));
468
                $messages->enqueue(new FlashMessage(
469
                    'Method used by compress: ' . $methodUsed . LF
470
                    . ' Previous filesize: ' . $previousSize . '. Current filesize:' . $compressedSize,
471
472
473
                    'Compressed gif',
                    FlashMessage::INFO
                ));
474
            } else {
475
476
477
478
479
                $messages->enqueue(new FlashMessage(
                    '',
                    'Gif compression not enabled by [GFX][gif_compress]',
                    FlashMessage::INFO
                ));
480
481
            }
            $result = [
482
                'status' => $messages,
483
484
                'fileExists' => true,
                'outputFile' => $imResult[3],
485
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-gif.gif',
486
487
488
489
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
490
                'status' => [$this->imageGenerationFailedMessage()],
491
                'command' => $imageProcessor->IM_commands,
492
493
            ];
        }
494
        return $this->getImageTestResponse($result);
495
496
497
498
499
    }

    /**
     * Writing png test
     *
500
     * @return ResponseInterface
501
     */
502
    public function imageProcessingWritePngAction(): ResponseInterface
503
504
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
505
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
506
                'status' => [$this->imageMagickDisabledMessage()],
507
            ]);
508
509
510
511
512
513
514
515
516
517
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $inputFile = $imageBasePath . 'TestInput/Test.png';
        $imageProcessor = $this->initializeImageProcessor();
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-png');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'png', '300', '', '', '', [], true);
        if ($imResult !== null && is_file($imResult[3])) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
518
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-png.png',
519
520
521
522
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
523
                'status' => [$this->imageGenerationFailedMessage()],
524
                'command' => $imageProcessor->IM_commands,
525
526
            ];
        }
527
        return $this->getImageTestResponse($result);
528
    }
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
    /**
     * Writing webp test
     *
     * @return ResponseInterface
     */
    public function imageProcessingWriteWebpAction(): ResponseInterface
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
            return new JsonResponse([
                'status' => [$this->imageMagickDisabledMessage()],
            ]);
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $inputFile = $imageBasePath . 'TestInput/Test.webp';
        $imageProcessor = $this->initializeImageProcessor();
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-webp');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'webp', '300', '', '', '', [], true);
        if ($imResult !== null && is_file($imResult[3])) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
550
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-webp.webp',
551
552
553
554
555
556
557
558
559
560
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
                'status' => [$this->imageGenerationFailedMessage()],
                'command' => $imageProcessor->IM_commands,
            ];
        }
        return $this->getImageTestResponse($result);
    }
561
562
563
564

    /**
     * Scaling transparent files - gif to gif
     *
565
     * @return ResponseInterface
566
     */
567
    public function imageProcessingGifToGifAction(): ResponseInterface
568
569
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
570
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
571
                'status' => [$this->imageMagickDisabledMessage()],
572
            ]);
573
574
575
576
577
578
579
580
581
582
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/Transparent.gif';
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-gif');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'gif', '300', '', '', '', [], true);
        if ($imResult !== null && file_exists($imResult[3])) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
583
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-gif.gif',
584
585
586
587
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
588
                'status' => [$this->imageGenerationFailedMessage()],
589
                'command' => $imageProcessor->IM_commands,
590
591
            ];
        }
592
        return $this->getImageTestResponse($result);
593
594
595
596
597
    }

    /**
     * Scaling transparent files - png to png
     *
598
     * @return ResponseInterface
599
     */
600
    public function imageProcessingPngToPngAction(): ResponseInterface
601
602
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
603
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
604
                'status' => [$this->imageMagickDisabledMessage()],
605
            ]);
606
607
608
609
610
611
612
613
614
615
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/Transparent.png';
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-png');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'png', '300', '', '', '', [], true);
        if ($imResult !== null && file_exists($imResult[3])) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
616
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-png.png',
617
618
619
620
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
621
                'status' => [$this->imageGenerationFailedMessage()],
622
                'command' => $imageProcessor->IM_commands,
623
624
            ];
        }
625
        return $this->getImageTestResponse($result);
626
627
628
629
630
    }

    /**
     * Scaling transparent files - gif to jpg
     *
631
     * @return ResponseInterface
632
     */
633
    public function imageProcessingGifToJpgAction(): ResponseInterface
634
635
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
636
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
637
                'status' => [$this->imageMagickDisabledMessage()],
638
            ]);
639
640
641
642
643
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/Transparent.gif';
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-jpg');
644
645
        $jpegQuality = MathUtility::forceIntegerInRange($GLOBALS['TYPO3_CONF_VARS']['GFX']['jpg_quality'], 10, 100, 85);
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'jpg', '300', '', '-quality ' . $jpegQuality . ' -opaque white -background white -flatten', '', [], true);
646
647
648
649
        if ($imResult !== null && file_exists($imResult[3])) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
650
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-jpg.jpg',
651
652
653
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
            $result = [
                'status' => [$this->imageGenerationFailedMessage()],
                'command' => $imageProcessor->IM_commands,
            ];
        }
        return $this->getImageTestResponse($result);
    }

    /**
     * Converting jpg to webp
     *
     * @return ResponseInterface
     */
    public function imageProcessingJpgToWebpAction(): ResponseInterface
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
            return new JsonResponse([
                'status' => [$this->imageMagickDisabledMessage()],
            ]);
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/Test.jpg';
        $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('read-webp');
        $imResult = $imageProcessor->imageMagickConvert($inputFile, 'webp', '300', '', '', '', [], true);
        if ($imResult !== null) {
            $result = [
                'fileExists' => file_exists($imResult[3]),
                'outputFile' => $imResult[3],
683
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Convert-webp.webp',
684
685
686
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
687
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
688
                'status' => [$this->imageGenerationFailedMessage()],
689
                'command' => $imageProcessor->IM_commands,
690
691
            ];
        }
692
        return $this->getImageTestResponse($result);
693
694
695
696
697
    }

    /**
     * Combine images with gif mask
     *
698
     * @return ResponseInterface
699
     */
700
    public function imageProcessingCombineGifMaskAction(): ResponseInterface
701
702
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
703
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
704
                'status' => [$this->imageMagickDisabledMessage()],
705
            ]);
706
707
708
709
710
711
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/BackgroundOrange.gif';
        $overlayFile = $imageBasePath . 'TestInput/Test.jpg';
        $maskFile = $imageBasePath . 'TestInput/MaskBlackWhite.gif';
712
        $resultFile = $this->getImagesPath() . $imageProcessor->filenamePrefix
713
714
715
716
717
718
719
            . StringUtility::getUniqueId($imageProcessor->alternativeOutputKey . 'combine1') . '.jpg';
        $imageProcessor->combineExec($inputFile, $overlayFile, $maskFile, $resultFile);
        $imResult = $imageProcessor->getImageDimensions($resultFile);
        if ($imResult) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
720
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Combine-1.jpg',
721
722
723
724
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
725
                'status' => [$this->imageGenerationFailedMessage()],
726
                'command' => $imageProcessor->IM_commands,
727
728
            ];
        }
729
        return $this->getImageTestResponse($result);
730
731
732
733
734
    }

    /**
     * Combine images with jpg mask
     *
735
     * @return ResponseInterface
736
     */
737
    public function imageProcessingCombineJpgMaskAction(): ResponseInterface
738
739
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
740
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
741
                'status' => [$this->imageMagickDisabledMessage()],
742
            ]);
743
744
745
746
747
748
        }
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $imageProcessor = $this->initializeImageProcessor();
        $inputFile = $imageBasePath . 'TestInput/BackgroundCombine.jpg';
        $overlayFile = $imageBasePath . 'TestInput/Test.jpg';
        $maskFile = $imageBasePath . 'TestInput/MaskCombine.jpg';
749
        $resultFile = $this->getImagesPath() . $imageProcessor->filenamePrefix
750
751
752
753
754
755
756
            . StringUtility::getUniqueId($imageProcessor->alternativeOutputKey . 'combine2') . '.jpg';
        $imageProcessor->combineExec($inputFile, $overlayFile, $maskFile, $resultFile);
        $imResult = $imageProcessor->getImageDimensions($resultFile);
        if ($imResult) {
            $result = [
                'fileExists' => true,
                'outputFile' => $imResult[3],
757
                'referenceFile' => self::TEST_REFERENCE_PATH . '/Combine-2.jpg',
758
759
760
761
                'command' => $imageProcessor->IM_commands,
            ];
        } else {
            $result = [
Christian Kuhn's avatar
Christian Kuhn committed
762
                'status' => [$this->imageGenerationFailedMessage()],
763
                'command' => $imageProcessor->IM_commands,
764
765
            ];
        }
766
        return $this->getImageTestResponse($result);
767
768
769
770
771
    }

    /**
     * GD with simple box
     *
772
     * @return ResponseInterface
773
     */
774
    public function imageProcessingGdlibSimpleAction(): ResponseInterface
775
776
777
778
779
780
781
782
783
784
785
786
    {
        $imageProcessor = $this->initializeImageProcessor();
        $gifOrPng = $imageProcessor->gifExtension;
        $image = imagecreatetruecolor(300, 225);
        $backgroundColor = imagecolorallocate($image, 0, 0, 0);
        imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor);
        $workArea = [0, 0, 300, 225];
        $conf = [
            'dimensions' => '10,50,280,50',
            'color' => 'olive',
        ];
        $imageProcessor->makeBox($image, $conf, $workArea);
787
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdSimple') . '.' . $gifOrPng;
788
789
790
791
792
        $imageProcessor->ImageWrite($image, $outputFile);
        $imResult = $imageProcessor->getImageDimensions($outputFile);
        $result = [
            'fileExists' => true,
            'outputFile' => $imResult[3],
793
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-simple.' . $gifOrPng,
794
795
            'command' => $imageProcessor->IM_commands,
        ];
796
        return $this->getImageTestResponse($result);
797
798
799
800
801
    }

    /**
     * GD from image with box
     *
802
     * @return ResponseInterface
803
     */
804
    public function imageProcessingGdlibFromFileAction(): ResponseInterface
805
806
807
808
809
810
811
812
813
814
815
816
    {
        $imageProcessor = $this->initializeImageProcessor();
        $gifOrPng = $imageProcessor->gifExtension;
        $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/';
        $inputFile = $imageBasePath . 'TestInput/Test.' . $gifOrPng;
        $image = $imageProcessor->imageCreateFromFile($inputFile);
        $workArea = [0, 0, 400, 300];
        $conf = [
            'dimensions' => '10,50,380,50',
            'color' => 'olive',
        ];
        $imageProcessor->makeBox($image, $conf, $workArea);
817
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdBox') . '.' . $gifOrPng;
818
819
820
821
822
        $imageProcessor->ImageWrite($image, $outputFile);
        $imResult = $imageProcessor->getImageDimensions($outputFile);
        $result = [
            'fileExists' => true,
            'outputFile' => $imResult[3],
823
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-box.' . $gifOrPng,
824
825
            'command' => $imageProcessor->IM_commands,
        ];
826
        return $this->getImageTestResponse($result);
827
828
829
830
831
    }

    /**
     * GD with text
     *
832
     * @return ResponseInterface
833
     */
834
    public function imageProcessingGdlibRenderTextAction(): ResponseInterface
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
    {
        $imageProcessor = $this->initializeImageProcessor();
        $gifOrPng = $imageProcessor->gifExtension;
        $image = imagecreatetruecolor(300, 225);
        $backgroundColor = imagecolorallocate($image, 128, 128, 150);
        imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor);
        $workArea = [0, 0, 300, 225];
        $conf = [
            'iterations' => 1,
            'angle' => 0,
            'antiAlias' => 1,
            'text' => 'HELLO WORLD',
            'fontColor' => '#003366',
            'fontSize' => 30,
            'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf',
            'offset' => '30,80',
        ];
        $conf['BBOX'] = $imageProcessor->calcBBox($conf);
        $imageProcessor->makeText($image, $conf, $workArea);
854
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng;
855
856
857
858
859
        $imageProcessor->ImageWrite($image, $outputFile);
        $imResult = $imageProcessor->getImageDimensions($outputFile);
        $result = [
            'fileExists' => true,
            'outputFile' => $imResult[3],
860
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-text.' . $gifOrPng,
861
862
            'command' => $imageProcessor->IM_commands,
        ];
863
        return $this->getImageTestResponse($result);
864
865
866
867
868
    }

    /**
     * GD with text, niceText
     *
869
     * @return ResponseInterface
870
     */
871
    public function imageProcessingGdlibNiceTextAction(): ResponseInterface
872
873
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
874
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
875
                'status' => [$this->imageMagickDisabledMessage()],
876
            ]);
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
        }
        $imageProcessor = $this->initializeImageProcessor();
        $gifOrPng = $imageProcessor->gifExtension;
        $image = imagecreatetruecolor(300, 225);
        $backgroundColor = imagecolorallocate($image, 128, 128, 150);
        imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor);
        $workArea = [0, 0, 300, 225];
        $conf = [
            'iterations' => 1,
            'angle' => 0,
            'antiAlias' => 1,
            'text' => 'HELLO WORLD',
            'fontColor' => '#003366',
            'fontSize' => 30,
            'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf',
            'offset' => '30,80',
        ];
        $conf['BBOX'] = $imageProcessor->calcBBox($conf);
        $imageProcessor->makeText($image, $conf, $workArea);
896
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng;
897
898
899
900
        $imageProcessor->ImageWrite($image, $outputFile);
        $conf['offset'] = '30,120';
        $conf['niceText'] = 1;
        $imageProcessor->makeText($image, $conf, $workArea);
901
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.' . $gifOrPng;
902
903
904
905
906
        $imageProcessor->ImageWrite($image, $outputFile);
        $imResult = $imageProcessor->getImageDimensions($outputFile);
        $result = [
            'fileExists' => true,
            'outputFile' => $imResult[3],
907
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-niceText.' . $gifOrPng,
908
909
            'command' => $imageProcessor->IM_commands,
        ];
910
        return $this->getImageTestResponse($result);
911
912
913
914
915
    }

    /**
     * GD with text, niceText, shadow
     *
916
     * @return ResponseInterface
917
     */
918
    public function imageProcessingGdlibNiceTextShadowAction(): ResponseInterface
919
920
    {
        if (!$this->isImageMagickEnabledAndConfigured()) {
921
            return new JsonResponse([
Christian Kuhn's avatar
Christian Kuhn committed
922
                'status' => [$this->imageMagickDisabledMessage()],
923
            ]);
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
        }
        $imageProcessor = $this->initializeImageProcessor();
        $gifOrPng = $imageProcessor->gifExtension;
        $image = imagecreatetruecolor(300, 225);
        $backgroundColor = imagecolorallocate($image, 128, 128, 150);
        imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor);
        $workArea = [0, 0, 300, 225];
        $conf = [
            'iterations' => 1,
            'angle' => 0,
            'antiAlias' => 1,
            'text' => 'HELLO WORLD',
            'fontColor' => '#003366',
            'fontSize' => 30,
            'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf',
            'offset' => '30,80',
        ];
        $conf['BBOX'] = $imageProcessor->calcBBox($conf);
        $imageProcessor->makeText($image, $conf, $workArea);
943
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng;
944
945
946
947
        $imageProcessor->ImageWrite($image, $outputFile);
        $conf['offset'] = '30,120';
        $conf['niceText'] = 1;
        $imageProcessor->makeText($image, $conf, $workArea);
948
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.' . $gifOrPng;
949
950
951
952
953
        $imageProcessor->ImageWrite($image, $outputFile);
        $conf['offset'] = '30,160';
        $conf['niceText'] = 1;
        $conf['shadow.'] = [
            'offset' => '2,2',
954
            'blur' => '20',
955
            'opacity' => '50',
956
            'color' => 'black',
957
958
959
960
        ];
        // Warning: Re-uses $image from above!
        $imageProcessor->makeShadow($image, $conf['shadow.'], $workArea, $conf);
        $imageProcessor->makeText($image, $conf, $workArea);
961
        $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('GDwithText-niceText-shadow') . '.' . $gifOrPng;
962
963
964
965
966
        $imageProcessor->ImageWrite($image, $outputFile);
        $imResult = $imageProcessor->getImageDimensions($outputFile);
        $result = [
            'fileExists' => true,
            'outputFile' => $imResult[3],
967
            'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-shadow.' . $gifOrPng,
968
969
            'command' => $imageProcessor->IM_commands,
        ];
970
        return $this->getImageTestResponse($result);
971
972
973
974
975
976
977
978
979
980
    }

    /**
     * Initialize image processor
     *
     * @return GraphicalFunctions Initialized image processor
     */
    protected function initializeImageProcessor(): GraphicalFunctions
    {
        $imageProcessor = GeneralUtility::makeInstance(GraphicalFunctions::class);
981
        $imageProcessor->dontCheckForExistingTempFile = true;
982
        $imageProcessor->filenamePrefix = 'installTool-';
983
        $imageProcessor->dontCompress = true;
984
        $imageProcessor->alternativeOutputKey = 'typo3InstallTest';
985
        $imageProcessor->setImageFileExt(self::IMAGE_FILE_EXT);
986
987
988
        return $imageProcessor;
    }

989
990
991
992
993
994
995
996
997
    /**
     * Determine ImageMagick / GraphicsMagick version
     *
     * @return string Version
     */
    protected function determineImageMagickVersion(): string
    {
        $command = CommandUtility::imageMagickCommand('identify', '-version');
        CommandUtility::exec($command, $result);
998
        $string = $result[0] ?? '';
999
1000
        $version = '';
        if (!empty($string)) {