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