1<?php 2 3declare(strict_types=1); 4 5/* 6 * This file is part of the TYPO3 CMS project. 7 * 8 * It is free software; you can redistribute it and/or modify it under 9 * the terms of the GNU General Public License, either version 2 10 * of the License, or any later version. 11 * 12 * For the full copyright and license information, please read the 13 * LICENSE.txt file that was distributed with this source code. 14 * 15 * The TYPO3 project - inspiring people to share! 16 */ 17 18namespace TYPO3\CMS\Install\Controller; 19 20use Psr\Http\Message\ResponseInterface; 21use Psr\Http\Message\ServerRequestInterface; 22use Symfony\Component\Mime\Address; 23use Symfony\Component\Mime\Exception\RfcComplianceException; 24use TYPO3\CMS\Backend\Toolbar\Enumeration\InformationStatus; 25use TYPO3\CMS\Core\Core\Environment; 26use TYPO3\CMS\Core\Database\ConnectionPool; 27use TYPO3\CMS\Core\FormProtection\FormProtectionFactory; 28use TYPO3\CMS\Core\FormProtection\InstallToolFormProtection; 29use TYPO3\CMS\Core\Http\JsonResponse; 30use TYPO3\CMS\Core\Imaging\GraphicalFunctions; 31use TYPO3\CMS\Core\Mail\FluidEmail; 32use TYPO3\CMS\Core\Mail\Mailer; 33use TYPO3\CMS\Core\Messaging\FlashMessage; 34use TYPO3\CMS\Core\Messaging\FlashMessageQueue; 35use TYPO3\CMS\Core\Utility\CommandUtility; 36use TYPO3\CMS\Core\Utility\ExtensionManagementUtility; 37use TYPO3\CMS\Core\Utility\GeneralUtility; 38use TYPO3\CMS\Core\Utility\MathUtility; 39use TYPO3\CMS\Core\Utility\StringUtility; 40use TYPO3\CMS\Install\FolderStructure\DefaultFactory; 41use TYPO3\CMS\Install\FolderStructure\DefaultPermissionsCheck; 42use TYPO3\CMS\Install\Service\LateBootService; 43use TYPO3\CMS\Install\SystemEnvironment\Check; 44use TYPO3\CMS\Install\SystemEnvironment\DatabaseCheck; 45use TYPO3\CMS\Install\SystemEnvironment\ServerResponse\ServerResponseCheck; 46use 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 */ 52class 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 * @var LateBootService 59 */ 60 private $lateBootService; 61 62 public function __construct( 63 LateBootService $lateBootService 64 ) { 65 $this->lateBootService = $lateBootService; 66 } 67 68 /** 69 * Main "show the cards" view 70 * 71 * @param ServerRequestInterface $request 72 * @return ResponseInterface 73 */ 74 public function cardsAction(ServerRequestInterface $request): ResponseInterface 75 { 76 $view = $this->initializeStandaloneView($request, 'Environment/Cards.html'); 77 return new JsonResponse([ 78 'success' => true, 79 'html' => $view->render(), 80 ]); 81 } 82 83 /** 84 * System Information Get Data action 85 * 86 * @param ServerRequestInterface $request 87 * @return ResponseInterface 88 */ 89 public function systemInformationGetDataAction(ServerRequestInterface $request): ResponseInterface 90 { 91 $view = $this->initializeStandaloneView($request, 'Environment/SystemInformation.html'); 92 $view->assignMultiple([ 93 'systemInformationCgiDetected' => Environment::isRunningOnCgiServer(), 94 'systemInformationDatabaseConnections' => $this->getDatabaseConnectionInformation(), 95 'systemInformationOperatingSystem' => Environment::isWindows() ? 'Windows' : 'Unix', 96 'systemInformationApplicationContext' => $this->getApplicationContextInformation(), 97 'phpVersion' => PHP_VERSION, 98 ]); 99 return new JsonResponse([ 100 'success' => true, 101 'html' => $view->render(), 102 ]); 103 } 104 105 /** 106 * System Information Get Data action 107 * 108 * @param ServerRequestInterface $request 109 * @return ResponseInterface 110 */ 111 public function phpInfoGetDataAction(ServerRequestInterface $request): ResponseInterface 112 { 113 $view = $this->initializeStandaloneView($request, 'Environment/PhpInfo.html'); 114 return new JsonResponse([ 115 'success' => true, 116 'html' => $view->render(), 117 ]); 118 } 119 120 /** 121 * Get environment status 122 * 123 * @param ServerRequestInterface $request 124 * @return ResponseInterface 125 */ 126 public function environmentCheckGetStatusAction(ServerRequestInterface $request): ResponseInterface 127 { 128 $view = $this->initializeStandaloneView($request, 'Environment/EnvironmentCheck.html'); 129 $messageQueue = new FlashMessageQueue('install'); 130 $checkMessages = (new Check())->getStatus(); 131 foreach ($checkMessages as $message) { 132 $messageQueue->enqueue($message); 133 } 134 $setupMessages = (new SetupCheck())->getStatus(); 135 foreach ($setupMessages as $message) { 136 $messageQueue->enqueue($message); 137 } 138 $databaseMessages = (new DatabaseCheck())->getStatus(); 139 foreach ($databaseMessages as $message) { 140 $messageQueue->enqueue($message); 141 } 142 $serverResponseMessages = (new ServerResponseCheck(false))->getStatus(); 143 foreach ($serverResponseMessages as $message) { 144 $messageQueue->enqueue($message); 145 } 146 return new JsonResponse([ 147 'success' => true, 148 'status' => [ 149 'error' => $messageQueue->getAllMessages(FlashMessage::ERROR), 150 'warning' => $messageQueue->getAllMessages(FlashMessage::WARNING), 151 'ok' => $messageQueue->getAllMessages(FlashMessage::OK), 152 'information' => $messageQueue->getAllMessages(FlashMessage::INFO), 153 'notice' => $messageQueue->getAllMessages(FlashMessage::NOTICE), 154 ], 155 'html' => $view->render(), 156 'buttons' => [ 157 [ 158 'btnClass' => 'btn-default t3js-environmentCheck-execute', 159 'text' => 'Run tests again', 160 ], 161 ], 162 ]); 163 } 164 165 /** 166 * Get folder structure status 167 * 168 * @param ServerRequestInterface $request 169 * @return ResponseInterface 170 */ 171 public function folderStructureGetStatusAction(ServerRequestInterface $request): ResponseInterface 172 { 173 $view = $this->initializeStandaloneView($request, 'Environment/FolderStructure.html'); 174 $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class); 175 $structureFacade = $folderStructureFactory->getStructure(); 176 177 $structureMessages = $structureFacade->getStatus(); 178 $errorQueue = new FlashMessageQueue('install'); 179 $okQueue = new FlashMessageQueue('install'); 180 foreach ($structureMessages as $message) { 181 if ($message->getSeverity() === FlashMessage::ERROR 182 || $message->getSeverity() === FlashMessage::WARNING 183 ) { 184 $errorQueue->enqueue($message); 185 } else { 186 $okQueue->enqueue($message); 187 } 188 } 189 190 $permissionCheck = GeneralUtility::makeInstance(DefaultPermissionsCheck::class); 191 192 $view->assign('publicPath', Environment::getPublicPath()); 193 194 $buttons = []; 195 if ($errorQueue->count() > 0) { 196 $buttons[] = [ 197 'btnClass' => 'btn-default t3js-folderStructure-errors-fix', 198 'text' => 'Try to fix file and folder permissions', 199 ]; 200 } 201 202 return new JsonResponse([ 203 'success' => true, 204 'errorStatus' => $errorQueue, 205 'okStatus' => $okQueue, 206 'folderStructureFilePermissionStatus' => $permissionCheck->getMaskStatus('fileCreateMask'), 207 'folderStructureDirectoryPermissionStatus' => $permissionCheck->getMaskStatus('folderCreateMask'), 208 'html' => $view->render(), 209 'buttons' => $buttons, 210 ]); 211 } 212 213 /** 214 * Try to fix folder structure errors 215 * 216 * @return ResponseInterface 217 */ 218 public function folderStructureFixAction(): ResponseInterface 219 { 220 $folderStructureFactory = GeneralUtility::makeInstance(DefaultFactory::class); 221 $structureFacade = $folderStructureFactory->getStructure(); 222 $fixedStatusObjects = $structureFacade->fix(); 223 return new JsonResponse([ 224 'success' => true, 225 'fixedStatus' => $fixedStatusObjects, 226 ]); 227 } 228 229 /** 230 * System Information Get Data action 231 * 232 * @param ServerRequestInterface $request 233 * @return ResponseInterface 234 */ 235 public function mailTestGetDataAction(ServerRequestInterface $request): ResponseInterface 236 { 237 $view = $this->initializeStandaloneView($request, 'Environment/MailTest.html'); 238 $formProtection = FormProtectionFactory::get(InstallToolFormProtection::class); 239 $view->assignMultiple([ 240 'mailTestToken' => $formProtection->generateToken('installTool', 'mailTest'), 241 'mailTestSenderAddress' => $this->getSenderEmailAddress(), 242 ]); 243 return new JsonResponse([ 244 'success' => true, 245 'html' => $view->render(), 246 'buttons' => [ 247 [ 248 'btnClass' => 'btn-default t3js-mailTest-execute', 249 'text' => 'Send test mail', 250 ], 251 ], 252 ]); 253 } 254 255 /** 256 * Send a test mail 257 * 258 * @param ServerRequestInterface $request 259 * @return ResponseInterface 260 */ 261 public function mailTestAction(ServerRequestInterface $request): ResponseInterface 262 { 263 $container = $this->lateBootService->getContainer(); 264 $backup = $this->lateBootService->makeCurrent($container); 265 $messages = new FlashMessageQueue('install'); 266 $recipient = $request->getParsedBody()['install']['email']; 267 if (empty($recipient) || !GeneralUtility::validEmail($recipient)) { 268 $messages->enqueue(new FlashMessage( 269 'Given address is not a valid email address.', 270 'Mail not sent', 271 FlashMessage::ERROR 272 )); 273 } else { 274 try { 275 $variables = [ 276 'headline' => 'TYPO3 Test Mail', 277 'introduction' => 'Hey TYPO3 Administrator', 278 'content' => 'Seems like your favorite TYPO3 installation can send out emails!', 279 ]; 280 $mailMessage = GeneralUtility::makeInstance(FluidEmail::class); 281 $mailMessage 282 ->to($recipient) 283 ->from(new Address($this->getSenderEmailAddress(), $this->getSenderEmailName())) 284 ->subject($this->getEmailSubject()) 285 ->setRequest($request) 286 ->assignMultiple($variables); 287 288 GeneralUtility::makeInstance(Mailer::class)->send($mailMessage); 289 $messages->enqueue(new FlashMessage( 290 'Recipient: ' . $recipient, 291 'Test mail sent' 292 )); 293 } catch (RfcComplianceException $exception) { 294 $messages->enqueue(new FlashMessage( 295 'Please verify $GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][\'defaultMailFromAddress\'] is a valid mail address.' 296 . ' Error message: ' . $exception->getMessage(), 297 'RFC compliance problem', 298 FlashMessage::ERROR 299 )); 300 } catch (\Throwable $throwable) { 301 $messages->enqueue(new FlashMessage( 302 'Please verify $GLOBALS[\'TYPO3_CONF_VARS\'][\'MAIL\'][*] settings are valid.' 303 . ' Error message: ' . $throwable->getMessage(), 304 'Could not deliver mail', 305 FlashMessage::ERROR 306 )); 307 } 308 } 309 $this->lateBootService->makeCurrent(null, $backup); 310 return new JsonResponse([ 311 'success' => true, 312 'status' => $messages, 313 ]); 314 } 315 316 /** 317 * System Information Get Data action 318 * 319 * @param ServerRequestInterface $request 320 * @return ResponseInterface 321 */ 322 public function imageProcessingGetDataAction(ServerRequestInterface $request): ResponseInterface 323 { 324 $view = $this->initializeStandaloneView($request, 'Environment/ImageProcessing.html'); 325 $view->assignMultiple([ 326 'imageProcessingProcessor' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor'] === 'GraphicsMagick' ? 'GraphicsMagick' : 'ImageMagick', 327 'imageProcessingEnabled' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled'], 328 'imageProcessingPath' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path'], 329 'imageProcessingVersion' => $this->determineImageMagickVersion(), 330 'imageProcessingEffects' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_effects'], 331 'imageProcessingGdlibEnabled' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib'], 332 'imageProcessingGdlibPng' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['gdlib_png'], 333 'imageProcessingFileFormats' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], 334 ]); 335 return new JsonResponse([ 336 'success' => true, 337 'html' => $view->render(), 338 'buttons' => [ 339 [ 340 'btnClass' => 'btn-default disabled t3js-imageProcessing-execute', 341 'text' => 'Run image tests again', 342 ], 343 ], 344 ]); 345 } 346 347 /** 348 * Create true type font test image 349 * 350 * @return ResponseInterface 351 */ 352 public function imageProcessingTrueTypeAction(): ResponseInterface 353 { 354 $image = @imagecreate(200, 50); 355 imagecolorallocate($image, 255, 255, 55); 356 $textColor = imagecolorallocate($image, 233, 14, 91); 357 @imagettftext( 358 $image, 359 20 / 96.0 * 72, // As in compensateFontSizeBasedOnFreetypeDpi 360 0, 361 10, 362 20, 363 $textColor, 364 ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf', 365 'Testing true type' 366 ); 367 $outputFile = Environment::getPublicPath() . '/typo3temp/assets/images/installTool-' . StringUtility::getUniqueId('createTrueTypeFontTestImage') . '.gif'; 368 @imagegif($image, $outputFile); 369 $fileExists = file_exists($outputFile); 370 if ($fileExists) { 371 GeneralUtility::fixPermissions($outputFile); 372 } 373 $result = [ 374 'fileExists' => $fileExists, 375 'referenceFile' => self::TEST_REFERENCE_PATH . '/Font.gif', 376 ]; 377 if ($fileExists) { 378 $result['outputFile'] = $outputFile; 379 } 380 return $this->getImageTestResponse($result); 381 } 382 383 /** 384 * Convert to jpg from jpg 385 * 386 * @return ResponseInterface 387 */ 388 public function imageProcessingReadJpgAction(): ResponseInterface 389 { 390 return $this->convertImageFormatsToJpg('jpg'); 391 } 392 393 /** 394 * Convert to jpg from gif 395 * 396 * @return ResponseInterface 397 */ 398 public function imageProcessingReadGifAction(): ResponseInterface 399 { 400 return $this->convertImageFormatsToJpg('gif'); 401 } 402 403 /** 404 * Convert to jpg from png 405 * 406 * @return ResponseInterface 407 */ 408 public function imageProcessingReadPngAction(): ResponseInterface 409 { 410 return $this->convertImageFormatsToJpg('png'); 411 } 412 413 /** 414 * Convert to jpg from tif 415 * 416 * @return ResponseInterface 417 */ 418 public function imageProcessingReadTifAction(): ResponseInterface 419 { 420 return $this->convertImageFormatsToJpg('tif'); 421 } 422 423 /** 424 * Convert to jpg from pdf 425 * 426 * @return ResponseInterface 427 */ 428 public function imageProcessingReadPdfAction(): ResponseInterface 429 { 430 return $this->convertImageFormatsToJpg('pdf'); 431 } 432 433 /** 434 * Convert to jpg from ai 435 * 436 * @return ResponseInterface 437 */ 438 public function imageProcessingReadAiAction(): ResponseInterface 439 { 440 return $this->convertImageFormatsToJpg('ai'); 441 } 442 443 /** 444 * Writing gif test 445 * 446 * @return ResponseInterface 447 */ 448 public function imageProcessingWriteGifAction(): ResponseInterface 449 { 450 if (!$this->isImageMagickEnabledAndConfigured()) { 451 return new JsonResponse([ 452 'status' => [$this->imageMagickDisabledMessage()], 453 ]); 454 } 455 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 456 $inputFile = $imageBasePath . 'TestInput/Test.gif'; 457 $imageProcessor = $this->initializeImageProcessor(); 458 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-gif'); 459 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'gif', '300', '', '', '', [], true); 460 $messages = new FlashMessageQueue('install'); 461 if ($imResult !== null && is_file($imResult[3])) { 462 if ($GLOBALS['TYPO3_CONF_VARS']['GFX']['gif_compress']) { 463 clearstatcache(); 464 $previousSize = GeneralUtility::formatSize((int)filesize($imResult[3])); 465 $methodUsed = GraphicalFunctions::gifCompress($imResult[3], ''); 466 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 'Compressed gif', 472 FlashMessage::INFO 473 )); 474 } else { 475 $messages->enqueue(new FlashMessage( 476 '', 477 'Gif compression not enabled by [GFX][gif_compress]', 478 FlashMessage::INFO 479 )); 480 } 481 $result = [ 482 'status' => $messages, 483 'fileExists' => true, 484 'outputFile' => $imResult[3], 485 'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-gif.gif', 486 'command' => $imageProcessor->IM_commands, 487 ]; 488 } else { 489 $result = [ 490 'status' => [$this->imageGenerationFailedMessage()], 491 'command' => $imageProcessor->IM_commands, 492 ]; 493 } 494 return $this->getImageTestResponse($result); 495 } 496 497 /** 498 * Writing png test 499 * 500 * @return ResponseInterface 501 */ 502 public function imageProcessingWritePngAction(): ResponseInterface 503 { 504 if (!$this->isImageMagickEnabledAndConfigured()) { 505 return new JsonResponse([ 506 'status' => [$this->imageMagickDisabledMessage()], 507 ]); 508 } 509 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 510 $inputFile = $imageBasePath . 'TestInput/Test.png'; 511 $imageProcessor = $this->initializeImageProcessor(); 512 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-png'); 513 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'png', '300', '', '', '', [], true); 514 if ($imResult !== null && is_file($imResult[3])) { 515 $result = [ 516 'fileExists' => true, 517 'outputFile' => $imResult[3], 518 'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-png.png', 519 'command' => $imageProcessor->IM_commands, 520 ]; 521 } else { 522 $result = [ 523 'status' => [$this->imageGenerationFailedMessage()], 524 'command' => $imageProcessor->IM_commands, 525 ]; 526 } 527 return $this->getImageTestResponse($result); 528 } 529 /** 530 * Writing webp test 531 * 532 * @return ResponseInterface 533 */ 534 public function imageProcessingWriteWebpAction(): ResponseInterface 535 { 536 if (!$this->isImageMagickEnabledAndConfigured()) { 537 return new JsonResponse([ 538 'status' => [$this->imageMagickDisabledMessage()], 539 ]); 540 } 541 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 542 $inputFile = $imageBasePath . 'TestInput/Test.webp'; 543 $imageProcessor = $this->initializeImageProcessor(); 544 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('write-webp'); 545 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'webp', '300', '', '', '', [], true); 546 if ($imResult !== null && is_file($imResult[3])) { 547 $result = [ 548 'fileExists' => true, 549 'outputFile' => $imResult[3], 550 'referenceFile' => self::TEST_REFERENCE_PATH . '/Write-webp.webp', 551 'command' => $imageProcessor->IM_commands, 552 ]; 553 } else { 554 $result = [ 555 'status' => [$this->imageGenerationFailedMessage()], 556 'command' => $imageProcessor->IM_commands, 557 ]; 558 } 559 return $this->getImageTestResponse($result); 560 } 561 562 /** 563 * Scaling transparent files - gif to gif 564 * 565 * @return ResponseInterface 566 */ 567 public function imageProcessingGifToGifAction(): ResponseInterface 568 { 569 if (!$this->isImageMagickEnabledAndConfigured()) { 570 return new JsonResponse([ 571 'status' => [$this->imageMagickDisabledMessage()], 572 ]); 573 } 574 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 575 $imageProcessor = $this->initializeImageProcessor(); 576 $inputFile = $imageBasePath . 'TestInput/Transparent.gif'; 577 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-gif'); 578 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'gif', '300', '', '', '', [], true); 579 if ($imResult !== null && file_exists($imResult[3])) { 580 $result = [ 581 'fileExists' => true, 582 'outputFile' => $imResult[3], 583 'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-gif.gif', 584 'command' => $imageProcessor->IM_commands, 585 ]; 586 } else { 587 $result = [ 588 'status' => [$this->imageGenerationFailedMessage()], 589 'command' => $imageProcessor->IM_commands, 590 ]; 591 } 592 return $this->getImageTestResponse($result); 593 } 594 595 /** 596 * Scaling transparent files - png to png 597 * 598 * @return ResponseInterface 599 */ 600 public function imageProcessingPngToPngAction(): ResponseInterface 601 { 602 if (!$this->isImageMagickEnabledAndConfigured()) { 603 return new JsonResponse([ 604 'status' => [$this->imageMagickDisabledMessage()], 605 ]); 606 } 607 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 608 $imageProcessor = $this->initializeImageProcessor(); 609 $inputFile = $imageBasePath . 'TestInput/Transparent.png'; 610 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-png'); 611 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'png', '300', '', '', '', [], true); 612 if ($imResult !== null && file_exists($imResult[3])) { 613 $result = [ 614 'fileExists' => true, 615 'outputFile' => $imResult[3], 616 'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-png.png', 617 'command' => $imageProcessor->IM_commands, 618 ]; 619 } else { 620 $result = [ 621 'status' => [$this->imageGenerationFailedMessage()], 622 'command' => $imageProcessor->IM_commands, 623 ]; 624 } 625 return $this->getImageTestResponse($result); 626 } 627 628 /** 629 * Scaling transparent files - gif to jpg 630 * 631 * @return ResponseInterface 632 */ 633 public function imageProcessingGifToJpgAction(): ResponseInterface 634 { 635 if (!$this->isImageMagickEnabledAndConfigured()) { 636 return new JsonResponse([ 637 'status' => [$this->imageMagickDisabledMessage()], 638 ]); 639 } 640 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 641 $imageProcessor = $this->initializeImageProcessor(); 642 $inputFile = $imageBasePath . 'TestInput/Transparent.gif'; 643 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('scale-jpg'); 644 $jpegQuality = MathUtility::forceIntegerInRange($GLOBALS['TYPO3_CONF_VARS']['GFX']['jpg_quality'], 10, 100, 85); 645 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'jpg', '300', '', '-quality ' . $jpegQuality . ' -opaque white -background white -flatten', '', [], true); 646 if ($imResult !== null && file_exists($imResult[3])) { 647 $result = [ 648 'fileExists' => true, 649 'outputFile' => $imResult[3], 650 'referenceFile' => self::TEST_REFERENCE_PATH . '/Scale-jpg.jpg', 651 'command' => $imageProcessor->IM_commands, 652 ]; 653 } else { 654 $result = [ 655 'status' => [$this->imageGenerationFailedMessage()], 656 'command' => $imageProcessor->IM_commands, 657 ]; 658 } 659 return $this->getImageTestResponse($result); 660 } 661 662 /** 663 * Converting jpg to webp 664 * 665 * @return ResponseInterface 666 */ 667 public function imageProcessingJpgToWebpAction(): ResponseInterface 668 { 669 if (!$this->isImageMagickEnabledAndConfigured()) { 670 return new JsonResponse([ 671 'status' => [$this->imageMagickDisabledMessage()], 672 ]); 673 } 674 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 675 $imageProcessor = $this->initializeImageProcessor(); 676 $inputFile = $imageBasePath . 'TestInput/Test.jpg'; 677 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('read-webp'); 678 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'webp', '300', '', '', '', [], true); 679 if ($imResult !== null) { 680 $result = [ 681 'fileExists' => file_exists($imResult[3]), 682 'outputFile' => $imResult[3], 683 'referenceFile' => self::TEST_REFERENCE_PATH . '/Convert-webp.webp', 684 'command' => $imageProcessor->IM_commands, 685 ]; 686 } else { 687 $result = [ 688 'status' => [$this->imageGenerationFailedMessage()], 689 'command' => $imageProcessor->IM_commands, 690 ]; 691 } 692 return $this->getImageTestResponse($result); 693 } 694 695 /** 696 * Combine images with gif mask 697 * 698 * @return ResponseInterface 699 */ 700 public function imageProcessingCombineGifMaskAction(): ResponseInterface 701 { 702 if (!$this->isImageMagickEnabledAndConfigured()) { 703 return new JsonResponse([ 704 'status' => [$this->imageMagickDisabledMessage()], 705 ]); 706 } 707 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 708 $imageProcessor = $this->initializeImageProcessor(); 709 $inputFile = $imageBasePath . 'TestInput/BackgroundOrange.gif'; 710 $overlayFile = $imageBasePath . 'TestInput/Test.jpg'; 711 $maskFile = $imageBasePath . 'TestInput/MaskBlackWhite.gif'; 712 $resultFile = $this->getImagesPath() . $imageProcessor->filenamePrefix 713 . StringUtility::getUniqueId($imageProcessor->alternativeOutputKey . 'combine1') . '.jpg'; 714 $imageProcessor->combineExec($inputFile, $overlayFile, $maskFile, $resultFile); 715 $imResult = $imageProcessor->getImageDimensions($resultFile); 716 if ($imResult) { 717 $result = [ 718 'fileExists' => true, 719 'outputFile' => $imResult[3], 720 'referenceFile' => self::TEST_REFERENCE_PATH . '/Combine-1.jpg', 721 'command' => $imageProcessor->IM_commands, 722 ]; 723 } else { 724 $result = [ 725 'status' => [$this->imageGenerationFailedMessage()], 726 'command' => $imageProcessor->IM_commands, 727 ]; 728 } 729 return $this->getImageTestResponse($result); 730 } 731 732 /** 733 * Combine images with jpg mask 734 * 735 * @return ResponseInterface 736 */ 737 public function imageProcessingCombineJpgMaskAction(): ResponseInterface 738 { 739 if (!$this->isImageMagickEnabledAndConfigured()) { 740 return new JsonResponse([ 741 'status' => [$this->imageMagickDisabledMessage()], 742 ]); 743 } 744 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 745 $imageProcessor = $this->initializeImageProcessor(); 746 $inputFile = $imageBasePath . 'TestInput/BackgroundCombine.jpg'; 747 $overlayFile = $imageBasePath . 'TestInput/Test.jpg'; 748 $maskFile = $imageBasePath . 'TestInput/MaskCombine.jpg'; 749 $resultFile = $this->getImagesPath() . $imageProcessor->filenamePrefix 750 . StringUtility::getUniqueId($imageProcessor->alternativeOutputKey . 'combine2') . '.jpg'; 751 $imageProcessor->combineExec($inputFile, $overlayFile, $maskFile, $resultFile); 752 $imResult = $imageProcessor->getImageDimensions($resultFile); 753 if ($imResult) { 754 $result = [ 755 'fileExists' => true, 756 'outputFile' => $imResult[3], 757 'referenceFile' => self::TEST_REFERENCE_PATH . '/Combine-2.jpg', 758 'command' => $imageProcessor->IM_commands, 759 ]; 760 } else { 761 $result = [ 762 'status' => [$this->imageGenerationFailedMessage()], 763 'command' => $imageProcessor->IM_commands, 764 ]; 765 } 766 return $this->getImageTestResponse($result); 767 } 768 769 /** 770 * GD with simple box 771 * 772 * @return ResponseInterface 773 */ 774 public function imageProcessingGdlibSimpleAction(): ResponseInterface 775 { 776 $imageProcessor = $this->initializeImageProcessor(); 777 $gifOrPng = $imageProcessor->gifExtension; 778 $image = imagecreatetruecolor(300, 225); 779 $backgroundColor = imagecolorallocate($image, 0, 0, 0); 780 imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor); 781 $workArea = [0, 0, 300, 225]; 782 $conf = [ 783 'dimensions' => '10,50,280,50', 784 'color' => 'olive', 785 ]; 786 $imageProcessor->makeBox($image, $conf, $workArea); 787 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdSimple') . '.' . $gifOrPng; 788 $imageProcessor->ImageWrite($image, $outputFile); 789 $imResult = $imageProcessor->getImageDimensions($outputFile); 790 $result = [ 791 'fileExists' => true, 792 'outputFile' => $imResult[3], 793 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-simple.' . $gifOrPng, 794 'command' => $imageProcessor->IM_commands, 795 ]; 796 return $this->getImageTestResponse($result); 797 } 798 799 /** 800 * GD from image with box 801 * 802 * @return ResponseInterface 803 */ 804 public function imageProcessingGdlibFromFileAction(): ResponseInterface 805 { 806 $imageProcessor = $this->initializeImageProcessor(); 807 $gifOrPng = $imageProcessor->gifExtension; 808 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 809 $inputFile = $imageBasePath . 'TestInput/Test.' . $gifOrPng; 810 $image = $imageProcessor->imageCreateFromFile($inputFile); 811 $workArea = [0, 0, 400, 300]; 812 $conf = [ 813 'dimensions' => '10,50,380,50', 814 'color' => 'olive', 815 ]; 816 $imageProcessor->makeBox($image, $conf, $workArea); 817 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdBox') . '.' . $gifOrPng; 818 $imageProcessor->ImageWrite($image, $outputFile); 819 $imResult = $imageProcessor->getImageDimensions($outputFile); 820 $result = [ 821 'fileExists' => true, 822 'outputFile' => $imResult[3], 823 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-box.' . $gifOrPng, 824 'command' => $imageProcessor->IM_commands, 825 ]; 826 return $this->getImageTestResponse($result); 827 } 828 829 /** 830 * GD with text 831 * 832 * @return ResponseInterface 833 */ 834 public function imageProcessingGdlibRenderTextAction(): ResponseInterface 835 { 836 $imageProcessor = $this->initializeImageProcessor(); 837 $gifOrPng = $imageProcessor->gifExtension; 838 $image = imagecreatetruecolor(300, 225); 839 $backgroundColor = imagecolorallocate($image, 128, 128, 150); 840 imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor); 841 $workArea = [0, 0, 300, 225]; 842 $conf = [ 843 'iterations' => 1, 844 'angle' => 0, 845 'antiAlias' => 1, 846 'text' => 'HELLO WORLD', 847 'fontColor' => '#003366', 848 'fontSize' => 30, 849 'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf', 850 'offset' => '30,80', 851 ]; 852 $conf['BBOX'] = $imageProcessor->calcBBox($conf); 853 $imageProcessor->makeText($image, $conf, $workArea); 854 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng; 855 $imageProcessor->ImageWrite($image, $outputFile); 856 $imResult = $imageProcessor->getImageDimensions($outputFile); 857 $result = [ 858 'fileExists' => true, 859 'outputFile' => $imResult[3], 860 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-text.' . $gifOrPng, 861 'command' => $imageProcessor->IM_commands, 862 ]; 863 return $this->getImageTestResponse($result); 864 } 865 866 /** 867 * GD with text, niceText 868 * 869 * @return ResponseInterface 870 */ 871 public function imageProcessingGdlibNiceTextAction(): ResponseInterface 872 { 873 if (!$this->isImageMagickEnabledAndConfigured()) { 874 return new JsonResponse([ 875 'status' => [$this->imageMagickDisabledMessage()], 876 ]); 877 } 878 $imageProcessor = $this->initializeImageProcessor(); 879 $gifOrPng = $imageProcessor->gifExtension; 880 $image = imagecreatetruecolor(300, 225); 881 $backgroundColor = imagecolorallocate($image, 128, 128, 150); 882 imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor); 883 $workArea = [0, 0, 300, 225]; 884 $conf = [ 885 'iterations' => 1, 886 'angle' => 0, 887 'antiAlias' => 1, 888 'text' => 'HELLO WORLD', 889 'fontColor' => '#003366', 890 'fontSize' => 30, 891 'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf', 892 'offset' => '30,80', 893 ]; 894 $conf['BBOX'] = $imageProcessor->calcBBox($conf); 895 $imageProcessor->makeText($image, $conf, $workArea); 896 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng; 897 $imageProcessor->ImageWrite($image, $outputFile); 898 $conf['offset'] = '30,120'; 899 $conf['niceText'] = 1; 900 $imageProcessor->makeText($image, $conf, $workArea); 901 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.' . $gifOrPng; 902 $imageProcessor->ImageWrite($image, $outputFile); 903 $imResult = $imageProcessor->getImageDimensions($outputFile); 904 $result = [ 905 'fileExists' => true, 906 'outputFile' => $imResult[3], 907 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-niceText.' . $gifOrPng, 908 'command' => $imageProcessor->IM_commands, 909 ]; 910 return $this->getImageTestResponse($result); 911 } 912 913 /** 914 * GD with text, niceText, shadow 915 * 916 * @return ResponseInterface 917 */ 918 public function imageProcessingGdlibNiceTextShadowAction(): ResponseInterface 919 { 920 if (!$this->isImageMagickEnabledAndConfigured()) { 921 return new JsonResponse([ 922 'status' => [$this->imageMagickDisabledMessage()], 923 ]); 924 } 925 $imageProcessor = $this->initializeImageProcessor(); 926 $gifOrPng = $imageProcessor->gifExtension; 927 $image = imagecreatetruecolor(300, 225); 928 $backgroundColor = imagecolorallocate($image, 128, 128, 150); 929 imagefilledrectangle($image, 0, 0, 300, 225, $backgroundColor); 930 $workArea = [0, 0, 300, 225]; 931 $conf = [ 932 'iterations' => 1, 933 'angle' => 0, 934 'antiAlias' => 1, 935 'text' => 'HELLO WORLD', 936 'fontColor' => '#003366', 937 'fontSize' => 30, 938 'fontFile' => ExtensionManagementUtility::extPath('install') . 'Resources/Private/Font/vera.ttf', 939 'offset' => '30,80', 940 ]; 941 $conf['BBOX'] = $imageProcessor->calcBBox($conf); 942 $imageProcessor->makeText($image, $conf, $workArea); 943 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdText') . '.' . $gifOrPng; 944 $imageProcessor->ImageWrite($image, $outputFile); 945 $conf['offset'] = '30,120'; 946 $conf['niceText'] = 1; 947 $imageProcessor->makeText($image, $conf, $workArea); 948 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('gdNiceText') . '.' . $gifOrPng; 949 $imageProcessor->ImageWrite($image, $outputFile); 950 $conf['offset'] = '30,160'; 951 $conf['niceText'] = 1; 952 $conf['shadow.'] = [ 953 'offset' => '2,2', 954 'blur' => '20', 955 'opacity' => '50', 956 'color' => 'black', 957 ]; 958 // Warning: Re-uses $image from above! 959 $imageProcessor->makeShadow($image, $conf['shadow.'], $workArea, $conf); 960 $imageProcessor->makeText($image, $conf, $workArea); 961 $outputFile = $this->getImagesPath() . $imageProcessor->filenamePrefix . StringUtility::getUniqueId('GDwithText-niceText-shadow') . '.' . $gifOrPng; 962 $imageProcessor->ImageWrite($image, $outputFile); 963 $imResult = $imageProcessor->getImageDimensions($outputFile); 964 $result = [ 965 'fileExists' => true, 966 'outputFile' => $imResult[3], 967 'referenceFile' => self::TEST_REFERENCE_PATH . '/Gdlib-shadow.' . $gifOrPng, 968 'command' => $imageProcessor->IM_commands, 969 ]; 970 return $this->getImageTestResponse($result); 971 } 972 973 /** 974 * Initialize image processor 975 * 976 * @return GraphicalFunctions Initialized image processor 977 */ 978 protected function initializeImageProcessor(): GraphicalFunctions 979 { 980 $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 return $imageProcessor; 987 } 988 989 /** 990 * Determine ImageMagick / GraphicsMagick version 991 * 992 * @return string Version 993 */ 994 protected function determineImageMagickVersion(): string 995 { 996 $command = CommandUtility::imageMagickCommand('identify', '-version'); 997 CommandUtility::exec($command, $result); 998 $string = $result[0] ?? ''; 999 $version = ''; 1000 if (!empty($string)) { 1001 [, $version] = explode('Magick', $string); 1002 [$version] = explode(' ', trim($version)); 1003 $version = trim($version); 1004 } 1005 return $version; 1006 } 1007 1008 /** 1009 * Convert to jpg from given input format 1010 * 1011 * @param string $inputFormat 1012 * @return ResponseInterface 1013 */ 1014 protected function convertImageFormatsToJpg(string $inputFormat): ResponseInterface 1015 { 1016 if (!$this->isImageMagickEnabledAndConfigured()) { 1017 return new JsonResponse([ 1018 'status' => [$this->imageMagickDisabledMessage()], 1019 ]); 1020 } 1021 if (!GeneralUtility::inList($GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'], $inputFormat)) { 1022 return new JsonResponse([ 1023 'status' => [ 1024 new FlashMessage( 1025 'Handling format ' . $inputFormat . ' must be enabled in TYPO3_CONF_VARS[\'GFX\'][\'imagefile_ext\']', 1026 'Skipped test', 1027 FlashMessage::WARNING 1028 ), 1029 ], 1030 ]); 1031 } 1032 $imageBasePath = ExtensionManagementUtility::extPath('install') . 'Resources/Public/Images/'; 1033 $imageProcessor = $this->initializeImageProcessor(); 1034 $inputFile = $imageBasePath . 'TestInput/Test.' . $inputFormat; 1035 $imageProcessor->imageMagickConvert_forceFileNameBody = StringUtility::getUniqueId('read') . '-' . $inputFormat; 1036 $imResult = $imageProcessor->imageMagickConvert($inputFile, 'jpg', '300', '', '', '', [], true); 1037 if ($imResult !== null) { 1038 $result = [ 1039 'fileExists' => file_exists($imResult[3]), 1040 'outputFile' => $imResult[3], 1041 'referenceFile' => self::TEST_REFERENCE_PATH . '/Read-' . $inputFormat . '.jpg', 1042 'command' => $imageProcessor->IM_commands, 1043 ]; 1044 } else { 1045 $result = [ 1046 'status' => [$this->imageGenerationFailedMessage()], 1047 'command' => $imageProcessor->IM_commands, 1048 ]; 1049 } 1050 return $this->getImageTestResponse($result); 1051 } 1052 1053 /** 1054 * Get details about all configured database connections 1055 * 1056 * @return array 1057 */ 1058 protected function getDatabaseConnectionInformation(): array 1059 { 1060 $connectionInfos = []; 1061 $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class); 1062 foreach ($connectionPool->getConnectionNames() as $connectionName) { 1063 $connection = $connectionPool->getConnectionByName($connectionName); 1064 $connectionParameters = $connection->getParams(); 1065 $connectionInfo = [ 1066 'connectionName' => $connectionName, 1067 'version' => $connection->getServerVersion(), 1068 'databaseName' => $connection->getDatabase(), 1069 'username' => $connectionParameters['user'] ?? '', 1070 'host' => $connectionParameters['host'] ?? '', 1071 'port' => $connectionParameters['port'] ?? '', 1072 'socket' => $connectionParameters['unix_socket'] ?? '', 1073 'numberOfTables' => count($connection->createSchemaManager()->listTableNames()), 1074 'numberOfMappedTables' => 0, 1075 ]; 1076 if (isset($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) 1077 && is_array($GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping']) 1078 ) { 1079 // Count number of array keys having $connectionName as value 1080 $connectionInfo['numberOfMappedTables'] = count(array_intersect( 1081 $GLOBALS['TYPO3_CONF_VARS']['DB']['TableMapping'], 1082 [$connectionName] 1083 )); 1084 } 1085 $connectionInfos[] = $connectionInfo; 1086 } 1087 return $connectionInfos; 1088 } 1089 1090 /** 1091 * Get details about the application context 1092 * 1093 * @return array 1094 */ 1095 protected function getApplicationContextInformation(): array 1096 { 1097 $applicationContext = Environment::getContext(); 1098 $status = $applicationContext->isProduction() ? InformationStatus::STATUS_OK : InformationStatus::STATUS_WARNING; 1099 1100 return [ 1101 'context' => (string)$applicationContext, 1102 'status' => $status, 1103 ]; 1104 } 1105 1106 /** 1107 * Get sender address from configuration 1108 * ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] 1109 * If this setting is empty fall back to 'no-reply@example.com' 1110 * 1111 * @return string Returns an email address 1112 */ 1113 protected function getSenderEmailAddress(): string 1114 { 1115 return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress']) 1116 ? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromAddress'] 1117 : 'no-reply@example.com'; 1118 } 1119 1120 /** 1121 * Gets sender name from configuration 1122 * ['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'] 1123 * If this setting is empty, it falls back to a default string. 1124 * 1125 * @return string 1126 */ 1127 protected function getSenderEmailName(): string 1128 { 1129 return !empty($GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName']) 1130 ? $GLOBALS['TYPO3_CONF_VARS']['MAIL']['defaultMailFromName'] 1131 : 'TYPO3 CMS install tool'; 1132 } 1133 1134 /** 1135 * Gets email subject from configuration 1136 * ['TYPO3_CONF_VARS']['SYS']['sitename'] 1137 * If this setting is empty, it falls back to a default string. 1138 * 1139 * @return string 1140 */ 1141 protected function getEmailSubject(): string 1142 { 1143 $name = !empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) 1144 ? ' from site "' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . '"' 1145 : ''; 1146 return 'Test TYPO3 CMS mail delivery' . $name; 1147 } 1148 1149 /** 1150 * Create a JsonResponse from single image tests 1151 * 1152 * @param array $testResult 1153 * @return ResponseInterface 1154 */ 1155 protected function getImageTestResponse(array $testResult): ResponseInterface 1156 { 1157 $responseData = [ 1158 'success' => true, 1159 ]; 1160 foreach ($testResult as $resultKey => $value) { 1161 if ($resultKey === 'referenceFile' && !empty($testResult['referenceFile'])) { 1162 $referenceFileArray = explode('.', $testResult['referenceFile']); 1163 $fileExt = end($referenceFileArray); 1164 $responseData['referenceFile'] = 'data:image/' . $fileExt . ';base64,' . base64_encode((string)file_get_contents($testResult['referenceFile'])); 1165 } elseif ($resultKey === 'outputFile' && !empty($testResult['outputFile'])) { 1166 $outputFileArray = explode('.', $testResult['outputFile']); 1167 $fileExt = end($outputFileArray); 1168 $responseData['outputFile'] = 'data:image/' . $fileExt . ';base64,' . base64_encode((string)file_get_contents($testResult['outputFile'])); 1169 } else { 1170 $responseData[$resultKey] = $value; 1171 } 1172 } 1173 return new JsonResponse($responseData); 1174 } 1175 1176 /** 1177 * Create a 'image generation failed' message 1178 * 1179 * @return FlashMessage 1180 */ 1181 protected function imageGenerationFailedMessage(): FlashMessage 1182 { 1183 return new FlashMessage( 1184 'ImageMagick / GraphicsMagick handling is enabled, but the execute' 1185 . ' command returned an error. Please check your settings, especially' 1186 . ' [\'GFX\'][\'processor_path\'] and [\'GFX\'][\'processor_path_lzw\'] and ensure Ghostscript is installed on your server.', 1187 'Image generation failed', 1188 FlashMessage::ERROR 1189 ); 1190 } 1191 1192 /** 1193 * Find out if ImageMagick or GraphicsMagick is enabled and set up 1194 * 1195 * @return bool TRUE if enabled and path is set 1196 */ 1197 protected function isImageMagickEnabledAndConfigured(): bool 1198 { 1199 $enabled = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_enabled']; 1200 $path = $GLOBALS['TYPO3_CONF_VARS']['GFX']['processor_path']; 1201 return $enabled && $path; 1202 } 1203 1204 /** 1205 * Create a 'imageMagick disabled' message 1206 * 1207 * @return FlashMessage 1208 */ 1209 protected function imageMagickDisabledMessage(): FlashMessage 1210 { 1211 return new FlashMessage( 1212 'ImageMagick / GraphicsMagick handling is disabled or not configured correctly.', 1213 'Tests not executed', 1214 FlashMessage::ERROR 1215 ); 1216 } 1217 1218 /** 1219 * Return the temp image dir. 1220 * If not exist it will be created 1221 * 1222 * @return string 1223 */ 1224 protected function getImagesPath(): string 1225 { 1226 $imagePath = Environment::getPublicPath() . '/typo3temp/assets/images/'; 1227 if (!is_dir($imagePath)) { 1228 GeneralUtility::mkdir_deep($imagePath); 1229 } 1230 return $imagePath; 1231 } 1232} 1233