[TASK] Throw PSR-7 Responses up to Application 66/57866/5
authorOliver Hader <oliver@typo3.org>
Fri, 10 Aug 2018 20:21:18 +0000 (22:21 +0200)
committerBenni Mack <benni@typo3.org>
Fri, 10 Aug 2018 21:38:04 +0000 (23:38 +0200)
In order to properly handle PSR-7 response objects explicit die()
or exit() calls should be avoided since those states cannot be
handle nor analyzed further. As a current work-around a new
ImmediateResponseException is introduced that throws the exception
up to application object that transforms the emitted message into
a proper response object. This API is internal and considered as
intermediate - the real solution would be to completely refactor
TypoScriptFrontendController processing and only use
request/response consequently.

Resolves: #85812
Releases: master
Change-Id: I047ad82fd5734c38160b8552aa754b1e7c356417
Reviewed-on: https://review.typo3.org/57866
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Benni Mack <benni@typo3.org>
Tested-by: Benni Mack <benni@typo3.org>
composer.json
composer.lock
typo3/sysext/core/Classes/Http/AbstractApplication.php
typo3/sysext/core/Classes/Http/ImmediateResponseException.php [new file with mode: 0644]
typo3/sysext/core/composer.json
typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedContentTest.php
typo3/sysext/fluid/Tests/Functional/View/TemplatesPathsTest.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Tests/Functional/Rendering/LocalizedContentRenderingTest.php
typo3/sysext/frontend/Tests/Functional/Rendering/UriPrefixRenderingTest.php

index 3208618..9a7bcdb 100644 (file)
@@ -67,7 +67,7 @@
                "fiunchinho/phpunit-randomizer": "^4.0",
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "typo3/cms-styleguide": "~9.2.0",
-               "typo3/testing-framework": "~4.2.0"
+               "typo3/testing-framework": "~4.3.0"
        },
        "suggest": {
                "ext-gd": "GDlib/Freetype is required for building images with text (GIFBUILDER) and can also be used to scale images",
index d06857a..a513d5b 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "47c7e434ca902bf4fd72f17f63d389a6",
+    "content-hash": "955beaf3f2c2d0c508368071e29bb275",
     "packages": [
         {
             "name": "cogpowered/finediff",
         },
         {
             "name": "typo3/testing-framework",
-            "version": "4.2.0",
+            "version": "4.3.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/TYPO3/testing-framework.git",
-                "reference": "5aaee9dd602961087af339fb4fd2597e162cb821"
+                "reference": "0a432e72e647bb54ace9718d58285e6daf48620f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/5aaee9dd602961087af339fb4fd2597e162cb821",
-                "reference": "5aaee9dd602961087af339fb4fd2597e162cb821",
+                "url": "https://api.github.com/repos/TYPO3/testing-framework/zipball/0a432e72e647bb54ace9718d58285e6daf48620f",
+                "reference": "0a432e72e647bb54ace9718d58285e6daf48620f",
                 "shasum": ""
             },
             "require": {
                 "tests",
                 "typo3"
             ],
-            "time": "2018-08-01T18:28:16+00:00"
+            "time": "2018-08-10T20:50:40+00:00"
         },
         {
             "name": "webmozart/assert",
index e2b917c..181cff6 100644 (file)
@@ -63,6 +63,7 @@ abstract class AbstractApplication implements ApplicationInterface
             return;
         }
 
+        // @todo This requires some merge strategy or header callback handling
         if (!headers_sent()) {
             // If the response code was not changed by legacy code (still is 200)
             // then allow the PSR-7 response object to explicitly set it.
@@ -96,12 +97,17 @@ abstract class AbstractApplication implements ApplicationInterface
      *
      * @param callable $execute
      */
-    public function run(callable $execute = null)
+    final public function run(callable $execute = null)
     {
-        $response = $this->handle(\TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals());
-
-        if ($execute !== null) {
-            call_user_func($execute);
+        try {
+            $response = $this->handle(
+                \TYPO3\CMS\Core\Http\ServerRequestFactory::fromGlobals()
+            );
+            if ($execute !== null) {
+                call_user_func($execute);
+            }
+        } catch (ImmediateResponseException $exception) {
+            $response = $exception->getResponse();
         }
 
         $this->sendResponse($response);
diff --git a/typo3/sysext/core/Classes/Http/ImmediateResponseException.php b/typo3/sysext/core/Classes/Http/ImmediateResponseException.php
new file mode 100644 (file)
index 0000000..e9c4711
--- /dev/null
@@ -0,0 +1,52 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Http;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Exception that has to be handled immediately in order to have
+ * stop current execution and provide the current response. This
+ * exception is used as alternative to previous die() or exit().
+ *
+ * @internal
+ */
+class ImmediateResponseException extends \Exception
+{
+    /**
+     * @var ResponseInterface
+     */
+    private $response;
+
+    /**
+     * @param ResponseInterface $response
+     * @param int $code
+     */
+    public function __construct(ResponseInterface $response, int $code = 0)
+    {
+        $this->response = $response;
+        $this->code = $code;
+    }
+
+    /**
+     * @return Response
+     */
+    public function getResponse(): ResponseInterface
+    {
+        return $this->response;
+    }
+}
index 482710a..b502085 100644 (file)
@@ -48,7 +48,7 @@
                "fiunchinho/phpunit-randomizer": "^4.0",
                "friendsofphp/php-cs-fixer": "^2.12.2",
                "typo3/cms-styleguide": "~9.2.0",
-               "typo3/testing-framework": "~4.2.0"
+               "typo3/testing-framework": "~4.3.0"
        },
        "suggest": {
                "ext-fileinfo": "Used for proper file type detection in the file abstraction layer",
index 6f8339c..7e3f49b 100644 (file)
@@ -18,7 +18,8 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
 
 use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Response;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
 
 /**
  * This test is an Extbase version of the \TYPO3\CMS\Frontend\Tests\Functional\Rendering\LocalizedContentRenderingTest
@@ -93,6 +94,13 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
         ]);
     }
 
+    protected function tearDown()
+    {
+        unset($this->objectManager);
+        unset($this->contentRepository);
+        parent::tearDown();
+    }
+
     public function defaultLanguageConfigurationDataProvider(): array
     {
         return [
@@ -758,7 +766,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 0
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [],
-                'status' => 404,
+                'statusCode' => 404,
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 0
@@ -851,7 +859,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = 1
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [],
-                'status' => 404
+                'statusCode' => 404
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 1
@@ -941,7 +949,7 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
                                 config.sys_language_mode = strict',
                 'visibleRecords' => [],
-                'status' => 404,
+                'statusCode' => 404,
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
@@ -976,18 +984,22 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
      *
      * @param string $typoScript
      * @param array $visibleRecords
-     * @param string $status 'success' or 404
+     * @param int $statusCode '200' or '404'
      */
-    public function contentOnNonTranslatedPageGerman(string $typoScript, array $visibleRecords, string $status='success')
+    public function contentOnNonTranslatedPageGerman(string $typoScript, array $visibleRecords, int $statusCode = 200)
     {
         $this->addTypoScriptToTemplateRecord(1, $typoScript);
-        $visibleHeaders = array_map(function ($element) {
-            return $element['header'];
-        }, $visibleRecords);
+        $visibleHeaders = array_column($visibleRecords, 'header');
+
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())
+                ->withPageId(self::VALUE_PageId)
+                ->withLanguageId(2)
+        );
 
-        $frontendResponse = $this->getFrontendResponse(self::VALUE_PageId, 2);
-        if ($status === Response::STATUS_Success) {
-            $responseSections = $frontendResponse->getResponseSections('Extbase:list()');
+        if ($statusCode === 200) {
+            $responseSections = ResponseContent::fromString((string)$response->getBody())
+                ->getSections('Extbase:list()');
             $this->assertThat(
                 $responseSections,
                 $this->getRequestSectionHasRecordConstraint()
@@ -1015,8 +1027,8 @@ class TranslatedContentTest extends AbstractDataHandlerActionTestCase
                     ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
             }
         }
-        //some configuration combinations results in 404, in that case status will be set to 404
-        $this->assertEquals($status, $frontendResponse->getStatus());
+
+        $this->assertEquals($statusCode, $response->getStatusCode());
     }
 
     public function contentOnPartiallyTranslatedPageDataProvider(): array
index a828bf3..d6b6dab 100644 (file)
@@ -14,9 +14,7 @@ namespace TYPO3\CMS\Fluid\Tests\Functional\View;
  * The TYPO3 project - inspiring people to share!
  */
 
-use PHPUnit\Util\PHP\AbstractPhpProcess;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Response;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
 class TemplatesPathsTest extends FunctionalTestCase
@@ -144,7 +142,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'mode' => 'fluidTemplate',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains($expectedTemplate, $content);
         $this->assertContains($expectedPartial, $content);
@@ -167,7 +165,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'mode' => 'controller',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains($expectedTemplate, $content);
         $this->assertContains($expectedPartial, $content);
@@ -191,7 +189,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'pluginConfig' => 'extensionKey',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains($expectedTemplate, $content);
         $this->assertContains($expectedPartial, $content);
@@ -215,7 +213,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'pluginConfig' => 'pluginName',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains($expectedTemplate, $content);
         $this->assertContains($expectedPartial, $content);
@@ -234,7 +232,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'widgetConfig' => 'new',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains('PAGINATE WIDGET', $content);
     }
@@ -251,7 +249,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'widgetConfig' => 'old',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains('PAGINATE WIDGET', $content);
     }
@@ -268,7 +266,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'pluginConfig' => 'incomplete',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains('Base Template', $content);
         $this->assertContains('Default Layout', $content);
@@ -285,7 +283,7 @@ class TemplatesPathsTest extends FunctionalTestCase
             'mode' => '2plugins',
         ];
 
-        $content = $this->fetchFrontendResponse($requestArguments)->getContent();
+        $content = $this->fetchFrontendResponseBody($requestArguments);
 
         $this->assertContains('Base Template', $content);
         $this->assertContains('Override Template', $content);
@@ -293,39 +291,17 @@ class TemplatesPathsTest extends FunctionalTestCase
 
     /**
      * @param array $requestArguments
-     * @param bool $failOnFailure
-     * @return Response
+     * @return string
      */
-    protected function fetchFrontendResponse(array $requestArguments, $failOnFailure = true)
+    protected function fetchFrontendResponseBody(array $requestArguments): string
     {
-        $arguments = [
-            'documentRoot' => $this->instancePath,
-            'requestUrl' => 'http://localhost' . '/?' . GeneralUtility::implodeArrayForUrl('', $requestArguments),
-        ];
+        (new InternalRequest())
+            ->withQueryParameters($requestArguments);
 
-        $template = new \Text_Template(TYPO3_PATH_PACKAGES . 'typo3/testing-framework/Resources/Core/Functional/Fixtures/Frontend/request.tpl');
-        $template->setVar(
-            [
-                'arguments' => var_export($arguments, true),
-                'originalRoot' => ORIGINAL_ROOT,
-                'vendorPath' => TYPO3_PATH_PACKAGES,
-            ]
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withQueryParameters($requestArguments)
         );
 
-        $php = AbstractPhpProcess::factory();
-        $response = $php->runJob($template->render());
-        $result = json_decode($response['stdout'], true);
-
-        if ($result === null) {
-            $this->fail('Frontend Response is empty');
-        }
-
-        if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
-            $this->fail('Frontend Response has failure:' . LF . $result['error']);
-        }
-
-        $response = new Response($result['status'], $result['content'], $result['error']);
-
-        return $response;
+        return (string)$response->getBody();
     }
 }
index 8c860a9..893b79a 100644 (file)
@@ -42,6 +42,7 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
 use TYPO3\CMS\Core\Error\Http\ShortcutTargetPageNotFoundException;
 use TYPO3\CMS\Core\Exception\Page\RootLineException;
+use TYPO3\CMS\Core\Http\ImmediateResponseException;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Locking\Exception\LockAcquireWouldBlockException;
 use TYPO3\CMS\Core\Locking\LockFactory;
@@ -894,7 +895,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $message,
                     ['code' => PageAccessFailureReasons::DATABASE_CONNECTION_FAILED]
                 );
-                $this->sendResponseAndExit($response);
+                throw new ImmediateResponseException($response, 1533931298);
             } catch (ServiceUnavailableException $e) {
                 throw new ServiceUnavailableException($message, 1301648782);
             }
@@ -1350,7 +1351,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                             $message,
                             ['code' => PageAccessFailureReasons::NO_PAGES_FOUND]
                         );
-                        $this->sendResponseAndExit($response);
+                        throw new ImmediateResponseException($response, 1533931299);
                     } catch (ServiceUnavailableException $e) {
                         throw new ServiceUnavailableException($message, 1301648975);
                     }
@@ -1404,7 +1405,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         $this->getPageAccessFailureReasons()
                     );
             }
-            $this->sendResponseAndExit($response);
+            throw new ImmediateResponseException($response, 1533931329);
         }
         // Init SYS_LASTCHANGED
         $this->register['SYS_LASTCHANGED'] = (int)$this->page['tstamp'];
@@ -1501,7 +1502,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         $message,
                         $this->getPageAccessFailureReasons(PageAccessFailureReasons::PAGE_NOT_FOUND)
                     );
-                    $this->sendResponseAndExit($response);
+                    throw new ImmediateResponseException($response, 1533931330);
                 } catch (PageNotFoundException $e) {
                     throw new PageNotFoundException($message, 1301648780);
                 }
@@ -1517,7 +1518,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $message,
                     $this->getPageAccessFailureReasons(PageAccessFailureReasons::ACCESS_DENIED_INVALID_PAGETYPE)
                 );
-                $this->sendResponseAndExit($response);
+                throw new ImmediateResponseException($response, 1533931343);
             } catch (PageNotFoundException $e) {
                 throw new PageNotFoundException($message, 1301648781);
             }
@@ -1564,7 +1565,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     $message,
                     $this->getPageAccessFailureReasons(PageAccessFailureReasons::ROOTLINE_BROKEN)
                 );
-                $this->sendResponseAndExit($response);
+                throw new ImmediateResponseException($response, 1533931350);
             } catch (ServiceUnavailableException $e) {
                 throw new ServiceUnavailableException($message, 1301648167);
             }
@@ -1579,7 +1580,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         $message,
                         $this->getPageAccessFailureReasons(PageAccessFailureReasons::ACCESS_DENIED_GENERAL)
                     );
-                    $this->sendResponseAndExit($response);
+                    throw new ImmediateResponseException($response, 1533931351);
                 } catch (ServiceUnavailableException $e) {
                     $this->logger->warning($message);
                     throw new ServiceUnavailableException($message, 1301648234);
@@ -2189,11 +2190,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         'Request parameters could not be validated (&cHash comparison failed)',
                         ['code' => PageAccessFailureReasons::CACHEHASH_COMPARISON_FAILED]
                     );
-                    $this->sendResponseAndExit($response);
-                } else {
-                    $this->disableCache();
-                    $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->cHash_array)) . '"', 2);
+                    throw new ImmediateResponseException($response, 1533931352);
                 }
+                $this->disableCache();
+                $this->getTimeTracker()->setTSlogMessage('The incoming cHash "' . $this->cHash . '" and calculated cHash "' . $cHash_calc . '" did not match, so caching was disabled. The fieldlist used was "' . implode(',', array_keys($this->cHash_array)) . '"', 2);
             }
         } elseif (is_array($GET)) {
             // No cHash is set, check if that is correct
@@ -2221,11 +2221,10 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                     'Request parameters could not be validated (&cHash empty)',
                     ['code' => PageAccessFailureReasons::CACHEHASH_EMPTY]
                 );
-                $this->sendResponseAndExit($response);
-            } else {
-                $this->disableCache();
-                $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
+                throw new ImmediateResponseException($response, 1533931354);
             }
+            $this->disableCache();
+            $this->getTimeTracker()->setTSlogMessage('TSFE->reqCHash(): No &cHash parameter was sent for GET vars though required so caching is disabled', 2);
         }
     }
 
@@ -2504,7 +2503,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                             $message,
                             ['code' => PageAccessFailureReasons::RENDERING_INSTRUCTIONS_NOT_CONFIGURED]
                         );
-                        $this->sendResponseAndExit($response);
+                        throw new ImmediateResponseException($response, 1533931374);
                     } catch (ServiceUnavailableException $e) {
                         $explanation = 'This means that there is no TypoScript object of type PAGE with typeNum=' . $this->type . ' configured.';
                         throw new ServiceUnavailableException($message . ' ' . $explanation, 1294587217);
@@ -2560,7 +2559,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         $message,
                         ['code' => PageAccessFailureReasons::RENDERING_INSTRUCTIONS_NOT_FOUND]
                     );
-                    $this->sendResponseAndExit($response);
+                    throw new ImmediateResponseException($response, 1533931380);
                 } catch (ServiceUnavailableException $e) {
                     throw new ServiceUnavailableException($message, 1294587218);
                 }
@@ -2654,50 +2653,49 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                         'Page is not available in the requested language.',
                         ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE]
                     );
-                    $this->sendResponseAndExit($response);
-                } else {
-                    switch ((string)$languageAspect->getLegacyLanguageMode()) {
-                        case 'strict':
-                            $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                                $GLOBALS['TYPO3_REQUEST'],
-                                'Page is not available in the requested language (strict).',
-                                ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE_STRICT_MODE]
-                            );
-                            $this->sendResponseAndExit($response);
-                            break;
-                        case 'fallback':
-                        case 'content_fallback':
-                            // Setting content uid (but leaving the sys_language_uid) when a content_fallback
-                            // value was found.
-                            foreach ($languageAspect->getFallbackChain() ?? [] as $orderValue) {
-                                if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
-                                    $languageContentId = 0;
-                                    break;
-                                }
-                                if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
-                                    $languageContentId = (int)$orderValue;
-                                    break;
-                                }
-                                if ($orderValue === 'pageNotFound') {
-                                    // The existing fallbacks have not been found, but instead of continuing
-                                    // page rendering with default language, a "page not found" message should be shown
-                                    // instead.
-                                    $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
-                                        $GLOBALS['TYPO3_REQUEST'],
-                                        'Page is not available in the requested language (fallbacks did not apply).',
-                                        ['code' => PageAccessFailureReasons::LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE]
-                                    );
-                                    $this->sendResponseAndExit($response);
-                                }
+                    throw new ImmediateResponseException($response, 1533931388);
+                }
+                switch ((string)$languageAspect->getLegacyLanguageMode()) {
+                    case 'strict':
+                        $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                            $GLOBALS['TYPO3_REQUEST'],
+                            'Page is not available in the requested language (strict).',
+                            ['code' => PageAccessFailureReasons::LANGUAGE_NOT_AVAILABLE_STRICT_MODE]
+                        );
+                        throw new ImmediateResponseException($response, 1533931395);
+                        break;
+                    case 'fallback':
+                    case 'content_fallback':
+                        // Setting content uid (but leaving the sys_language_uid) when a content_fallback
+                        // value was found.
+                        foreach ($languageAspect->getFallbackChain() ?? [] as $orderValue) {
+                            if ($orderValue === '0' || $orderValue === 0 || $orderValue === '') {
+                                $languageContentId = 0;
+                                break;
                             }
-                            break;
-                        case 'ignore':
-                            $languageContentId = $languageAspect->getId();
-                            break;
-                        default:
-                            // Default is that everything defaults to the default language...
-                            $languageId = ($languageContentId = 0);
-                    }
+                            if (MathUtility::canBeInterpretedAsInteger($orderValue) && !empty($this->sys_page->getPageOverlay($this->id, (int)$orderValue))) {
+                                $languageContentId = (int)$orderValue;
+                                break;
+                            }
+                            if ($orderValue === 'pageNotFound') {
+                                // The existing fallbacks have not been found, but instead of continuing
+                                // page rendering with default language, a "page not found" message should be shown
+                                // instead.
+                                $response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
+                                    $GLOBALS['TYPO3_REQUEST'],
+                                    'Page is not available in the requested language (fallbacks did not apply).',
+                                    ['code' => PageAccessFailureReasons::LANGUAGE_AND_FALLBACKS_NOT_AVAILABLE]
+                                );
+                                throw new ImmediateResponseException($response, 1533931402);
+                            }
+                        }
+                        break;
+                    case 'ignore':
+                        $languageContentId = $languageAspect->getId();
+                        break;
+                    default:
+                        // Default is that everything defaults to the default language...
+                        $languageId = ($languageContentId = 0);
                 }
             } else {
                 // Setting sys_language if an overlay record was found (which it is only if a language is used)
@@ -2728,7 +2726,7 @@ class TypoScriptFrontendController implements LoggerAwareInterface
                 $message,
                 ['code' => PageAccessFailureReasons::LANGUAGE_DEFAULT_NOT_AVAILABLE]
             );
-            $this->sendResponseAndExit($response);
+            throw new ImmediateResponseException($response, 1533931423);
         }
 
         if ($languageAspect->getId() > 0) {
@@ -4898,30 +4896,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
     }
 
     /**
-     * Helper method to kill the request. Exits.
-     * Should not be used from the outside, rather return the response object
-     * Ideally, this method will be dropped by TYPO3 v9 LTS.
-     *
-     * @param ResponseInterface $response
-     */
-    protected function sendResponseAndExit(ResponseInterface $response)
-    {
-        // If the response code was not changed by legacy code (still is 200)
-        // then allow the PSR-7 response object to explicitly set it.
-        // Otherwise let legacy code take precedence.
-        // This code path can be deprecated once we expose the response object to third party code
-        if (http_response_code() === 200) {
-            header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase());
-        }
-
-        foreach ($response->getHeaders() as $name => $values) {
-            header($name . ': ' . implode(', ', $values));
-        }
-        echo $response->getBody()->__toString();
-        die;
-    }
-
-    /**
      * Returns the current BE user.
      *
      * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
index a8e35c4..507ccba 100644 (file)
@@ -16,7 +16,8 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Rendering;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Response;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
 
 /**
  * Test case checking if localized tt_content is rendered correctly with different language settings
@@ -736,7 +737,7 @@ class LocalizedContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\Dat
                 'sys_language_content' => 2,
                 'sys_language_mode' => 'strict',
                 'sys_language_contentOL' => 0,
-                'status' => 404,
+                'statusCode' => 404,
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 0
@@ -838,7 +839,7 @@ class LocalizedContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\Dat
                 'sys_language_content' => 2,
                 'sys_language_mode' => 'strict',
                 'sys_language_contentOL' => 1,
-                'status' => 404
+                'statusCode' => 404
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = 1
@@ -937,7 +938,7 @@ class LocalizedContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\Dat
                 'sys_language_content' => 2,
                 'sys_language_mode' => 'strict',
                 'sys_language_contentOL' => 'hideNonTranslated',
-                'status' => 404,
+                'statusCode' => 404,
             ],
             [
                 'typoScript' => 'config.sys_language_overlay = hideNonTranslated
@@ -970,18 +971,23 @@ class LocalizedContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\Dat
      * @param int $sysLanguageContent
      * @param string $sysLanguageMode
      * @param string $sysLanguageContentOL
-     * @param string $status 'success' or 404
+     * @param string $statusCode 200 or 404
      */
-    public function contentOnNonTranslatedPageGerman(string $typoScript, array $visibleRecords, string $pageTitle, int $sysLanguageUid, int $sysLanguageContent, string $sysLanguageMode, string $sysLanguageContentOL, string $status='success')
+    public function contentOnNonTranslatedPageGerman(string $typoScript, array $visibleRecords, string $pageTitle, int $sysLanguageUid, int $sysLanguageContent, string $sysLanguageMode, string $sysLanguageContentOL, int $statusCode = 200)
     {
         $this->addTypoScriptToTemplateRecord(1, $typoScript);
-        $visibleHeaders = array_map(function ($element) {
-            return $element['header'];
-        }, $visibleRecords);
+        $visibleHeaders = array_column($visibleRecords, 'header');
+
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())
+                ->withPageId(self::VALUE_PageId)
+                ->withLanguageId(2)
+        );
+
+        if ($statusCode === 200) {
+            $responseStructure = ResponseContent::fromString((string)$response->getBody());
+            $responseSections = $responseStructure->getSections();
 
-        $frontendResponse = $this->getFrontendResponse(self::VALUE_PageId, 2);
-        if ($status === Response::STATUS_Success) {
-            $responseSections = $frontendResponse->getResponseSections();
             $this->assertThat(
                 $responseSections,
                 $this->getRequestSectionHasRecordConstraint()
@@ -1009,15 +1015,14 @@ class LocalizedContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional\Dat
                     ->setTable('sys_file_reference')->setField('title')->setValues(...$this->getNonVisibleFileTitles($visibleFileTitles)));
             }
 
-            $content = json_decode($frontendResponse->getContent());
-            $this->assertEquals($pageTitle, $content->Scope->page->title);
-            $this->assertEquals($sysLanguageUid, $content->Scope->tsfe->sys_language_uid, 'sys_language_uid doesn\'t match');
-            $this->assertEquals($sysLanguageContent, $content->Scope->tsfe->sys_language_content, 'sys_language_content doesn\'t match');
-            $this->assertEquals($sysLanguageMode, $content->Scope->tsfe->sys_language_mode, 'sys_language_mode doesn\t match');
-            $this->assertEquals($sysLanguageContentOL, $content->Scope->tsfe->sys_language_contentOL, 'sys_language_contentOL doesn\t match');
+            $this->assertEquals($pageTitle, $responseStructure->getScopePath('page/title'));
+            $this->assertEquals($sysLanguageUid, $responseStructure->getScopePath('tsfe/sys_language_uid'), 'sys_language_uid doesn\'t match');
+            $this->assertEquals($sysLanguageContent, $responseStructure->getScopePath('tsfe/sys_language_content'), 'sys_language_content doesn\'t match');
+            $this->assertEquals($sysLanguageMode, $responseStructure->getScopePath('tsfe/sys_language_mode'), 'sys_language_mode doesn\t match');
+            $this->assertEquals($sysLanguageContentOL, $responseStructure->getScopePath('tsfe/sys_language_contentOL'), 'sys_language_contentOL doesn\t match');
         }
-        //some configuration combinations results in 404, in that case status will be set to 404
-        $this->assertEquals($status, $frontendResponse->getStatus());
+
+        $this->assertEquals($statusCode, $response->getStatusCode());
     }
 
     public function contentOnPartiallyTranslatedPageDataProvider(): array
index 7b8ffaa..7c2e1c5 100644 (file)
@@ -3,10 +3,9 @@ declare(strict_types = 1);
 
 namespace TYPO3\CMS\Frontend\Tests\Functional\Rendering;
 
-use PHPUnit\Util\PHP\AbstractPhpProcess;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\Response;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
 
 class UriPrefixRenderingTest extends FunctionalTestCase
@@ -284,13 +283,14 @@ class UriPrefixRenderingTest extends FunctionalTestCase
      */
     public function urisAreRenderedUsingAbsRefPrefix(string $absRefPrefixAspect, string $compressorAspect, array $expectations)
     {
-        $content = $this->executeFrontendRequest(
-            1,
-            [
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withQueryParameters([
+                'id' => 1,
                 'testAbsRefPrefix' => $absRefPrefixAspect,
-                'testCompressor' => $compressorAspect
-            ]
+                'testCompressor' => $compressorAspect,
+            ])
         );
+        $content = (string)$response->getBody();
 
         foreach ($expectations as $type => $expectation) {
             $shallExist = true;
@@ -341,48 +341,6 @@ class UriPrefixRenderingTest extends FunctionalTestCase
     }
 
     /**
-     * Executes frontend request by invoking PHP sub-process.
-     *
-     * @param int $pageId
-     * @param array $queryArguments
-     * @return string
-     */
-    protected function executeFrontendRequest(int $pageId, array $queryArguments = []): string
-    {
-        $query = array_merge(
-            $queryArguments,
-            ['id' => (int)$pageId]
-        );
-        $arguments = [
-            'documentRoot' => $this->instancePath,
-            'requestUrl' => 'http://localhost/?' . http_build_query($query),
-        ];
-
-        $template = new \Text_Template(TYPO3_PATH_PACKAGES . 'typo3/testing-framework/Resources/Core/Functional/Fixtures/Frontend/request.tpl');
-        $template->setVar(
-            [
-                'arguments' => var_export($arguments, true),
-                'originalRoot' => ORIGINAL_ROOT,
-                'vendorPath' => TYPO3_PATH_PACKAGES
-            ]
-        );
-
-        $php = AbstractPhpProcess::factory();
-        $response = $php->runJob($template->render());
-        $result = json_decode($response['stdout'], true);
-
-        if ($result === null) {
-            $this->fail('Frontend Response is empty');
-        }
-
-        if ($result['status'] === Response::STATUS_Failure) {
-            $this->fail('Frontend Response has failure:' . LF . $result['error']);
-        }
-
-        return $result['content'];
-    }
-
-    /**
      * Adds TypoScript constants snippet to the existing template record
      *
      * @param int $pageId