[!!!][TASK] Remove legacy "pageNotFound_handling" options 53/60453/15
authorBenni Mack <benni@typo3.org>
Fri, 12 Apr 2019 00:39:46 +0000 (02:39 +0200)
committerBenni Mack <benni@typo3.org>
Tue, 21 May 2019 10:45:16 +0000 (12:45 +0200)
With the introduction of site handling, the old "pageNotFound_handling"
mechanisms can be removed. If a site configuration hasn't configured
a error handler, a 404/403/500 response with the default error
renderer is now returned. Making PageNotFoundExceptions rendering
the frontend very seldom.

The following options are removed:
- $TYPO3_CONF_VARS[FE][pageNotFound_handling]
- $TYPO3_CONF_VARS[FE][pageNotFound_handling_statheader]
- $TYPO3_CONF_VARS[FE][pageNotFound_handling_accessdeniedheader]
- $TYPO3_CONF_VARS[FE][pageUnavailable_handling]
- $TYPO3_CONF_VARS[FE][pageUnavailable_handling_statheader]

Tests have been adopted to check against status code / message
rather than on the exceptions (not) thrown (anymore).

In addition, a JsonResponse is now returned if JSON
was requested, instead of the regular HTML content.

Resolves: #88376
Releases: master
Change-Id: I6be57fadaa0ddf83039fbdcc9186ef93d5a51158
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60453
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Benni Mack <benni@typo3.org>
33 files changed:
typo3/sysext/core/Configuration/DefaultConfiguration.php
typo3/sysext/core/Configuration/DefaultConfigurationDescription.yaml
typo3/sysext/core/Documentation/Changelog/master/Breaking-87193-DeprecatedFunctionalityRemoved.rst
typo3/sysext/core/Documentation/Changelog/master/Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php
typo3/sysext/core/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
typo3/sysext/core/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/TranslatedSiteContentTest.php
typo3/sysext/frontend/Classes/Controller/ErrorController.php
typo3/sysext/frontend/Classes/Controller/TypoScriptFrontendController.php
typo3/sysext/frontend/Tests/Functional/Rendering/LocalizedSiteContentRenderingTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/AbstractLocalizedPagesTestCase.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioATest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioBTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioCTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioDTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioETest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/LocalizedPageRendering/ScenarioFTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/SiteRequestTest.php
typo3/sysext/frontend/Tests/Functional/SiteHandling/SlugSiteRequestTest.php
typo3/sysext/frontend/Tests/Unit/Controller/ErrorControllerTest.php
typo3/sysext/install/Classes/Service/SilentConfigurationUpgradeService.php
typo3/sysext/install/Configuration/ExtensionScanner/Php/ArrayDimensionMatcher.php
typo3/sysext/redirects/Classes/Service/RedirectService.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/Modify/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/Publish/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/CSV/PublishAll/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/Modify/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/Publish/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/IRRE/ForeignField/PublishAll/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/Modify/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/Publish/ActionTest.php
typo3/sysext/workspaces/Tests/Functional/DataHandling/Regular/PublishAll/ActionTest.php

index 601c9b8..62829a5 100644 (file)
@@ -1258,12 +1258,7 @@ return [
         'addAllowedPaths' => '',
         'debug' => false,
         'compressionLevel' => 0,
-        'pageNotFound_handling' => '',
-        'pageNotFound_handling_statheader' => 'HTTP/1.0 404 Not Found',
-        'pageNotFound_handling_accessdeniedheader' => 'HTTP/1.0 403 Access denied',
         'pageNotFoundOnCHashError' => true,
-        'pageUnavailable_handling' => '',
-        'pageUnavailable_handling_statheader' => 'HTTP/1.0 503 Service Temporarily Unavailable',
         'pageUnavailable_force' => false,
         'addRootLineFields' => '',
         'checkFeUserPid' => true,
index 63cda56..1f46ea8 100644 (file)
@@ -377,27 +377,12 @@ FE:
         compressionLevel:
             type: int
             description: 'Determines output compression of FE output. Makes output smaller but slows down the page generation depending on the compression level. Requires zlib in your PHP installation. Range 1-9, where 1 is least compression and 9 is greatest compression. ''true'' as value will set the compression based on the PHP default settings (usually 5). Suggested and most optimal value is 5.'
-        pageNotFound_handling:
-            type: text
-            description: '<p>How TYPO3 should handle requests for non-existing/accessible pages.</p> <dl><dt>empty (default)</dt><dd>The next visible page upwards in the page tree is shown.</dd> <dt>''true'' or ''1''</dt><dd>An error message is shown.</dd><dt>String</dt><dd>Static HTML file to show (reads content and outputs with correct headers), e.g. ''notfound.html'' or ''http://www.example.org/errors/notfound.html''.</dd> <dt>Prefix "REDIRECT:"</dt><dd> If prefixed with "REDIRECT:" it will redirect to the URL/script after the prefix.</dd><dt>Prefix "READFILE:"</dt><dd>If prefixed with "READFILE" then it will expect the remaining string to be a HTML file which will be read and outputted directly after having the marker "###CURRENT_URL###" substituted with REQUEST_URI and ###REASON### with reason text, for example: "READFILE:fileadmin/notfound.html".</dd> <dt>Prefix "USER_FUNCTION:"</dt><dd> If prefixed with "USER_FUNCTION:" a user function is called, e.g. "USER_FUNCTION:fileadmin/class.user_notfound.php:user_notFound->pageNotFound" where the file must contain a class "user_notFound" with a method "pageNotFound" inside with two parameters $param and $ref.</dd></dl>'
-        pageNotFound_handling_statheader:
-            type: text
-            description: 'If ''pageNotFound_handling'' is enabled, this string will always be sent as header before the actual handling. This applies to non-access-restriction related errors.'
-        pageNotFound_handling_accessdeniedheader:
-            type: text
-            description: 'If ''pageNotFound_handling'' is enabled, this string will always be sent as header before the actual handling. This applies to access-restricted related errors.'
         pageNotFoundOnCHashError:
             type: bool
             description: 'If TRUE, a page not found call is made when cHash evaluation error occurs, otherwise caching is disabled and page output is displayed.'
-        pageUnavailable_handling:
-            type: text
-            description: '<p>How TYPO3 should handle requests when pages are unavailable due to system problems.</p><dl><dt>empty (default)</dt><dd>An error message is shown.</dd><dt>String</dt><dd>HTML file or URL to show (reads content and outputs with correct headers), e.g. ''unavailable.html'' or ''http://www.example.org/errors/unavailable.html''.</dd><dt>Prefix "REDIRECT:"</dt><dd>If prefixed "REDIRECT:" it will redirect to the URL/script after the prefix.</dd><dt>Prefix "READFILE:"</dt><dd>If prefixed with "READFILE:" then it will expect the remaining string to be a HTML file which will be read and outputted directly after having the marker "###CURRENT_URL###" substituted with REQUEST_URI and ###REASON### with reason text, for example: "READFILE:fileadmin/unavailable.html".</dd><dt>Prefix "USER_FUNCTION:"</dt><dd>If prefixed "USER_FUNCTION:" then it will call a user function, eg. "USER_FUNCTION:fileadmin/class.user_unavailable.php:user_unavailable->pageUnavailable" where the file must contain a class "user_unavailable" with a method "pageUnavailable" inside with two parameters $param and $ref. If the client matches <a href="#SYS-devIPmask">[SYS][devIPmask]</a>, this setting is ignored and the page is shown as normal.</dd></dl>'
-        pageUnavailable_handling_statheader:
-            type: text
-            description: 'If ''pageUnavailable_handling'' is enabled, this string will always be sent as header before the actual handling.'
         pageUnavailable_force:
             type: bool
-            description: 'If TRUE, pageUnavailable_handling is used for every frontend page. If the client matches <a href="#SYS-devIPmask">[SYS][devIPmask]</a>, the page is shown as normal. This is useful during temporary site maintenance.'
+            description: 'If TRUE, every frontend page is shown as "unavailable". If the client matches <a href="#SYS-devIPmask">[SYS][devIPmask]</a>, the page is shown as normal. This is useful during temporary site maintenance.'
         addRootLineFields:
             type: list
             description: 'Comma-list of fields from the ''pages''-table. These fields are added to the select query for fields in the rootline.'
index eab0b17..a6a16c7 100644 (file)
@@ -1363,6 +1363,11 @@ The following constants have been set to protected:
 The following global options are ignored:
 
 * :php:`$GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader']`
 * :php:`$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/saltedpasswords']['saltMethods']`
 * :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['enableDeprecationLog']`
 * :php:`$GLOBALS['TYPO3_CONF_VARS']['SYS']['recursiveDomainSearch']`
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst
new file mode 100644 (file)
index 0000000..ad34408
--- /dev/null
@@ -0,0 +1,49 @@
+.. include:: ../../Includes.txt
+
+====================================================================
+Breaking: #88376 - Removed obsolete "pageNotFound_handling" settings
+====================================================================
+
+See :issue:`88376`
+
+Description
+===========
+
+The following global TYPO3 settings usually set within `LocalConfiguration.php` have been removed:
+
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']`
+* :php:`$GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader']`
+
+These settings are effectively replaced by the error handling of the newly introduced Site Handling
+which is more flexible and robust, and is used instead of these options when Site Handling was
+enabled in TYPO3 v9. For TYPO3 v10 Site Handling is a requirement, making these options useless.
+
+
+Impact
+======
+
+Setting any of the options will have no effect any more. Executing the Silent Upgrade Wizard
+will remove the settings automatically.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installations having these settings overridden in `LocalConfiguration.php`
+file of an installation.
+
+
+Migration
+=========
+
+Access the install tool to automatically update the `LocalConfiguration.php` file and remove the
+settings.
+
+Ensure to set up Site Handling with proper error handlers. Avoid accessing these settings but
+rather use the available `ErrorController` PHP class, when trying to manually trigger a 404/500
+in the Frontend (e.g. custom plugin) instead.
+
+.. index:: Frontend, LocalConfiguration, PartiallyScanned, ext:frontend
index 7b14ec6..3ff62fd 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\CSV\Modify;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -244,8 +246,8 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\CSV\
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest((new InternalRequest())->withPageId(self::VALUE_PageId));
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index a57624b..1c351a4 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\ForeignField\Modify;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -311,8 +313,8 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\IRRE\Fore
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest((new InternalRequest())->withPageId(self::VALUE_PageId));
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 8a64f24..ec70897 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular\Modify;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -484,8 +486,8 @@ class ActionTest extends \TYPO3\CMS\Core\Tests\Functional\DataHandling\Regular\A
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest((new InternalRequest())->withPageId(self::VALUE_PageId));
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 2ec8909..934c1bc 100644 (file)
@@ -17,7 +17,6 @@ namespace TYPO3\CMS\Extbase\Tests\Functional\Persistence;
  */
 
 use ExtbaseTeam\BlogExample\Domain\Repository\TtContentRepository;
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase;
 use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -496,11 +495,6 @@ class TranslatedSiteContentTest extends AbstractDataHandlerActionTestCase
             ]
         );
 
-        if ($statusCode === 404) {
-            $this->expectExceptionCode(1518472189);
-            $this->expectException(PageNotFoundException::class);
-        }
-
         $response = $this->executeFrontendRequest(
             new InternalRequest('https://website.local/de/?id=' . static::VALUE_PageId)
         );
index 6160354..18a1a96 100644 (file)
@@ -23,7 +23,7 @@ use TYPO3\CMS\Core\Error\Http\ServiceUnavailableException;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerInterface;
 use TYPO3\CMS\Core\Error\PageErrorHandler\PageErrorHandlerNotConfiguredException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
-use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Core\Http\JsonResponse;
 use TYPO3\CMS\Core\Site\Entity\Site;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -52,12 +52,7 @@ class ErrorController
         if ($errorHandler instanceof PageErrorHandlerInterface) {
             return $errorHandler->handlePageError($request, $message, $reasons);
         }
-        return $this->handlePageError(
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'],
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'],
-            $message,
-            $reasons
-        );
+        return $this->handleDefaultError($request, 500, $message ?: 'Page is unavailable');
     }
 
     /**
@@ -76,15 +71,11 @@ class ErrorController
         if ($errorHandler instanceof PageErrorHandlerInterface) {
             return $errorHandler->handlePageError($request, $message, $reasons);
         }
-        if (!$GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling']) {
+        try {
+            return $this->handleDefaultError($request, 404, $message);
+        } catch (\RuntimeException $e) {
             throw new PageNotFoundException($message, 1518472189);
         }
-        return $this->handlePageError(
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'],
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'],
-            $message,
-            $reasons
-        );
     }
 
     /**
@@ -95,6 +86,7 @@ class ErrorController
      * @param string $message
      * @param array $reasons
      * @return ResponseInterface
+     * @throws PageNotFoundException
      */
     public function accessDeniedAction(ServerRequestInterface $request, string $message, array $reasons = []): ResponseInterface
     {
@@ -102,12 +94,11 @@ class ErrorController
         if ($errorHandler instanceof PageErrorHandlerInterface) {
             return $errorHandler->handlePageError($request, $message, $reasons);
         }
-        return $this->handlePageError(
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'],
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader'],
-            $message,
-            $reasons
-        );
+        try {
+            return $this->handleDefaultError($request, 403, $message);
+        } catch (\RuntimeException $e) {
+            throw new PageNotFoundException($message, 1518472195);
+        }
     }
 
     /**
@@ -118,181 +109,7 @@ class ErrorController
      */
     protected function isPageUnavailableHandlerConfigured(): bool
     {
-        return
-            $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling']
-            && !GeneralUtility::cmpIP(
-                GeneralUtility::getIndpEnv('REMOTE_ADDR'),
-                $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']
-            )
-        ;
-    }
-
-    /**
-     * Generic error page handler.
-     *
-     * @param mixed $errorHandler See docs of ['FE']['pageNotFound_handling'] and ['FE']['pageUnavailable_handling'] for all possible values
-     * @param string $header If set, this is passed directly to the PHP function, header()
-     * @param string $reason If set, error messages will also mention this as the reason for the page-not-found.
-     * @param array $pageAccessFailureReasons
-     * @return ResponseInterface
-     * @throws \RuntimeException
-     */
-    protected function handlePageError($errorHandler, string $header = '', string $reason = '', array $pageAccessFailureReasons = []): ResponseInterface
-    {
-        $response = null;
-        $content = '';
-        // Simply boolean; Just shows TYPO3 error page with reason:
-        if (is_bool($errorHandler) || strtolower($errorHandler) === 'true' || (string)$errorHandler === '1') {
-            $content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
-                'Page Not Found',
-                'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : '')
-            );
-        } elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'USER_FUNCTION:')) {
-            $funcRef = trim(substr($errorHandler, 14));
-            $params = [
-                'currentUrl' => GeneralUtility::getIndpEnv('REQUEST_URI'),
-                'reasonText' => $reason,
-                'pageAccessFailureReasons' => $pageAccessFailureReasons
-            ];
-            try {
-                $content = GeneralUtility::callUserFunction($funcRef, $params, $this);
-            } catch (\Exception $e) {
-                throw new \RuntimeException('Error: 404 page by USER_FUNCTION "' . $funcRef . '" failed.', 1518472235, $e);
-            }
-        } elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'READFILE:')) {
-            $readFile = GeneralUtility::getFileAbsFileName(trim(substr($errorHandler, 9)));
-            if (@is_file($readFile)) {
-                $content = str_replace(
-                    [
-                        '###CURRENT_URL###',
-                        '###REASON###'
-                    ],
-                    [
-                        GeneralUtility::getIndpEnv('REQUEST_URI'),
-                        htmlspecialchars($reason)
-                    ],
-                    file_get_contents($readFile)
-                );
-            } else {
-                throw new \RuntimeException('Configuration Error: 404 page "' . $readFile . '" could not be found.', 1518472245);
-            }
-        } elseif (GeneralUtility::isFirstPartOfStr($errorHandler, 'REDIRECT:')) {
-            $response = new RedirectResponse(substr($errorHandler, 9));
-        } elseif ($errorHandler !== '') {
-            // Check if URL is relative
-            $urlParts = parse_url($errorHandler);
-            // parse_url could return an array without the key "host", the empty check works better than strict check
-            if (empty($urlParts['host'])) {
-                $urlParts['host'] = GeneralUtility::getIndpEnv('HTTP_HOST');
-                if ($errorHandler[0] === '/') {
-                    $errorHandler = GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST') . $errorHandler;
-                } else {
-                    $errorHandler = GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR') . $errorHandler;
-                }
-                $checkBaseTag = false;
-            } else {
-                $checkBaseTag = true;
-            }
-            // Check recursion
-            if ($errorHandler === GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')) {
-                $reason = $reason ?: 'Page cannot be found.';
-                $reason .= LF . LF . 'Additionally, ' . $errorHandler . ' was not found while trying to retrieve the error document.';
-                throw new \RuntimeException(nl2br(htmlspecialchars($reason)), 1518472252);
-            }
-            // Prepare headers
-            $requestHeaders = [
-                'User-agent' => GeneralUtility::getIndpEnv('HTTP_USER_AGENT'),
-                'Referer' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL')
-            ];
-            $report = [];
-            $res = GeneralUtility::getUrl($errorHandler, 1, $requestHeaders, $report);
-            if ((int)$report['error'] !== 0 && (int)$report['error'] !== 200) {
-                throw new \RuntimeException('Failed to fetch error page "' . $errorHandler . '", reason: ' . $report['message'], 1518472257);
-            }
-            if ($res === false) {
-                // Last chance -- redirect
-                $response = new RedirectResponse($errorHandler);
-            } else {
-                // Header and content are separated by an empty line
-                list($returnedHeaders, $content) = explode(CRLF . CRLF, $res, 2);
-                $content .= CRLF;
-                // Forward these response headers to the client
-                $forwardHeaders = [
-                    'Content-Type:'
-                ];
-                $headerArr = preg_split('/\\r|\\n/', $returnedHeaders, -1, PREG_SPLIT_NO_EMPTY);
-                foreach ($headerArr as $headerLine) {
-                    foreach ($forwardHeaders as $h) {
-                        if (preg_match('/^' . $h . '/', $headerLine)) {
-                            $header .= CRLF . $headerLine;
-                        }
-                    }
-                }
-                // Put <base> if necessary
-                if ($checkBaseTag) {
-                    // If content already has <base> tag, we do not need to do anything
-                    if (false === stristr($content, '<base ')) {
-                        // Generate href for base tag
-                        $base = $urlParts['scheme'] . '://';
-                        if ($urlParts['user'] != '') {
-                            $base .= $urlParts['user'];
-                            if ($urlParts['pass'] != '') {
-                                $base .= ':' . $urlParts['pass'];
-                            }
-                            $base .= '@';
-                        }
-                        $base .= $urlParts['host'];
-                        // Add path portion skipping possible file name
-                        $base .= preg_replace('/(.*\\/)[^\\/]*/', '${1}', $urlParts['path']);
-                        // Put it into content (generate also <head> if necessary)
-                        $replacement = LF . '<base href="' . htmlentities($base) . '" />' . LF;
-                        if (stristr($content, '<head>')) {
-                            $content = preg_replace('/(<head>)/i', '\\1' . $replacement, $content);
-                        } else {
-                            $content = preg_replace('/(<html[^>]*>)/i', '\\1<head>' . $replacement . '</head>', $content);
-                        }
-                    }
-                }
-            }
-        } else {
-            $content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
-                'Page Not Found',
-                $reason ? 'Reason: ' . $reason : 'Page cannot be found.'
-            );
-        }
-
-        if (!$response) {
-            $response = new HtmlResponse($content);
-        }
-        return $this->applySanitizedHeadersToResponse($response, $header);
-    }
-
-    /**
-     * Headers which have been requested, will be added to the response object.
-     * If a header is part of the HTTP Response code, the response object will be annotated as well.
-     *
-     * @param ResponseInterface $response
-     * @param string $headers
-     * @return ResponseInterface
-     */
-    protected function applySanitizedHeadersToResponse(ResponseInterface $response, string $headers): ResponseInterface
-    {
-        if (!empty($headers)) {
-            $headerArr = preg_split('/\\r|\\n/', $headers, -1, PREG_SPLIT_NO_EMPTY);
-            foreach ($headerArr as $headerLine) {
-                if (strpos($headerLine, 'HTTP/') === 0 && strpos($headerLine, ':') === false) {
-                    list($protocolVersion, $statusCode, $reasonPhrase) = explode(' ', $headerLine, 3);
-                    list(, $protocolVersion) = explode('/', $protocolVersion, 2);
-                    $response = $response
-                        ->withProtocolVersion((int)$protocolVersion)
-                        ->withStatus($statusCode, $reasonPhrase);
-                } else {
-                    list($headerName, $value) = GeneralUtility::trimExplode(':', $headerLine, 2);
-                    $response = $response->withHeader($headerName, $value);
-                }
-            }
-        }
-        return $response;
+        return !GeneralUtility::cmpIP(GeneralUtility::getIndpEnv('REMOTE_ADDR'), $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask']);
     }
 
     /**
@@ -314,4 +131,24 @@ class ErrorController
         }
         return null;
     }
+
+    /**
+     * Ensures that a response object is created as a "fallback" when no error handler is configured.
+     *
+     * @param ServerRequestInterface $request
+     * @param int $statusCode
+     * @param string $reason
+     * @return ResponseInterface
+     */
+    protected function handleDefaultError(ServerRequestInterface $request, int $statusCode, string $reason = ''): ResponseInterface
+    {
+        if (strpos($request->getHeaderLine('Accept'), 'application/json') !== false) {
+            return new JsonResponse(['reason' => $reason], $statusCode);
+        }
+        $content = GeneralUtility::makeInstance(ErrorPageController::class)->errorAction(
+            'Page Not Found',
+            'The page did not exist or was inaccessible.' . ($reason ? ' Reason: ' . $reason : '')
+        );
+        return new HtmlResponse($content, $statusCode);
+    }
 }
index 15c992e..dd3a515 100644 (file)
@@ -1129,10 +1129,6 @@ class TypoScriptFrontendController implements LoggerAwareInterface
      * Exceptions may be thrown for DOKTYPE_SPACER and not loadable page records
      * or root lines.
      *
-     * If $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] is set,
-     * instead of throwing an exception it's handled by a page unavailable
-     * handler.
-     *
      * May set or update this properties:
      *
      * @see TypoScriptFrontendController::$id
index bb2a865..30fb75b 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\Rendering;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\CMS\Core\Tests\Functional\SiteHandling\SiteBasedTestTrait;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\ResponseContent;
@@ -676,10 +675,6 @@ class LocalizedSiteContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional
             ]
         );
 
-        if ($statusCode === 404) {
-            $this->expectExceptionCode(1518472189);
-            $this->expectException(PageNotFoundException::class);
-        }
         $response = $this->executeFrontendRequest(
             new InternalRequest('https://website.local/de/?id=' . static::VALUE_PageId)
         );
@@ -723,6 +718,7 @@ class LocalizedSiteContentRenderingTest extends \TYPO3\CMS\Core\Tests\Functional
             $this->assertEquals($fallbackChain, $responseStructure->getScopePath('languageInfo/fallbackChain'), 'fallbackChain does not match');
             $this->assertEquals($overlayMode, $responseStructure->getScopePath('languageInfo/overlayType'), 'language overlayType does not match');
         }
+        $this->assertEquals($statusCode, $response->getStatusCode());
     }
 
     public function contentOnPartiallyTranslatedPageDataProvider(): array
index 30e478c..0c38c7a 100644 (file)
@@ -106,17 +106,17 @@ abstract class AbstractLocalizedPagesTestCase extends AbstractTestCase
 
     /**
      * @param string $url
-     * @param string $exception
+     * @param int $exception
      */
-    protected function assertException(string $url, string $exception): void
+    protected function assertResponseStatusCode(string $url, int $statusCode): void
     {
         $this->setUpFrontendRootPage(
             1000,
             ['typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.typoscript']
         );
 
-        $this->expectException($exception);
-        $this->executeFrontendRequest(new InternalRequest($url), $this->internalRequestContext);
+        $response = $this->executeFrontendRequest(new InternalRequest($url), $this->internalRequestContext);
+        static::assertSame(404, $response->getStatusCode());
     }
 
     /**
index 0516c92..c713615 100644 (file)
@@ -34,7 +34,7 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  *   Calling home page in DE-CH renders page in DE as defined in the fallback chain
  *
  *   Calling "headquarter" page in EN renders page in EN
- *   Calling "headquarter" page in DE throws a PageNotFoundException because no fallback chain is configured
+ *   Calling "headquarter" page in DE returns a 404 response because no fallback chain is configured
  *   Calling "headquarter" page in DE-CH renders page in EN
  */
 class ScenarioATest extends AbstractLocalizedPagesTestCase
index cac360c..2920381 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-
 /**
  * Scenario prerequisites:
  *   Site configuration has localizations
@@ -31,11 +29,11 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
  *
  * Scenario expectations:
  *   Calling home page in EN renders page in EN
- *   Calling home page in DE throws a PageNotFoundException because no fallback chain is configured
+ *   Calling home page in DE returns a 404 response because no fallback chain is configured
  *   Calling home page in DE-CH renders page in EN as defined in the fallback chain
  *
  *   Calling "headquarter" page in EN renders page in EN
- *   Calling "headquarter" page in DE throws a PageNotFoundException because no fallback chain is configured
+ *   Calling "headquarter" page in DE returns a 404 response because no fallback chain is configured
  *   Calling "headquarter" page in DE-CH renders page in EN
  */
 class ScenarioBTest extends AbstractLocalizedPagesTestCase
@@ -110,21 +108,19 @@ class ScenarioBTest extends AbstractLocalizedPagesTestCase
         return [
             'home page in DE where page translation does not exist and has no fallback configured' => [
                 'url' => 'https://acme.com/de/hello',
-                'exception' => PageNotFoundException::class,
             ],
         ];
     }
 
     /**
      * @param string $url
-     * @param string $exception
      *
      * @test
      * @dataProvider pageNotFoundDataProvider
      */
-    public function requestsThrowException(string $url, string $exception): void
+    public function pageNotFound(string $url): void
     {
-        $this->assertException($url, $exception);
+        $this->assertResponseStatusCode($url, 404);
     }
 
     /**
index df29a50..cddd2c8 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-
 /**
  * Scenario prerequisites:
  *   Site configuration has localizations
@@ -33,10 +31,10 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
  * Scenario expectations:
  *   Calling home page in EN renders page in EN
  *   Calling home page in DE renders page in DE
- *   Calling home page in DE-CH throws a PageNotFoundException because fallback chain is not processed due to l18n_cfg=2
+ *   Calling home page in DE-CH returns a 404 response because fallback chain is not processed due to l18n_cfg=2
  *
  *   Calling "about" page in EN renders page in EN
- *   Calling "about" page in DE throws a PageNotFoundException because fallback chain is not processed due to l18n_cfg=2
+ *   Calling "about" page in DE returns a 404 response because fallback chain is not processed due to l18n_cfg=2
  *   Calling "about" page in DE-CH renders page in DE
  */
 class ScenarioCTest extends AbstractLocalizedPagesTestCase
@@ -111,25 +109,22 @@ class ScenarioCTest extends AbstractLocalizedPagesTestCase
         return [
             'home page in DE-CH where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/wllkommen',
-                'exception' => PageNotFoundException::class,
             ],
             'about page in DE where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de/ueber-uns',
-                'exception' => PageNotFoundException::class,
             ],
         ];
     }
 
     /**
      * @param string $url
-     * @param string $exception
      *
      * @test
      * @dataProvider pageNotFoundDataProvider
      */
-    public function requestsThrowException(string $url, string $exception): void
+    public function pageNotFound(string $url): void
     {
-        $this->assertException($url, $exception);
+        $this->assertResponseStatusCode($url, 404);
     }
 
     /**
index 5f66b4e..7a8351a 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-
 /**
  * Scenario prerequisites:
  *   Site configuration has localizations
@@ -33,8 +31,8 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
  *
  * Scenario expectations:
  *   Calling home page in EN renders page in EN
- *   Calling home page in DE throws a PageNotFoundException
- *   Calling home page in DE-CH throws a PageNotFoundException because fallback chain is not processed due to l18n_cfg=2
+ *   Calling home page in DE returns a 404 response
+ *   Calling home page in DE-CH returns a 404 response because fallback chain is not processed due to l18n_cfg=2
  *
  *   Calling "about" page in EN renders page in EN
  *   Calling "about" page in DE renders page in DE
@@ -112,29 +110,25 @@ class ScenarioDTest extends AbstractLocalizedPagesTestCase
         return [
             'home page in DE where page translation does not exist' => [
                 'url' => 'https://acme.com/de/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'home page in DE-CH where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'DE-CH shortcut to home page where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/shortcut-to-welcome',
-                'exception' => PageNotFoundException::class,
             ]
         ];
     }
 
     /**
      * @param string $url
-     * @param string $exception
      *
      * @test
      * @dataProvider pageNotFoundDataProvider
      */
-    public function requestsThrowException(string $url, string $exception): void
+    public function pageNotFound(string $url): void
     {
-        $this->assertException($url, $exception);
+        $this->assertResponseStatusCode($url, 404);
     }
 
     /**
index aff3b2c..d03d61c 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-
 /**
  * Scenario prerequisites:
  *   Site configuration has localizations
@@ -32,14 +30,14 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
  *   "Products" page is localized into DE and has l18n_cfg=1 set
  *
  * Scenario expectations:
- *   Calling home page in EN throws a PageNotFoundException due to l18n_cfg=1
- *   Calling home page in DE throws a PageNotFoundException
- *   Calling home page in DE-CH throws a PageNotFoundException because EN is used as fallback but is not processed due to l18n_cfg=1
+ *   Calling home page in EN returns a 404 response due to l18n_cfg=1
+ *   Calling home page in DE returns a 404 response
+ *   Calling home page in DE-CH returns a 404 response because EN is used as fallback but is not processed due to l18n_cfg=1
  *
- *   Calling "about" page in EN throws a PageNotFoundException due to l18n_cfg=1
+ *   Calling "about" page in EN returns a 404 response due to l18n_cfg=1
  *   Calling "about" page in DE renders page in DE
  *   Calling "about" page in DE-CH renders page in DE
- *   Calling "about" page in DE with EN slug throws a PageNotFoundException
+ *   Calling "about" page in DE with EN slug returns a 404 response
  *   Calling "about" page in DE-CH with EN slug renders page in DE
  */
 class ScenarioETest extends AbstractLocalizedPagesTestCase
@@ -108,37 +106,31 @@ class ScenarioETest extends AbstractLocalizedPagesTestCase
         return [
             'home page in EN' => [
                 'url' => 'https://acme.com/en/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'home page in DE where page translation does not exist' => [
                 'url' => 'https://acme.com/de/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'home page in DE-CH where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'about page in EN' => [
                 'url' => 'https://acme.com/en/about-us',
-                'exception' => PageNotFoundException::class,
             ],
             'about page in DE with EN slug' => [
                 'url' => 'https://acme.com/de/about-us',
-                'exception' => PageNotFoundException::class,
             ],
         ];
     }
 
     /**
      * @param string $url
-     * @param string $exception
      *
      * @test
      * @dataProvider pageNotFoundDataProvider
      */
-    public function requestsThrowException(string $url, string $exception): void
+    public function pageNotFound(string $url): void
     {
-        $this->assertException($url, $exception);
+        $this->assertResponseStatusCode($url, 404);
     }
 
     /**
index 8bc98d4..d2b0b04 100644 (file)
@@ -16,8 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling\LocalizedPageRenderin
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
-
 /**
  * Scenario prerequisites:
  *   Site configuration has localizations
@@ -32,15 +30,15 @@ use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
  *   "Products" page is localized into DE-CH, DE and has l18n_cfg=3 set
  *
  * Scenario expectations:
- *   Calling home page in EN throws a PageNotFoundException due to l18n_cfg=3
+ *   Calling home page in EN returns a 404 response due to l18n_cfg=3
  *   Calling home page in DE renders page in DE
- *   Calling home page in DE-CH throws a PageNotFoundException because fallback chain is not processed due to l18n_cfg=3
+ *   Calling home page in DE-CH returns a 404 response because fallback chain is not processed due to l18n_cfg=3
  *
- *   Calling "about" page in EN throws a PageNotFoundException due to l18n_cfg=3
+ *   Calling "about" page in EN returns a 404 response due to l18n_cfg=3
  *   Calling "about" page in DE renders page in DE
- *   Calling "about" page in DE-CH throws a PageNotFoundException because fallback chain is not processed due to l18n_cfg=3
+ *   Calling "about" page in DE-CH returns a 404 response because fallback chain is not processed due to l18n_cfg=3
  *
- *   Calling "products" page in EN throws a PageNotFoundException due to l18n_cfg=3
+ *   Calling "products" page in EN returns a 404 response due to l18n_cfg=3
  *   Calling "products" page in DE renders page in DE
  *   Calling "products" page in DE-CH renders page in DE-CH
  */
@@ -110,41 +108,34 @@ class ScenarioFTest extends AbstractLocalizedPagesTestCase
         return [
             'home page in EN' => [
                 'url' => 'https://acme.com/en/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'home page in DE where page translation does not exist' => [
                 'url' => 'https://acme.com/de/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'home page in DE-CH where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/hello',
-                'exception' => PageNotFoundException::class,
             ],
             'about page in EN' => [
                 'url' => 'https://acme.com/en/about-us',
-                'exception' => PageNotFoundException::class,
             ],
             'about page in DE-CH where page translation does not exist and is trapped by l18n_cfg' => [
                 'url' => 'https://acme.com/de-ch/ueber-uns',
-                'exception' => PageNotFoundException::class,
             ],
             'products page in EN' => [
                 'url' => 'https://acme.com/en/products',
-                'exception' => PageNotFoundException::class,
             ],
         ];
     }
 
     /**
      * @param string $url
-     * @param string $exception
      *
      * @test
      * @dataProvider pageNotFoundDataProvider
      */
-    public function requestsThrowException(string $url, string $exception): void
+    public function pageNotFound(string $url): void
     {
-        $this->assertException($url, $exception);
+        $this->assertResponseStatusCode($url, 404);
     }
 
     /**
index 9199ed5..9a2c862 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
  */
 
 use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
@@ -607,60 +606,6 @@ class SiteRequestTest extends AbstractTestCase
      * @test
      * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
      */
-    public function pageRequestThrowsExceptionWithInvalidCacheHashWithoutHavingErrorHandling(string $uri)
-    {
-        $this->writeSiteConfiguration(
-            'website-local',
-            $this->buildSiteConfiguration(1000, 'https://website.local/')
-        );
-
-        $this->expectExceptionCode(1518472189);
-        $this->expectException(PageNotFoundException::class);
-
-        $this->executeFrontendRequest(
-            new InternalRequest($uri),
-            $this->internalRequestContext
-        );
-    }
-
-    /**
-     * @param string $uri
-     *
-     * @test
-     * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
-     */
-    public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri)
-    {
-        $response = $this->executeFrontendRequest(
-            new InternalRequest($uri),
-            $this->internalRequestContext->withMergedGlobalSettings([
-                'TYPO3_CONF_VARS' => [
-                    'FE' => [
-                        'pageNotFound_handling' => 'READFILE:typo3/sysext/core/Tests/Functional/Fixtures/Frontend/PageError.txt',
-                    ]
-                ]
-            ])
-        );
-
-        static::assertSame(
-            404,
-            $response->getStatusCode()
-        );
-        static::assertThat(
-            (string)$response->getBody(),
-            static::logicalOr(
-                static::stringContains('reason: Request parameters could not be validated (&amp;cHash empty)'),
-                static::stringContains('reason: Request parameters could not be validated (&amp;cHash comparison failed)')
-            )
-        );
-    }
-
-    /**
-     * @param string $uri
-     *
-     * @test
-     * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
-     */
     public function pageRequestSendsNotFoundResponseWithInvalidCacheHashWithHavingFluidErrorHandling(string $uri)
     {
         $this->writeSiteConfiguration(
index 42b7650..d822fb2 100644 (file)
@@ -16,7 +16,6 @@ namespace TYPO3\CMS\Frontend\Tests\Functional\SiteHandling;
  */
 
 use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Error\Http\PageNotFoundException;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerFactory;
 use TYPO3\TestingFramework\Core\Functional\Framework\DataHandling\Scenario\DataHandlerWriter;
 use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
@@ -236,24 +235,8 @@ class SlugSiteRequestTest extends AbstractTestCase
         );
 
         $uri = 'https://website.other/any/invalid/slug';
-        $response = $this->executeFrontendRequest(
-            new InternalRequest($uri),
-            $this->internalRequestContext->withMergedGlobalSettings([
-                'TYPO3_CONF_VARS' => [
-                    'FE' => [
-                        'pageNotFound_handling' => 'READFILE:typo3/sysext/core/Tests/Functional/Fixtures/Frontend/PageError.txt',
-                    ]
-                ]
-            ])
-        );
-        static::assertSame(
-            404,
-            $response->getStatusCode()
-        );
-        static::assertThat(
-            (string)$response->getBody(),
-            static::stringContains('No site configuration found')
-        );
+        $response = $this->executeFrontendRequest(new InternalRequest($uri));
+        static::assertSame(404, $response->getStatusCode());
     }
 
     /**
@@ -719,52 +702,18 @@ class SlugSiteRequestTest extends AbstractTestCase
      * @test
      * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
      */
-    public function pageRequestThrowsExceptionWithInvalidCacheHashWithoutHavingErrorHandling(string $uri)
+    public function pageRequestNotFoundInvalidCacheHashWithoutHavingErrorHandling(string $uri)
     {
         $this->writeSiteConfiguration(
             'website-local',
             $this->buildSiteConfiguration(1000, 'https://website.local/')
         );
 
-        $this->expectExceptionCode(1518472189);
-        $this->expectException(PageNotFoundException::class);
-
-        $this->executeFrontendRequest(
-            new InternalRequest($uri),
-            $this->internalRequestContext
-        );
-    }
-
-    /**
-     * @param string $uri
-     *
-     * @test
-     * @dataProvider pageRenderingStopsWithInvalidCacheHashDataProvider
-     */
-    public function pageRequestSendsNotFoundResponseWithInvalidCacheHash(string $uri)
-    {
         $response = $this->executeFrontendRequest(
             new InternalRequest($uri),
-            $this->internalRequestContext->withMergedGlobalSettings([
-                'TYPO3_CONF_VARS' => [
-                    'FE' => [
-                        'pageNotFound_handling' => 'READFILE:typo3/sysext/core/Tests/Functional/Fixtures/Frontend/PageError.txt',
-                    ]
-                ]
-            ])
-        );
-
-        static::assertSame(
-            404,
-            $response->getStatusCode()
-        );
-        static::assertThat(
-            (string)$response->getBody(),
-            static::logicalOr(
-                static::stringContains('reason: Request parameters could not be validated (&amp;cHash empty)'),
-                static::stringContains('reason: Request parameters could not be validated (&amp;cHash comparison failed)')
-            )
+            $this->internalRequestContext
         );
+        static::assertSame(404, $response->getStatusCode());
     }
 
     /**
index 05b8f07..34635ea 100644 (file)
@@ -15,15 +15,7 @@ namespace TYPO3\CMS\Frontend\Tests\Unit\Controller;
  * The TYPO3 project - inspiring people to share!
  */
 
-use Prophecy\Argument;
-use Psr\Http\Message\StreamInterface;
-use TYPO3\CMS\Core\Controller\ErrorPageController;
-use TYPO3\CMS\Core\Http\HtmlResponse;
-use TYPO3\CMS\Core\Http\RedirectResponse;
-use TYPO3\CMS\Core\Http\RequestFactory;
-use TYPO3\CMS\Core\Http\Response;
 use TYPO3\CMS\Core\Http\ServerRequest;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Frontend\Controller\ErrorController;
 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 
@@ -33,242 +25,15 @@ use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
 class ErrorControllerTest extends UnitTestCase
 {
     /**
-     * Purge possibly left over instances
-     */
-    public function tearDown(): void
-    {
-        GeneralUtility::purgeInstances();
-        parent::tearDown();
-    }
-
-    /**
      * @test
      */
     public function pageNotFoundHandlingThrowsExceptionIfNotConfigured()
     {
-        $this->expectExceptionMessage('This test page was not found!');
-        $this->expectExceptionCode(1518472189);
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] = false;
-        $GLOBALS['TYPO3_REQUEST'] = [];
-        $subject = new ErrorController();
-        $subject->pageNotFoundAction(new ServerRequest(), 'This test page was not found!');
-    }
-
-    /**
-     * Data Provider for 404
-     *
-     * @return array
-     */
-    public function errorPageHandlingDataProvider()
-    {
-        return [
-            '404 with default errorpage' => [
-                'handler' => true,
-                'header' => 'HTTP/1.0 404 Not Found',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 404,
-                    'reasonPhrase' => 'Not Found',
-                    'content' => 'Reason: Custom message',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '404 with default errorpage setting the handler to legacy value' => [
-                'handler' => '1',
-                'header' => 'HTTP/1.0 404 This is a dead end',
-                'message' => 'Come back tomorrow',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 404,
-                    'reasonPhrase' => 'This is a dead end',
-                    'content' => 'Reason: Come back tomorrow',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '404 with custom userfunction' => [
-                'handler' => 'USER_FUNCTION:' . ErrorControllerTest::class . '->mockedUserFunctionCall',
-                'header' => 'HTTP/1.0 404 Not Found',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 404,
-                    'reasonPhrase' => 'Not Found',
-                    'content' => 'It\'s magic, Michael: Custom message',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '404 with a readfile functionality' => [
-                'handler' => 'READFILE:typo3/sysext/frontend/Tests/Unit/Controller/Fixtures/error.txt',
-                'header' => 'HTTP/1.0 404 Not Found',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 404,
-                    'reasonPhrase' => 'Not Found',
-                    'content' => 'rama-lama-ding-dong',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '404 with a readfile functionality with an invalid file' => [
-                'handler' => 'READFILE:does_not_exist.php6',
-                'header' => 'HTTP/1.0 404 Not Found',
-                'message' => 'Custom message',
-                'response' => null,
-                'exceptionCode' => 1518472245,
-            ],
-            '404 with a redirect - never do that in production - it is bad for SEO. But with custom headers as well...' => [
-                'handler' => 'REDIRECT:www.typo3.org',
-                'header' => 'HTTP/1.0 404 Not Found
-X-TYPO3-Additional-Header: Banana Stand',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => RedirectResponse::class,
-                    'statusCode' => 404,
-                    'reasonPhrase' => 'Not Found',
-                    'headers' => [
-                        'location' => ['www.typo3.org'],
-                        'X-TYPO3-Additional-Header' => ['Banana Stand'],
-                    ],
-                ],
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider errorPageHandlingDataProvider
-     */
-    public function pageNotFoundHandlingReturnsConfiguredResponseObject(
-        $handler,
-        $header,
-        $message,
-        $expectedResponseDetails,
-        $expectedExceptionCode = null
-    ) {
-        if ($expectedExceptionCode !== null) {
-            $this->expectExceptionCode($expectedExceptionCode);
-        }
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] = $handler;
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'] = $header;
-        // faking getIndpEnv() variables
-        $_SERVER['REQUEST_URI'] = '/unit-test/';
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-        $_SERVER['HTTP_HOST'] = 'localhost';
-        $_SERVER['SSL_SESSION_ID'] = true;
         $GLOBALS['TYPO3_REQUEST'] = [];
-
-        $this->prophesizeErrorPageController();
-
         $subject = new ErrorController();
-        $response = $subject->pageNotFoundAction(new ServerRequest(), $message);
-        if (is_array($expectedResponseDetails)) {
-            $this->assertInstanceOf($expectedResponseDetails['type'], $response);
-            $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
-            $this->assertEquals($expectedResponseDetails['reasonPhrase'], $response->getReasonPhrase());
-            if (isset($expectedResponseDetails['content'])) {
-                $this->assertStringContainsString($expectedResponseDetails['content'], $response->getBody()->getContents());
-            }
-            $this->assertEquals($expectedResponseDetails['headers'], $response->getHeaders());
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function pageNotFoundHandlingReturnsResponseFromPrefix()
-    {
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] = '/404/';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_statheader'] = 'HTTP/1.0 404 Not Found
-X-TYPO3-Additional-Header: Banana Stand';
-        // faking getIndpEnv() variables
-        $_SERVER['REQUEST_URI'] = '/unit-test/';
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-        $_SERVER['HTTP_HOST'] = 'localhost';
-        $_SERVER['SSL_SESSION_ID'] = true;
-        $GLOBALS['TYPO3_REQUEST'] = [];
-        $this->prophesizeErrorPageController();
-        $subject = new ErrorController();
-
-        $this->prophesizeGetUrl();
-        $response = $subject->pageNotFoundAction(new ServerRequest(), 'Custom message');
-
-        $expectedResponseDetails = [
-            'type' => HtmlResponse::class,
-            'statusCode' => 404,
-            'reasonPhrase' => 'Not Found',
-            'headers' => [
-                'Content-Type' => ['text/html; charset=utf-8'],
-                'X-TYPO3-Additional-Header' => ['Banana Stand'],
-            ],
-        ];
-        $this->assertInstanceOf($expectedResponseDetails['type'], $response);
-        $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
-        $this->assertEquals($expectedResponseDetails['reasonPhrase'], $response->getReasonPhrase());
-        if (isset($expectedResponseDetails['content'])) {
-            $this->assertContains($expectedResponseDetails['content'], $response->getBody()->getContents());
-        }
-        $this->assertEquals($expectedResponseDetails['headers'], $response->getHeaders());
-    }
-
-    /**
-     * Data Provider for 403
-     *
-     * @return array
-     */
-    public function accessDeniedDataProvider()
-    {
-        return [
-            '403 with default errorpage' => [
-                'handler' => true,
-                'header' => 'HTTP/1.0 403 Who are you',
-                'message' => 'Be nice, do good',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 403,
-                    'reasonPhrase' => 'Who are you',
-                    'content' => 'Reason: Be nice, do good',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider accessDeniedDataProvider
-     */
-    public function accessDeniedReturnsProperHeaders($handler, $header, $message, $expectedResponseDetails)
-    {
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling'] = $handler;
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageNotFound_handling_accessdeniedheader'] = $header;
-        // faking getIndpEnv() variables
-        $_SERVER['REQUEST_URI'] = '/unit-test/';
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-        $_SERVER['HTTP_HOST'] = 'localhost';
-        $_SERVER['SSL_SESSION_ID'] = true;
-        $GLOBALS['TYPO3_REQUEST'] = [];
-        $subject = new ErrorController();
-        $response = $subject->accessDeniedAction(new ServerRequest(), $message);
-        if (is_array($expectedResponseDetails)) {
-            $this->assertInstanceOf($expectedResponseDetails['type'], $response);
-            $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
-            $this->assertEquals($expectedResponseDetails['reasonPhrase'], $response->getReasonPhrase());
-            if (isset($expectedResponseDetails['content'])) {
-                $this->assertStringContainsString($expectedResponseDetails['content'], $response->getBody()->getContents());
-            }
-            $this->assertEquals($expectedResponseDetails['headers'], $response->getHeaders());
-        }
+        $response = $subject->pageNotFoundAction(new ServerRequest(), 'This test page was not found!');
+        static::assertSame(404, $response->getStatusCode());
+        static::assertStringContainsString('This test page was not found!', $response->getBody()->getContents());
     }
 
     /**
@@ -277,11 +42,9 @@ X-TYPO3-Additional-Header: Banana Stand';
     public function unavailableHandlingThrowsExceptionIfNotConfigured()
     {
         $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = true;
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
         $this->expectExceptionMessage('All your system are belong to us!');
         $this->expectExceptionCode(1518472181);
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = false;
         $subject = new ErrorController();
         $subject->unavailableAction(new ServerRequest(), 'All your system are belong to us!');
     }
@@ -292,7 +55,6 @@ X-TYPO3-Additional-Header: Banana Stand';
     public function unavailableHandlingDoesNotTriggerDueToDevIpMask()
     {
         $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '*';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = true;
         $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
 
         $this->expectExceptionMessage('All your system are belong to us!');
@@ -302,200 +64,77 @@ X-TYPO3-Additional-Header: Banana Stand';
     }
 
     /**
-     * Data Provider for 503
-     *
-     * @return array
+     * @test
      */
-    public function unavailableHandlingDataProvider()
+    public function defaultErrorHandlerWithHtmlResponseIsChosenWhenNoSiteConfiguredForPageNotFoundAction()
     {
-        return [
-            '503 with default errorpage' => [
-                'handler' => true,
-                'header' => 'HTTP/1.0 503 Service Temporarily Unavailable',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 503,
-                    'reasonPhrase' => 'Service Temporarily Unavailable',
-                    'content' => 'Reason: Custom message',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '503 with default errorpage setting the handler to legacy value' => [
-                'handler' => '1',
-                'header' => 'HTTP/1.0 503 This is a dead end',
-                'message' => 'Come back tomorrow',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 503,
-                    'reasonPhrase' => 'This is a dead end',
-                    'content' => 'Reason: Come back tomorrow',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '503 with custom userfunction' => [
-                'handler' => 'USER_FUNCTION:' . ErrorControllerTest::class . '->mockedUserFunctionCall',
-                'header' => 'HTTP/1.0 503 Service Temporarily Unavailable',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 503,
-                    'reasonPhrase' => 'Service Temporarily Unavailable',
-                    'content' => 'It\'s magic, Michael: Custom message',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '503 with a readfile functionality' => [
-                'handler' => 'READFILE:typo3/sysext/frontend/Tests/Unit/Controller/Fixtures/error.txt',
-                'header' => 'HTTP/1.0 503 Service Temporarily Unavailable',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => HtmlResponse::class,
-                    'statusCode' => 503,
-                    'reasonPhrase' => 'Service Temporarily Unavailable',
-                    'content' => 'Let it snow',
-                    'headers' => [
-                        'Content-Type' => ['text/html; charset=utf-8'],
-                    ],
-                ],
-            ],
-            '503 with a readfile functionality with an invalid file' => [
-                'handler' => 'READFILE:does_not_exist.php6',
-                'header' => 'HTTP/1.0 503 Service Temporarily Unavailable',
-                'message' => 'Custom message',
-                'response' => null,
-                'exceptionCode' => 1518472245,
-            ],
-            '503 with a redirect - never do that in production - it is bad for SEO. But with custom headers as well...' => [
-                'handler' => 'REDIRECT:www.typo3.org',
-                'header' => 'HTTP/1.0 503 Service Temporarily Unavailable
-X-TYPO3-Additional-Header: Banana Stand',
-                'message' => 'Custom message',
-                'response' => [
-                    'type' => RedirectResponse::class,
-                    'statusCode' => 503,
-                    'reasonPhrase' => 'Service Temporarily Unavailable',
-                    'headers' => [
-                        'location' => ['www.typo3.org'],
-                        'X-TYPO3-Additional-Header' => ['Banana Stand'],
-                    ],
-                ],
-            ],
-        ];
+        $subject = new ErrorController();
+        $response = $subject->pageNotFoundAction(new ServerRequest(), 'Error handler is not configured.');
+        self::assertSame(404, $response->getStatusCode());
+        self::assertSame('text/html; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertStringContainsString('Error handler is not configured.', $response->getBody()->getContents());
     }
 
     /**
      * @test
-     * @dataProvider unavailableHandlingDataProvider
      */
-    public function pageUnavailableHandlingReturnsConfiguredResponseObject(
-        $handler,
-        $header,
-        $message,
-        $expectedResponseDetails,
-        $expectedExceptionCode = null
-    ) {
-        if ($expectedExceptionCode !== null) {
-            $this->expectExceptionCode($expectedExceptionCode);
-        }
-        $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '-1';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = $handler;
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'] = $header;
-        // faking getIndpEnv() variables
-        $_SERVER['REQUEST_URI'] = '/unit-test/';
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-        $_SERVER['HTTP_HOST'] = 'localhost';
-        $_SERVER['SSL_SESSION_ID'] = true;
-        $GLOBALS['TYPO3_REQUEST'] = [];
-        $this->prophesizeGetUrl();
-        $this->prophesizeErrorPageController();
+    public function defaultErrorHandlerWithJsonResponseIsChosenWhenNoSiteConfiguredForPageNotFoundAction()
+    {
         $subject = new ErrorController();
-        $response = $subject->unavailableAction(new ServerRequest(), $message);
-        if (is_array($expectedResponseDetails)) {
-            $this->assertInstanceOf($expectedResponseDetails['type'], $response);
-            $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
-            $this->assertEquals($expectedResponseDetails['reasonPhrase'], $response->getReasonPhrase());
-            if (isset($expectedResponseDetails['content'])) {
-                $this->assertStringContainsString($expectedResponseDetails['content'], $response->getBody()->getContents());
-            }
-            $this->assertEquals($expectedResponseDetails['headers'], $response->getHeaders());
-        }
+        $response = $subject->pageNotFoundAction((new ServerRequest())->withAddedHeader('Accept', 'application/json'), 'Error handler is not configured.');
+        $responseContent = \json_decode($response->getBody()->getContents(), true);
+        self::assertSame(404, $response->getStatusCode());
+        self::assertSame('application/json; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertEquals(['reason' => 'Error handler is not configured.'], $responseContent);
     }
 
     /**
      * @test
      */
-    public function pageUnavailableHandlingReturnsResponseOfPrefix()
+    public function defaultErrorHandlerWithHtmlResponseIsChosenWhenNoSiteConfiguredForUnavailableAction()
     {
-        $GLOBALS['TYPO3_CONF_VARS']['SYS']['devIPmask'] = '-1';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = '/fail/';
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling_statheader'] = 'HTTP/1.0 503 Service Temporarily Unavailable
-X-TYPO3-Additional-Header: Banana Stand';
-        // faking getIndpEnv() variables
-        $_SERVER['REQUEST_URI'] = '/unit-test/';
-        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-        $_SERVER['HTTP_HOST'] = 'localhost';
-        $_SERVER['SSL_SESSION_ID'] = true;
-        $GLOBALS['TYPO3_REQUEST'] = [];
-        $this->prophesizeErrorPageController();
-        $this->prophesizeGetUrl();
         $subject = new ErrorController();
-        $response = $subject->unavailableAction(new ServerRequest(), 'custom message');
-
-        $expectedResponseDetails = [
-            'type' => HtmlResponse::class,
-            'statusCode' => 503,
-            'reasonPhrase' => 'Service Temporarily Unavailable',
-            'headers' => [
-                'Content-Type' => ['text/html; charset=utf-8'],
-                'X-TYPO3-Additional-Header' => ['Banana Stand'],
-            ],
-        ];
-        $this->assertInstanceOf($expectedResponseDetails['type'], $response);
-        $this->assertEquals($expectedResponseDetails['statusCode'], $response->getStatusCode());
-        $this->assertEquals($expectedResponseDetails['reasonPhrase'], $response->getReasonPhrase());
-        if (isset($expectedResponseDetails['content'])) {
-            $this->assertContains($expectedResponseDetails['content'], $response->getBody()->getContents());
-        }
-        $this->assertEquals($expectedResponseDetails['headers'], $response->getHeaders());
+        $response = $subject->unavailableAction(new ServerRequest(), 'Error handler is not configured.');
+        self::assertSame(500, $response->getStatusCode());
+        self::assertSame('text/html; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertStringContainsString('Error handler is not configured.', $response->getBody()->getContents());
     }
 
     /**
-     * Callback function when testing "USER_FUNCTION:" prefix
+     * @test
      */
-    public function mockedUserFunctionCall($params)
+    public function defaultErrorHandlerWithJsonResponseIsChosenWhenNoSiteConfiguredForUnavailableAction()
     {
-        return '<p>It\'s magic, Michael: ' . $params['reasonText'] . '</p>';
+        $subject = new ErrorController();
+        $response = $subject->unavailableAction((new ServerRequest())->withAddedHeader('Accept', 'application/json'), 'Error handler is not configured.');
+        $responseContent = \json_decode($response->getBody()->getContents(), true);
+        self::assertSame(500, $response->getStatusCode());
+        self::assertSame('application/json; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertEquals(['reason' => 'Error handler is not configured.'], $responseContent);
     }
 
-    private function prophesizeErrorPageController(): void
+    /**
+     * @test
+     */
+    public function defaultErrorHandlerWithHtmlResponseIsChosenWhenNoSiteConfiguredForAccessDeniedAction()
     {
-        $errorPageControllerProphecy = $this->prophesize(ErrorPageController::class);
-        $errorPageControllerProphecy->errorAction(Argument::cetera())
-            ->will(
-                function ($args) {
-                    return 'Reason: ' . $args[1];
-                }
-            );
-        GeneralUtility::addInstance(ErrorPageController::class, $errorPageControllerProphecy->reveal());
+        $subject = new ErrorController();
+        $response = $subject->accessDeniedAction(new ServerRequest(), 'Error handler is not configured.');
+        self::assertSame(403, $response->getStatusCode());
+        self::assertSame('text/html; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertStringContainsString('Error handler is not configured.', $response->getBody()->getContents());
     }
 
-    private function prophesizeGetUrl(): void
+    /**
+     * @test
+     */
+    public function defaultErrorHandlerWithJsonResponseIsChosenWhenNoSiteConfiguredForAccessDeniedAction()
     {
-        $streamProphecy = $this->prophesize(StreamInterface::class);
-        $prefixPageResponseProphecy = $this->prophesize(Response::class);
-        $prefixPageResponseProphecy->getHeaders()->willReturn([]);
-        $prefixPageResponseProphecy->getBody()->willReturn($streamProphecy);
-        $prefixPageResponseProphecy->getStatusCode()->willReturn(200);
-        $prefixPageResponseProphecy->getHeaderLine('Content-Type')->willReturn('text/html; charset=utf-8');
-        $requestFactoryProphecy = $this->prophesize(RequestFactory::class);
-        $requestFactoryProphecy->request(Argument::cetera())->willReturn($prefixPageResponseProphecy->reveal());
-        GeneralUtility::addInstance(RequestFactory::class, $requestFactoryProphecy->reveal());
+        $subject = new ErrorController();
+        $response = $subject->accessDeniedAction((new ServerRequest())->withAddedHeader('Accept', 'application/json'), 'Error handler is not configured.');
+        $responseContent = \json_decode($response->getBody()->getContents(), true);
+        self::assertSame(403, $response->getStatusCode());
+        self::assertSame('application/json; charset=utf-8', $response->getHeaderLine('Content-Type'));
+        self::assertEquals(['reason' => 'Error handler is not configured.'], $responseContent);
     }
 }
index 63a830c..4e3a3d1 100644 (file)
@@ -140,6 +140,12 @@ class SilentConfigurationUpgradeService
         'EXT/extConf',
         // #87767
         'SYS/recursiveDomainSearch',
+        // #88376
+        'FE/pageNotFound_handling',
+        'FE/pageNotFound_handling_statheader',
+        'FE/pageNotFound_handling_accessdeniedheader',
+        'FE/pageUnavailable_handling',
+        'FE/pageUnavailable_handling_statheader',
     ];
 
     public function __construct(ConfigurationManager $configurationManager = null)
index 2519b50..da2e085 100644 (file)
@@ -251,6 +251,36 @@ return [
             'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
         ],
     ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'pageNotFound_handling\']' => [
+        'restFiles' => [
+            'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
+            'Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst',
+        ],
+    ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'pageNotFound_handling_statheader\']' => [
+        'restFiles' => [
+            'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
+            'Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst',
+        ],
+    ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'pageNotFound_handling_accessdeniedheader\']' => [
+        'restFiles' => [
+            'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
+            'Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst',
+        ],
+    ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'pageUnavailable_handling\']' => [
+        'restFiles' => [
+            'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
+            'Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst',
+        ],
+    ],
+    '$GLOBALS[\'TYPO3_CONF_VARS\'][\'FE\'][\'pageUnavailable_handling_statheader\']' => [
+        'restFiles' => [
+            'Breaking-87193-DeprecatedFunctionalityRemoved.rst',
+            'Breaking-88376-RemovedObsoletePageNotFound_handlingSettings.rst',
+        ],
+    ],
     '$GLOBALS[\'TCA\'][\'sys_history\']' => [
         'restFiles' => [
             'Breaking-87936-TCAForSysHistoryRemoved.rst',
index 554d756..5a7afc0 100644 (file)
@@ -271,8 +271,6 @@ class RedirectService implements LoggerAwareInterface
      */
     protected function bootFrontendController(?SiteInterface $site, array $queryParams): TypoScriptFrontendController
     {
-        // disable page errors
-        $GLOBALS['TYPO3_CONF_VARS']['FE']['pageUnavailable_handling'] = false;
         $controller = GeneralUtility::makeInstance(
             TypoScriptFrontendController::class,
             null,
index 2d150db..db082a9 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\CSV\Modify;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
+
 /**
  * Functional test for the DataHandler
  */
@@ -219,8 +222,11 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, self::VALUE_BackendUserId, self::VALUE_WorkspaceId, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId),
+            (new InternalRequestContext())->withBackendUserId(self::VALUE_BackendUserId)->withWorkspaceId(self::VALUE_WorkspaceId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 9a0419d..d2fe08f 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\CSV\Publish;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -233,8 +235,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         $this->actionService->publishRecord(self::TABLE_Page, self::VALUE_PageId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index aad5010..78ccf54 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\CSV\PublishAll
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -227,8 +229,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         $this->actionService->publishWorkspace(self::VALUE_WorkspaceId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 1e61ad6..cac885f 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\ForeignField\M
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
+
 /**
  * Functional test for the DataHandler
  */
@@ -219,8 +222,11 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, self::VALUE_BackendUserId, self::VALUE_WorkspaceId, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId),
+            (new InternalRequestContext())->withBackendUserId(self::VALUE_BackendUserId)->withWorkspaceId(self::VALUE_WorkspaceId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 2053e08..2f89686 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\ForeignField\P
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -238,8 +240,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         $this->actionService->publishRecord(self::TABLE_Page, self::VALUE_PageId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 52fbe93..e1c5e25 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRRE\ForeignField\P
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -232,8 +234,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\IRR
         $this->actionService->publishWorkspace(self::VALUE_WorkspaceId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 54ffae9..a019e67 100644 (file)
@@ -14,6 +14,9 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Regular\Modify;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequestContext;
+
 /**
  * Functional test for the DataHandler
  */
@@ -321,8 +324,11 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         parent::deletePage();
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, self::VALUE_BackendUserId, self::VALUE_WorkspaceId, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId),
+            (new InternalRequestContext())->withBackendUserId(self::VALUE_BackendUserId)->withWorkspaceId(self::VALUE_WorkspaceId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
@@ -334,8 +340,11 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         parent::deleteContentAndPage();
         $this->assertAssertionDataSet('deleteContentAndPage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, self::VALUE_BackendUserId, self::VALUE_WorkspaceId, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId),
+            (new InternalRequestContext())->withBackendUserId(self::VALUE_BackendUserId)->withWorkspaceId(self::VALUE_WorkspaceId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index 675919e..fe05314 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Regular\Publish;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -324,8 +326,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         $this->actionService->publishRecord(self::TABLE_Page, self::VALUE_PageId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
@@ -338,8 +342,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         $this->actionService->publishRecord(self::TABLE_Page, self::VALUE_PageId);
         $this->assertAssertionDataSet('deleteContentAndPage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
index cb322b7..30a7ff0 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Regular\PublishAll;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
+
 /**
  * Functional test for the DataHandler
  */
@@ -305,8 +307,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         $this->actionService->publishWorkspace(self::VALUE_WorkspaceId);
         $this->assertAssertionDataSet('deletePage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**
@@ -319,8 +323,10 @@ class ActionTest extends \TYPO3\CMS\Workspaces\Tests\Functional\DataHandling\Reg
         $this->actionService->publishWorkspace(self::VALUE_WorkspaceId);
         $this->assertAssertionDataSet('deleteContentAndPage');
 
-        $response = $this->getFrontendResponse(self::VALUE_PageId, 0, 0, 0, false);
-        $this->assertStringContainsString('PageNotFoundException', $response->getError());
+        $response = $this->executeFrontendRequest(
+            (new InternalRequest())->withPageId(self::VALUE_PageId)
+        );
+        $this->assertEquals(404, $response->getStatusCode());
     }
 
     /**