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