From 8028f2fd3fe4f7693df5539022a36198b173650c Mon Sep 17 00:00:00 2001
From: Benni Mack <benni@typo3.org>
Date: Fri, 17 May 2019 13:06:49 +0200
Subject: [PATCH] [!!!][TASK] Remove TCEMAIN.previewDomain

The functionality to set a custom TCEMAIN.previewDomain via
PageTS is not in use anymore as SiteHandling
is using the proper domain anyway already.

Conceptually this does not work anymore, as the base (domain + path prefix)
determines Language + Site / PageTree entry point. Setting
this to something else via TCEMAIN.previewDomain would not work
anyways, so this hack is removed, as we now can safely
determine the full (speaking) URL with Site Handling.

In addition, the method BackendUtility::getViewDomain()
is now deprecated.

Resolves: #88499
Releases: master
Change-Id: Id88c16e2e86ccce8a2e7be02af0e2a39802624c0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60773
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Susanne Moog <look@susi.dev>
Tested-by: Daniel Gorges <daniel.gorges@b13.de>
Tested-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <look@susi.dev>
Reviewed-by: Daniel Gorges <daniel.gorges@b13.de>
Reviewed-by: Benni Mack <benni@typo3.org>
---
 .../Controller/EditDocumentController.php     |  67 +++++----
 .../Classes/Utility/BackendUtility.php        | 132 ++++++------------
 .../Routing/UnableToLinkToPageException.php   |  24 ++++
 ...g-87193-DeprecatedFunctionalityRemoved.rst |   1 +
 ...tion-88499-BackendUtilitygetViewDomain.rst |  45 ++++++
 .../Php/MethodCallStaticMatcher.php           |   7 +
 .../Controller/ManagementController.php       |   2 -
 .../redirects/Classes/Service/UrlService.php  |  42 ------
 .../Templates/Management/Overview.html        |   2 +-
 .../Controller/ViewModuleController.php       |  11 --
 .../Classes/Controller/PreviewController.php  |  12 +-
 .../Classes/Hook/BackendUtilityHook.php       |   2 +-
 .../Classes/Hook/DataHandlerHook.php          |   2 +-
 .../Classes/Preview/PreviewUriBuilder.php     |  27 ++--
 14 files changed, 172 insertions(+), 204 deletions(-)
 create mode 100644 typo3/sysext/core/Classes/Routing/UnableToLinkToPageException.php
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Deprecation-88499-BackendUtilitygetViewDomain.rst
 delete mode 100644 typo3/sysext/redirects/Classes/Service/UrlService.php

diff --git a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
index ac47e439f0cb..8b9ab8f1e7c1 100644
--- a/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
+++ b/typo3/sysext/backend/Classes/Controller/EditDocumentController.php
@@ -41,6 +41,7 @@ use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
+use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
 use TYPO3\CMS\Core\Site\Entity\NullSite;
 use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Site\SiteFinder;
@@ -775,9 +776,10 @@ class EditDocumentController
         $previewPageId = $this->getPreviewPageId();
         $previewPageRootLine = BackendUtility::BEgetRootLine($previewPageId);
         $anchorSection = $this->getPreviewUrlAnchorSection();
-        $previewUrlParameters = $this->getPreviewUrlParameters($previewPageId);
 
-        return '
+        try {
+            $previewUrlParameters = $this->getPreviewUrlParameters($previewPageId);
+            return '
             if (window.opener) {
                 '
                 . BackendUtility::viewOnClick(
@@ -789,7 +791,7 @@ class EditDocumentController
                     $previewUrlParameters,
                     false
                 )
-            . '
+                . '
             } else {
             '
                 . BackendUtility::viewOnClick(
@@ -800,8 +802,11 @@ class EditDocumentController
                     $this->viewUrl,
                     $previewUrlParameters
                 )
-            . '
+                . '
             }';
+        } catch (UnableToLinkToPageException $e) {
+            return '';
+        }
     }
 
     /**
@@ -1406,35 +1411,39 @@ class EditDocumentController
                 || isset($pagesTSconfig['TCEMAIN.']['preview.'][$this->firstEl['table'] . '.']['previewPageId'])
             ) {
                 $previewPageId = $this->getPreviewPageId();
-                $previewUrl = BackendUtility::getPreviewUrl(
-                    $previewPageId,
-                    '',
-                    BackendUtility::BEgetRootLine($previewPageId),
-                    $this->getPreviewUrlAnchorSection(),
-                    $this->viewUrl,
-                    $this->getPreviewUrlParameters($previewPageId)
-                );
+                try {
+                    $previewUrl = BackendUtility::getPreviewUrl(
+                        $previewPageId,
+                        '',
+                        BackendUtility::BEgetRootLine($previewPageId),
+                        $this->getPreviewUrlAnchorSection(),
+                        $this->viewUrl,
+                        $this->getPreviewUrlParameters($previewPageId)
+                    );
+
+                    $viewButton = $buttonBar->makeLinkButton()
+                        ->setHref($previewUrl)
+                        ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
+                            'actions-view',
+                            Icon::SIZE_SMALL
+                        ))
+                        ->setShowLabelText(true)
+                        ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.viewDoc'));
+
+                    if (!$this->isSavedRecord) {
+                        if ($this->firstEl['table'] === 'pages') {
+                            $viewButton->setDataAttributes(['is-new' => '']);
+                        }
+                    }
 
-                $viewButton = $buttonBar->makeLinkButton()
-                    ->setHref($previewUrl)
-                    ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
-                        'actions-view',
-                        Icon::SIZE_SMALL
-                    ))
-                    ->setShowLabelText(true)
-                    ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:rm.viewDoc'));
-
-                if (!$this->isSavedRecord) {
-                    if ($this->firstEl['table'] === 'pages') {
-                        $viewButton->setDataAttributes(['is-new' => '']);
+                    if ($classNames !== '') {
+                        $viewButton->setClasses($classNames);
                     }
-                }
 
-                if ($classNames !== '') {
-                    $viewButton->setClasses($classNames);
+                    $buttonBar->addButton($viewButton, $position, $group);
+                } catch (UnableToLinkToPageException $e) {
+                    // Do not add any button
                 }
-
-                $buttonBar->addButton($viewButton, $position, $group);
             }
         }
     }
diff --git a/typo3/sysext/backend/Classes/Utility/BackendUtility.php b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
index 6ad482cd43f3..a94e895b2cdc 100644
--- a/typo3/sysext/backend/Classes/Utility/BackendUtility.php
+++ b/typo3/sysext/backend/Classes/Utility/BackendUtility.php
@@ -30,6 +30,7 @@ use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
 use TYPO3\CMS\Core\Database\RelationHandler;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
+use TYPO3\CMS\Core\Http\Uri;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
@@ -39,6 +40,7 @@ use TYPO3\CMS\Core\Resource\ProcessedFile;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
 use TYPO3\CMS\Core\Routing\RouterInterface;
+use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\TypoScript\Parser\TypoScriptParser;
@@ -2306,15 +2308,19 @@ class BackendUtility
         $additionalGetVars = '',
         $switchFocus = true
     ) {
-        $previewUrl = self::getPreviewUrl(
-            $pageUid,
-            $backPath,
-            $rootLine,
-            $anchorSection,
-            $alternativeUrl,
-            $additionalGetVars,
-            $switchFocus
-        );
+        try {
+            $previewUrl = self::getPreviewUrl(
+                $pageUid,
+                $backPath,
+                $rootLine,
+                $anchorSection,
+                $alternativeUrl,
+                $additionalGetVars,
+                $switchFocus
+            );
+        } catch (UnableToLinkToPageException $e) {
+            return '';
+        }
 
         $onclickCode = 'var previewWin = window.open(' . GeneralUtility::quoteJSvalue($previewUrl) . ',\'newTYPO3frontendWindow\');'
             . ($switchFocus ? 'previewWin.focus();' : '') . LF
@@ -2380,21 +2386,25 @@ class BackendUtility
             $rootLine = $rootLine ?? BackendUtility::BEgetRootLine($pageUid);
             try {
                 $site = $siteFinder->getSiteByPageId((int)$pageUid, $rootLine);
-                // Create a multi-dimensional array out of the additional get vars
-                $additionalQueryParams = [];
-                parse_str($additionalGetVars, $additionalQueryParams);
-                if (isset($additionalQueryParams['L'])) {
-                    $additionalQueryParams['_language'] = $additionalQueryParams['_language'] ?? $additionalQueryParams['L'];
-                    unset($additionalQueryParams['L']);
-                }
+            } catch (SiteNotFoundException $e) {
+                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794919);
+            }
+            // Create a multi-dimensional array out of the additional get vars
+            $additionalQueryParams = [];
+            parse_str($additionalGetVars, $additionalQueryParams);
+            if (isset($additionalQueryParams['L'])) {
+                $additionalQueryParams['_language'] = $additionalQueryParams['_language'] ?? $additionalQueryParams['L'];
+                unset($additionalQueryParams['L']);
+            }
+            try {
                 $previewUrl = (string)$site->getRouter()->generateUri(
                     $pageUid,
                     $additionalQueryParams,
                     $anchorSection,
                     RouterInterface::ABSOLUTE_URL
                 );
-            } catch (SiteNotFoundException | \InvalidArgumentException | InvalidRouteArgumentsException $e) {
-                $previewUrl = self::createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript);
+            } catch (\InvalidArgumentException | InvalidRouteArgumentsException $e) {
+                throw new UnableToLinkToPageException('The page ' . $pageUid . ' had no proper connection to a site, no link could be built.', 1559794914);
             }
         }
 
@@ -2479,62 +2489,6 @@ class BackendUtility
         return $url;
     }
 
-    /**
-     * Creates the view-on-click preview URL without any alternative URL.
-     *
-     * @param int $pageUid Page UID
-     * @param array $rootLine If rootline is supplied, the function will look for the first found domain record and use that URL instead
-     * @param string $anchorSection Optional anchor to the URL
-     * @param string $additionalGetVars Additional GET variables.
-     * @param string $viewScript The path to the script used to view the page
-     *
-     * @return string The preview URL
-     */
-    protected static function createPreviewUrl($pageUid, $rootLine, $anchorSection, $additionalGetVars, $viewScript)
-    {
-        // Look if a fixed preview language should be added:
-        $beUser = static::getBackendUserAuthentication();
-        $viewLanguageOrder = (string)($beUser->getTSConfig()['options.']['view.']['languageOrder'] ?? '');
-
-        if (!empty($viewLanguageOrder)) {
-            $suffix = '';
-            // Find allowed languages (if none, all are allowed!)
-            $allowedLanguages = null;
-            if (!$beUser->isAdmin() && $beUser->groupData['allowed_languages'] !== '') {
-                $allowedLanguages = array_flip(explode(',', $beUser->groupData['allowed_languages']));
-            }
-            // Traverse the view order, match first occurrence:
-            $languageOrder = GeneralUtility::intExplode(',', $viewLanguageOrder);
-            foreach ($languageOrder as $langUid) {
-                if (is_array($allowedLanguages) && !empty($allowedLanguages)) {
-                    // Choose if set.
-                    if (isset($allowedLanguages[$langUid])) {
-                        $suffix = '&L=' . $langUid;
-                        break;
-                    }
-                } else {
-                    // All allowed since no lang. are listed.
-                    $suffix = '&L=' . $langUid;
-                    break;
-                }
-            }
-            // Add it
-            $additionalGetVars .= $suffix;
-        }
-
-        // Check a mount point needs to be previewed
-        $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
-        $mountPointInfo = $pageRepository->getMountPointInfo($pageUid);
-
-        if ($mountPointInfo && $mountPointInfo['overlay']) {
-            $pageUid = $mountPointInfo['mount_pid'];
-            $additionalGetVars .= '&MP=' . $mountPointInfo['MPvar'];
-        }
-        $viewDomain = self::getViewDomain($pageUid, $rootLine);
-
-        return $viewDomain . $viewScript . $pageUid . $additionalGetVars . $anchorSection;
-    }
-
     /**
      * Builds the frontend view domain for a given page ID with a given root
      * line.
@@ -2542,33 +2496,31 @@ class BackendUtility
      * @param int $pageId The page ID to use, must be > 0
      * @param array|null $rootLine The root line structure to use
      * @return string The full domain including the protocol http:// or https://, but without the trailing '/'
+     * @deprecated since TYPO3 v10.0, will be removed in TYPO3 v11.0. Use PageRouter instead.
      */
     public static function getViewDomain($pageId, $rootLine = null)
     {
+        trigger_error('BackendUtility::getViewDomain() will be removed in TYPO3 v11.0. Use a Site and its PageRouter to link to a page directly', E_USER_DEPRECATED);
         $domain = rtrim(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), '/');
         if (!is_array($rootLine)) {
             $rootLine = self::BEgetRootLine($pageId);
         }
         // Checks alternate domains
         if (!empty($rootLine)) {
-            $protocol = GeneralUtility::getIndpEnv('TYPO3_SSL') ? 'https' : 'http';
-            $previewDomainConfig = self::getPagesTSconfig($pageId)['TCEMAIN.']['previewDomain'] ?? '';
-            $domainName = null;
-            if (!empty($previewDomainConfig)) {
-                if (strpos($previewDomainConfig, '://') !== false) {
-                    list($protocol, $domainName) = explode('://', $previewDomainConfig);
-                } else {
-                    $domainName = $previewDomainConfig;
+            try {
+                $site = GeneralUtility::makeInstance(SiteFinder::class)
+                    ->getSiteByPageId((int)$pageId, $rootLine);
+                $uri = $site->getBase();
+            } catch (SiteNotFoundException $e) {
+                // Just use the current domain
+                $uri = new Uri($domain);
+                // Append port number if lockSSLPort is not the standard port 443
+                $portNumber = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
+                if ($portNumber > 0 && $portNumber !== 443 && $portNumber < 65536 && $uri->getScheme() === 'https') {
+                    $uri = $uri->withPort((int)$portNumber);
                 }
             }
-            if ($domainName) {
-                $domain = $protocol . '://' . $domainName;
-            }
-            // Append port number if lockSSLPort is not the standard port 443
-            $portNumber = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['lockSSLPort'];
-            if ($portNumber > 0 && $portNumber !== 443 && $portNumber < 65536 && $protocol === 'https') {
-                $domain .= ':' . strval($portNumber);
-            }
+            return (string)$uri;
         }
         return $domain;
     }
diff --git a/typo3/sysext/core/Classes/Routing/UnableToLinkToPageException.php b/typo3/sysext/core/Classes/Routing/UnableToLinkToPageException.php
new file mode 100644
index 000000000000..622526c297ca
--- /dev/null
+++ b/typo3/sysext/core/Classes/Routing/UnableToLinkToPageException.php
@@ -0,0 +1,24 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Core\Routing;
+
+/*
+ * 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!
+ */
+
+/**
+ * Exception thrown when a link to a page (or page in a specific translation) cannot be built.
+ */
+class UnableToLinkToPageException extends \TYPO3\CMS\Core\Exception
+{
+}
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-87193-DeprecatedFunctionalityRemoved.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-87193-DeprecatedFunctionalityRemoved.rst
index de57b95396e2..86feb6016707 100644
--- a/typo3/sysext/core/Documentation/Changelog/master/Breaking-87193-DeprecatedFunctionalityRemoved.rst
+++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-87193-DeprecatedFunctionalityRemoved.rst
@@ -1239,6 +1239,7 @@ The following user TSconfig options have been dropped:
 * `RTE.proc.keepPDIVattribs`
 * `RTE.proc.dontRemoveUnknownTags_db`
 * `options.clearCache.system`
+* `TCEMAIN.previewDomain`
 
 
 The following TypoScript options have been dropped:
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Deprecation-88499-BackendUtilitygetViewDomain.rst b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-88499-BackendUtilitygetViewDomain.rst
new file mode 100644
index 000000000000..25beafa7a2b0
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Deprecation-88499-BackendUtilitygetViewDomain.rst
@@ -0,0 +1,45 @@
+.. include:: ../../Includes.txt
+
+===================================================
+Deprecation: #88499 - BackendUtility::getViewDomain
+===================================================
+
+See :issue:`88499`
+
+Description
+===========
+
+The static method :php:`TYPO3\CMS\Backend\Utility\BackendUtility::getViewDomain()` has been marked
+as deprecated, as it has been superseded by directly using the PageRouter of Site Handling.
+
+Site Handling allows to generate proper frontend preview URLs the same way as TYPO3 Core does in
+all other places,by calling the PageRouter of a Site object directly, so the workarounds are not
+necessary anymore, making this method obsolete.
+
+
+Impact
+======
+
+Calling :php:`BackendUtility::getViewDomain()` will trigger a deprecation warning.
+
+
+Affected Installations
+======================
+
+Any TYPO3 installations with custom extensions that call this method.
+
+
+Migration
+=========
+
+Substitute the method by directly detecting a site based on a given Page ID in the TYPO3 Backend.
+Call the `getRouter()` method on this Site object to create proper links to pages in TYPO3 Frontend.
+
+Example with additional GET parameters:
+
+:php:
+
+    $site = GeneralUtility::makeInstance(SiteFinder::class)->getSiteByPageId($pageId);
+    $url = $site->getRouter()->generateUri($pageId, ['type' => 13]);
+
+.. index:: PHP-API, FullyScanned
diff --git a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
index bbd9dd8ed82e..2534bdffff68 100644
--- a/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
+++ b/typo3/sysext/install/Configuration/ExtensionScanner/Php/MethodCallStaticMatcher.php
@@ -917,4 +917,11 @@ return [
             'Deprecation-88569-LocalesinitializeInFavorOfRegularSingletonInstance.rst',
         ],
     ],
+    'TYPO3\CMS\Backend\Utility\BackendUtility::getViewDomain' => [
+        'numberOfMandatoryArguments' => 1,
+        'maximumNumberOfArguments' => 2,
+        'restFiles' => [
+            'Deprecation-88499-BackendUtilitygetViewDomain.rst',
+        ],
+    ],
 ];
diff --git a/typo3/sysext/redirects/Classes/Controller/ManagementController.php b/typo3/sysext/redirects/Classes/Controller/ManagementController.php
index 4066a2d1d7e8..54b8bb4203c3 100644
--- a/typo3/sysext/redirects/Classes/Controller/ManagementController.php
+++ b/typo3/sysext/redirects/Classes/Controller/ManagementController.php
@@ -30,7 +30,6 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Redirects\Repository\Demand;
 use TYPO3\CMS\Redirects\Repository\RedirectRepository;
-use TYPO3\CMS\Redirects\Service\UrlService;
 use TYPO3Fluid\Fluid\View\ViewInterface;
 
 /**
@@ -108,7 +107,6 @@ class ManagementController
             'hosts' => $redirectRepository->findHostsOfRedirects(),
             'statusCodes' => $redirectRepository->findStatusCodesOfRedirects(),
             'demand' => $demand,
-            'defaultUrl' => GeneralUtility::makeInstance(UrlService::class)->getDefaultUrl(),
             'showHitCounter' => GeneralUtility::makeInstance(Features::class)->isFeatureEnabled('redirects.hitCount'),
             'pagination' => $this->preparePagination($demand, $count),
         ]);
diff --git a/typo3/sysext/redirects/Classes/Service/UrlService.php b/typo3/sysext/redirects/Classes/Service/UrlService.php
deleted file mode 100644
index c464796294fb..000000000000
--- a/typo3/sysext/redirects/Classes/Service/UrlService.php
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-declare(strict_types = 1);
-namespace TYPO3\CMS\Redirects\Service;
-
-/*
- * 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 TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Service for URL-related data
- *
- * @internal
- */
-class UrlService
-{
-    /**
-     * Retrieves the first valid URL
-     *
-     * @return string a URL like "http://example.org"
-     */
-    public function getDefaultUrl(): string
-    {
-        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
-        $firstPageInTree = $connection->select(['uid'], 'pages', ['pid' => 0], [], ['sorting' => 'ASC'], 1)->fetchColumn(0);
-        $url = BackendUtility::getViewDomain($firstPageInTree);
-
-        return $url;
-    }
-}
diff --git a/typo3/sysext/redirects/Resources/Private/Templates/Management/Overview.html b/typo3/sysext/redirects/Resources/Private/Templates/Management/Overview.html
index 6223fd808faf..9a47b829707a 100644
--- a/typo3/sysext/redirects/Resources/Private/Templates/Management/Overview.html
+++ b/typo3/sysext/redirects/Resources/Private/Templates/Management/Overview.html
@@ -114,7 +114,7 @@
                                         <span class="btn btn-default disabled"><core:icon identifier="empty-empty" /></span>
                                     </f:then>
                                     <f:else>
-                                        <f:link.external class="btn btn-default" uri="{f:if(condition: '{redirect.source_host} == \'*\'', then: defaultUrl, else: redirect.source_host)}{redirect.source_path}" target="_blank">
+                                        <f:link.external class="btn btn-default" uri="{f:if(condition: '{redirect.source_host} == \'*\'', then: '', else: redirect.source_host)}{redirect.source_path}" target="_blank">
                                             <core:icon identifier="actions-view-page" />
                                         </f:link.external>
                                     </f:else>
diff --git a/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php b/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php
index 07e0a9b980d7..2d17723c2f35 100644
--- a/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php
+++ b/typo3/sysext/viewpage/Classes/Controller/ViewModuleController.php
@@ -290,17 +290,6 @@ class ViewModuleController
         return $typeParameter;
     }
 
-    /**
-     * Get domain name for requested page id
-     *
-     * @param int $pageId
-     * @return string|null Domain name from TCEMAIN.previewDomain, NULL if not configured
-     */
-    protected function getDomainName(int $pageId)
-    {
-        return BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.']['previewDomain'] ?? '';
-    }
-
     /**
      * Get available presets for page id
      *
diff --git a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php
index 41d0aae90929..8d5236ffc8b4 100644
--- a/typo3/sysext/workspaces/Classes/Controller/PreviewController.php
+++ b/typo3/sysext/workspaces/Classes/Controller/PreviewController.php
@@ -24,9 +24,9 @@ use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
+use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Workspaces\Service\StagesService;
 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
@@ -121,9 +121,6 @@ class PreviewController
         // Remove the GET parameters related to the workspaces module
         unset($queryParameters['route'], $queryParameters['token'], $queryParameters['previewWS']);
 
-        // Assemble a query string from the retrieved parameters
-        $queryString = HttpUtility::buildQueryString($queryParameters, '&');
-
         // fetch the next and previous stage
         $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
@@ -163,12 +160,7 @@ class PreviewController
             $parameters['ADMCMD_prev'] = 'IGNORE';
             $wsUrl = (string)$site->getRouter()->generateUri($this->pageId, $parameters);
         } catch (SiteNotFoundException | InvalidRouteArgumentsException $e) {
-            // Base URL for frontend preview links
-            $previewBaseUrl = BackendUtility::getViewDomain($this->pageId) . '/index.php?' . $queryString;
-            if (!WorkspaceService::isNewPage($this->pageId)) {
-                $liveUrl = $previewBaseUrl . '&ADMCMD_noBeUser=1&ADMCMD_prev=IGNORE';
-            }
-            $wsUrl = $previewBaseUrl . '&ADMCMD_prev=IGNORE&ADMCMD_view=1&ADMCMD_editIcons=1';
+            throw new UnableToLinkToPageException('The page ' . $this->pageId . ' had no proper connection to a site, no link could be built.', 1559794913);
         }
 
         // Build the "list view" link to the review controller
diff --git a/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php b/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php
index d9cc7fe2f3cd..6261e8e5d6e4 100644
--- a/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php
+++ b/typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php
@@ -44,7 +44,7 @@ class BackendUtilityHook
     public function preProcess(&$pageUid, $backPath, $rootLine, $anchorSection, &$viewScript, $additionalGetVars, $switchFocus)
     {
         if ($GLOBALS['BE_USER']->workspace !== 0) {
-            $viewScript = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForWorkspaceSplitPreview((int)$pageUid, false);
+            $viewScript = (string)GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForWorkspaceSplitPreview((int)$pageUid);
             $viewScript .= $additionalGetVars ?: '';
         }
     }
diff --git a/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
index 687913b7b073..09400af9078f 100644
--- a/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
+++ b/typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
@@ -614,7 +614,7 @@ class DataHandlerHook
             }
             unset($tempEmailMessage);
 
-            $markers['###SPLITTED_PREVIEW_LINK###'] = $previewUriBuilder->buildUriForWorkspaceSplitPreview((int)$elementUid, true);
+            $markers['###SPLITTED_PREVIEW_LINK###'] = (string)$previewUriBuilder->buildUriForWorkspaceSplitPreview((int)$elementUid);
             // Hook for preprocessing of the content for formmails:
             foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/version/class.tx_version_tcemain.php']['notifyStageChange-postModifyMarkers'] ?? [] as $className) {
                 $_procObj = GeneralUtility::makeInstance($className);
diff --git a/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php b/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php
index b67f8caabfee..6209ee32e300 100644
--- a/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php
+++ b/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php
@@ -15,6 +15,7 @@ namespace TYPO3\CMS\Workspaces\Preview;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Psr\Http\Message\UriInterface;
 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
 use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
@@ -24,9 +25,9 @@ use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
+use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
 use TYPO3\CMS\Core\Site\SiteFinder;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\HttpUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
@@ -77,12 +78,7 @@ class PreviewUriBuilder
             $uri = $site->getRouter()->generateUri($uid, ['ADMCMD_prev' => $previewKeyword, '_language' => $language], '');
             return (string)$uri;
         } catch (SiteNotFoundException | InvalidRouteArgumentsException $e) {
-            $linkParams = [
-                'ADMCMD_prev' => $previewKeyword,
-                'id' => $uid,
-                'L' => $languageId
-            ];
-            return BackendUtility::getViewDomain($uid) . '/index.php?' . HttpUtility::buildQueryString($linkParams);
+            throw new UnableToLinkToPageException('The page ' . $uid . ' had no proper connection to a site, no link could be built.', 1559794916);
         }
     }
 
@@ -108,24 +104,21 @@ class PreviewUriBuilder
      * Generates a workspace split-bar preview link.
      *
      * @param int $uid The ID of the record to be linked
-     * @param bool $addDomain Parameter to decide if domain should be added to the generated link, FALSE per default
-     * @return string the preview link without the trailing '/'
+     * @return UriInterface
      * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
      */
-    public function buildUriForWorkspaceSplitPreview(int $uid, bool $addDomain = false): string
+    public function buildUriForWorkspaceSplitPreview(int $uid): UriInterface
     {
         // In case a $pageUid is submitted we need to make sure it points to a live-page
         if ($uid > 0) {
             $uid = $this->getLivePageUid($uid);
         }
         $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
-        // the actual uid will be appended directly in BackendUtility Hook
-        $viewScript = $uriBuilder->buildUriFromRoute('workspace_previewcontrols', ['id' => $uid]);
-        if ($addDomain === true) {
-            $viewScript = $uriBuilder->buildUriFromRoute('workspace_previewcontrols', ['id' => $uid]);
-            return BackendUtility::getViewDomain($uid) . 'index.php?redirect_url=' . urlencode((string)$viewScript);
-        }
-        return (string)$viewScript;
+        return $uriBuilder->buildUriFromRoute(
+            'workspace_previewcontrols',
+            ['id' => $uid],
+            UriBuilder::ABSOLUTE_URL
+        );
     }
 
     /**
-- 
GitLab