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