[TASK] Extract workspace preview uri generation 75/56875/4
authorBenni Mack <benni@typo3.org>
Sun, 6 May 2018 17:05:01 +0000 (19:05 +0200)
committerGeorg Ringer <georg.ringer@gmail.com>
Thu, 10 May 2018 18:10:11 +0000 (20:10 +0200)
All logic regarding generating preview links for workspaces has been
extracted into the "PreviewUriBuilder" method. Previously this was all
scattered between the WorkspaceService and the legacy "PreviewHook"
which was not used as a hook.

Separating this functionality allows to further split up the entry points
(Controllers / Handlers) and clean up concerns.

Resolves: #84940
Releases: master
Change-Id: I648fa12bab29ecb53ae319938d1086f95abfff58
Reviewed-on: https://review.typo3.org/56875
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Tested-by: Mathias Schreiber <mathias.schreiber@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
typo3/sysext/workspaces/Classes/Controller/Remote/ActionHandler.php
typo3/sysext/workspaces/Classes/Controller/ReviewController.php
typo3/sysext/workspaces/Classes/Hook/BackendUtilityHook.php
typo3/sysext/workspaces/Classes/Hook/DataHandlerHook.php
typo3/sysext/workspaces/Classes/Hook/PreviewHook.php [deleted file]
typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php [new file with mode: 0644]
typo3/sysext/workspaces/Classes/Service/GridDataService.php
typo3/sysext/workspaces/Classes/Service/WorkspaceService.php
typo3/sysext/workspaces/Migrations/Code/ClassAliasMap.php

index f701abb..97ae316 100644 (file)
@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Fluid\View\StandaloneView;
 use TYPO3\CMS\Workspaces\Domain\Record\StageRecord;
 use TYPO3\CMS\Workspaces\Domain\Record\WorkspaceRecord;
+use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
 use TYPO3\CMS\Workspaces\Service\StagesService;
 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
@@ -60,7 +61,7 @@ class ActionHandler
      */
     public function generateWorkspacePreviewLink($uid)
     {
-        return $this->workspaceService->generateWorkspacePreviewLink($uid);
+        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForPage((int)$uid);
     }
 
     /**
@@ -71,7 +72,7 @@ class ActionHandler
      */
     public function generateWorkspacePreviewLinksForAllLanguages($uid)
     {
-        return $this->workspaceService->generateWorkspacePreviewLinksForAllLanguages($uid);
+        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUrisForAllLanguagesOfPage((int)$uid);
     }
 
     /**
@@ -126,7 +127,7 @@ class ActionHandler
      */
     public function viewSingleRecord($table, $uid)
     {
-        return WorkspaceService::viewSingleRecord($table, $uid);
+        return GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, $uid);
     }
 
     /**
@@ -422,8 +423,7 @@ class ActionHandler
     public function discardStagesFromPage($pageId)
     {
         $cmdMapArray = [];
-        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
             $filter = 1,
             $stage = -99,
@@ -746,8 +746,7 @@ class ActionHandler
      */
     public function sendPageToPreviousStage($id)
     {
-        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
             $filter = 1,
             $stage = -99,
@@ -757,7 +756,7 @@ class ActionHandler
         );
         list($currentStage, $previousStage) = $this->stageService->getPreviousStageForElementCollection($workspaceItemsArray);
         // get only the relevant items for processing
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
             $filter = 1,
             $currentStage['uid'],
@@ -781,8 +780,7 @@ class ActionHandler
      */
     public function sendPageToNextStage($id)
     {
-        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
             $filter = 1,
             $stage = -99,
@@ -792,7 +790,7 @@ class ActionHandler
         );
         list($currentStage, $nextStage) = $this->stageService->getNextStageForElementCollection($workspaceItemsArray);
         // get only the relevant items for processing
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
             $this->stageService->getWorkspaceId(),
             $filter = 1,
             $currentStage['uid'],
@@ -817,19 +815,17 @@ class ActionHandler
      */
     public function updateStageChangeButtons($id)
     {
-        $stageService = GeneralUtility::makeInstance(StagesService::class);
-        $workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
         // fetch the next and previous stage
-        $workspaceItemsArray = $workspaceService->selectVersionsInWorkspace(
-            $stageService->getWorkspaceId(),
+        $workspaceItemsArray = $this->workspaceService->selectVersionsInWorkspace(
+            $this->stageService->getWorkspaceId(),
             $filter = 1,
             $stage = -99,
             $id,
             $recursionLevel = 0,
             $selectionType = 'tables_modify'
         );
-        list(, $nextStage) = $stageService->getNextStageForElementCollection($workspaceItemsArray);
-        list(, $previousStage) = $stageService->getPreviousStageForElementCollection($workspaceItemsArray);
+        list(, $nextStage) = $this->stageService->getNextStageForElementCollection($workspaceItemsArray);
+        list(, $previousStage) = $this->stageService->getPreviousStageForElementCollection($workspaceItemsArray);
 
         $view = GeneralUtility::makeInstance(StandaloneView::class);
         $extensionPath = ExtensionManagementUtility::extPath('workspaces');
index 2568ee9..aa91810 100644 (file)
@@ -23,6 +23,7 @@ use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
 use TYPO3\CMS\Workspaces\Service\AdditionalColumnService;
@@ -145,7 +146,6 @@ class ReviewController extends ActionController
         $backendUser = $this->getBackendUser();
         $moduleTemplate = $this->view->getModuleTemplate();
 
-        $wsService = GeneralUtility::makeInstance(WorkspaceService::class);
         if (GeneralUtility::_GP('id')) {
             $pageRecord = BackendUtility::getRecord('pages', GeneralUtility::_GP('id'));
             if ($pageRecord) {
@@ -153,7 +153,7 @@ class ReviewController extends ActionController
                 $this->view->assign('pageTitle', BackendUtility::getRecordTitle('pages', $pageRecord));
             }
         }
-        $wsList = $wsService->getAvailableWorkspaces();
+        $wsList = GeneralUtility::makeInstance(WorkspaceService::class)->getAvailableWorkspaces();
         $activeWorkspace = $backendUser->workspace;
         $performWorkspaceSwitch = false;
         // Only admins see multiple tabs, we decided to use it this
@@ -189,7 +189,7 @@ class ReviewController extends ActionController
             'activeWorkspaceTitle' => WorkspaceService::getWorkspaceTitle($activeWorkspace),
         ]);
 
-        if ($wsService->canCreatePreviewLink(GeneralUtility::_GP('id'), $activeWorkspace)) {
+        if ($this->canCreatePreviewLink((int)GeneralUtility::_GP('id'), (int)$activeWorkspace)) {
             $buttonBar = $moduleTemplate->getDocHeaderComponent()->getButtonBar();
             $iconFactory = $moduleTemplate->getIconFactory();
             $showButton = $buttonBar->makeLinkButton()
@@ -345,6 +345,26 @@ class ReviewController extends ActionController
     }
 
     /**
+     * Determine whether this page for the current
+     *
+     * @param int $pageUid
+     * @param int $workspaceUid
+     * @return bool
+     */
+    protected function canCreatePreviewLink(int $pageUid, int $workspaceUid): bool
+    {
+        if ($pageUid > 0 && $workspaceUid > 0) {
+            $pageRecord = BackendUtility::getRecord('pages', $pageUid);
+            BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
+            if (VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Gets the selected language.
      *
      * @return string
index fde0aa9..1b3e0ee 100644 (file)
@@ -19,8 +19,8 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Messaging\FlashMessage;
 use TYPO3\CMS\Core\Messaging\FlashMessageService;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
 use TYPO3\CMS\Workspaces\Service\StagesService;
-use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
 /**
  * Befunc service
@@ -42,7 +42,7 @@ class BackendUtilityHook
     public function preProcess(&$pageUid, $backPath, $rootLine, $anchorSection, &$viewScript, $additionalGetVars, $switchFocus)
     {
         if ($GLOBALS['BE_USER']->workspace !== 0) {
-            $viewScript = GeneralUtility::makeInstance(WorkspaceService::class)->generateWorkspaceSplittedPreviewLink($pageUid);
+            $viewScript = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForWorkspaceSplitPreview((int)$pageUid, false);
         }
     }
 
index 23a1893..87637d3 100644 (file)
@@ -33,6 +33,7 @@ use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
 use TYPO3\CMS\Workspaces\DataHandler\CommandMap;
+use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
 use TYPO3\CMS\Workspaces\Service\StagesService;
 use TYPO3\CMS\Workspaces\Service\WorkspaceService;
 
@@ -565,6 +566,7 @@ class DataHandlerHook
         }
         // prepare and then send the emails
         if (!empty($emails)) {
+            $previewUriBuilder = GeneralUtility::makeInstance(PreviewUriBuilder::class);
             // Path to record is found:
             list($elementTable, $elementUid) = explode(':', $elementName);
             $elementUid = (int)$elementUid;
@@ -606,10 +608,11 @@ class DataHandlerHook
                 $tempEmailMessage = $emailConfig['message'];
             }
             if (strpos($tempEmailMessage, '###PREVIEW_LINK###') !== false) {
-                $markers['###PREVIEW_LINK###'] = $this->workspaceService->generateWorkspacePreviewLink($elementUid);
+                $markers['###PREVIEW_LINK###'] = $previewUriBuilder->buildUriForPage((int)$elementUid);
             }
             unset($tempEmailMessage);
-            $markers['###SPLITTED_PREVIEW_LINK###'] = $this->workspaceService->generateWorkspaceSplittedPreviewLink($elementUid, true);
+
+            $markers['###SPLITTED_PREVIEW_LINK###'] = $previewUriBuilder->buildUriForWorkspaceSplitPreview((int)$elementUid, true);
             // 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/Hook/PreviewHook.php b/typo3/sysext/workspaces/Classes/Hook/PreviewHook.php
deleted file mode 100644 (file)
index 70a4505..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?php
-namespace TYPO3\CMS\Workspaces\Hook;
-
-/*
- * 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\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Hook for checking if the preview mode is activated
- * preview mode = show a page of a workspace without having to log in
- */
-class PreviewHook
-{
-    /**
-     * Set preview keyword, eg:
-     * $previewUrl = GeneralUtility::getIndpEnv('TYPO3_SITE_URL').'index.php?ADMCMD_prev='.$this->compilePreviewKeyword($GLOBALS['BE_USER']->user['uid'], 120);
-     *
-     * @todo for sys_preview:
-     * - Add a comment which can be shown to previewer in frontend in some way (plus maybe ability to write back, take other action?)
-     * - Add possibility for the preview keyword to work in the backend as well: So it becomes a quick way to a certain action of sorts?
-     *
-     * @param string $backendUserUid 32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]
-     * @param int $ttl Time-To-Live for keyword
-     * @param int|null $fullWorkspace Which workspace ID to preview.
-     * @return string Returns keyword to use in URL for ADMCMD_prev=
-     */
-    public function compilePreviewKeyword($backendUserUid, $ttl = 172800, $fullWorkspace = null)
-    {
-        $fieldData = [
-            'keyword' => md5(uniqid(microtime(), true)),
-            'tstamp' => $GLOBALS['EXEC_TIME'],
-            'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
-            'config' => json_encode([
-                'fullWorkspace' => $fullWorkspace,
-                'BEUSER_uid' => $backendUserUid
-            ])
-        ];
-        GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getConnectionForTable('sys_preview')
-            ->insert(
-                'sys_preview',
-                $fieldData
-            );
-
-        return $fieldData['keyword'];
-    }
-
-    /**
-     * easy function to just return the number of hours
-     * a preview link is valid, based on the TSconfig value "options.workspaces.previewLinkTTLHours"
-     * by default, it's 48hs
-     *
-     * @return int The hours as a number
-     */
-    public function getPreviewLinkLifetime()
-    {
-        $ttlHours = (int)$GLOBALS['BE_USER']->getTSConfigVal('options.workspaces.previewLinkTTLHours');
-        return $ttlHours ? $ttlHours : 24 * 2;
-    }
-}
diff --git a/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php b/typo3/sysext/workspaces/Classes/Preview/PreviewUriBuilder.php
new file mode 100644 (file)
index 0000000..e7b141a
--- /dev/null
@@ -0,0 +1,301 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Workspaces\Preview;
+
+/*
+ * 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\Configuration\TranslationConfigurationProvider;
+use TYPO3\CMS\Backend\Routing\UriBuilder;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
+use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Versioning\VersionState;
+use TYPO3\CMS\Workspaces\Service\WorkspaceService;
+
+/**
+ * Create links to pages when in a workspace for previewing purposes
+ */
+class PreviewUriBuilder
+{
+    /**
+     * @var array
+     */
+    protected $pageCache = [];
+
+    /**
+     * @var WorkspaceService
+     */
+    protected $workspaceService;
+
+    public function __construct()
+    {
+        $this->workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
+    }
+
+    /**
+     * Generates a workspace preview link.
+     *
+     * @param int $uid The ID of the record to be linked
+     * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
+     */
+    public function buildUriForPage(int $uid): string
+    {
+        $previewKeyword = $this->compilePreviewKeyword(
+            $this->getBackendUser()->user['uid'],
+            $this->getPreviewLinkLifetime() * 3600,
+            $this->workspaceService->getCurrentWorkspace()
+        );
+
+        $linkParams = [
+            'ADMCMD_prev' => $previewKeyword,
+            'id' => $uid
+        ];
+        return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams);
+    }
+
+    /**
+     * Generate workspace preview links for all available languages of a page
+     *
+     * @param int $pageId
+     * @return array
+     */
+    public function buildUrisForAllLanguagesOfPage(int $pageId): array
+    {
+        $previewUrl = $this->buildUriForPage($pageId);
+        $previewLanguages = $this->getAvailableLanguages($pageId);
+        $previewLinks = [];
+
+        foreach ($previewLanguages as $languageUid => $language) {
+            $previewLinks[$language] = $previewUrl . '&L=' . $languageUid;
+        }
+
+        return $previewLinks;
+    }
+
+    /**
+     * 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 '/'
+     */
+    public function buildUriForWorkspaceSplitPreview(int $uid, bool $addDomain = false): string
+    {
+        // 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' => '']);
+        if ($addDomain === true) {
+            $viewScript = $uriBuilder->buildUriFromRoute('workspace_previewcontrols', ['id' => $uid]);
+            return BackendUtility::getViewDomain($uid) . 'index.php?redirect_url=' . urlencode($viewScript);
+        }
+        return (string)$viewScript;
+    }
+
+    /**
+     * Generates a view Uri for a element.
+     *
+     * @param string $table Table to be used
+     * @param int $uid Uid of the version(!) record
+     * @param array $liveRecord Optional live record data
+     * @param array $versionRecord Optional version record data
+     * @return string
+     */
+    public function buildUriForElement(string $table, int $uid, array $liveRecord = null, array $versionRecord = null): string
+    {
+        if ($table === 'pages') {
+            return BackendUtility::viewOnClick(BackendUtility::getLiveVersionIdOfRecord('pages', $uid));
+        }
+
+        if ($liveRecord === null) {
+            $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
+        }
+        if ($versionRecord === null) {
+            $versionRecord = BackendUtility::getRecord($table, $uid);
+        }
+        if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
+            $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
+        }
+
+        // Directly use pid value and consider move placeholders
+        $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
+        $additionalParameters = '&previewWS=' . $versionRecord['t3ver_wsid'];
+        // Add language parameter if record is a localization
+        if (BackendUtility::isTableLocalizable($table)) {
+            $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
+            if ($versionRecord[$languageField] > 0) {
+                $additionalParameters .= '&L=' . $versionRecord[$languageField];
+            }
+        }
+
+        $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
+        $viewUrl = '';
+
+        // Directly use determined direct page id
+        if ($table === 'tt_content') {
+            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
+        } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
+            // Analyze Page TSconfig options.workspaces.previewPageId
+            if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
+                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
+            } else {
+                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
+            }
+            // Extract possible settings (e.g. "field:pid")
+            list($previewKey, $previewValue) = explode(':', $previewConfiguration, 2);
+            if ($previewKey === 'field') {
+                $previewPageId = (int)$liveRecord[$previewValue];
+            } else {
+                $previewPageId = (int)$previewConfiguration;
+            }
+            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
+        } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
+            // Call user function to render the single record view
+            $_params = [
+                'table' => $table,
+                'uid' => $uid,
+                'record' => $liveRecord,
+                'liveRecord' => $liveRecord,
+                'versionRecord' => $versionRecord,
+            ];
+            $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
+            $null = null;
+            $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
+        }
+
+        return $viewUrl;
+    }
+
+    /**
+     * Adds an entry to the sys_preview database table and return the preview keyword.
+     *
+     * @param int $backendUserUid the user ID who created the preview link
+     * @param int $ttl Time-To-Live for keyword
+     * @param int|null $workspaceId Which workspace ID to preview.
+     * @return string Returns keyword to use in URL for ADMCMD_prev=, a 32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]
+     */
+    protected function compilePreviewKeyword(int $backendUserUid, int $ttl = 172800, int $workspaceId = null): string
+    {
+        $keyword = md5(uniqid(microtime(), true));
+        GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('sys_preview')
+            ->insert(
+                'sys_preview',
+                [
+                    'keyword' => $keyword,
+                    'tstamp' => $GLOBALS['EXEC_TIME'],
+                    'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
+                    'config' => json_encode([
+                        'fullWorkspace' => $workspaceId,
+                        'BEUSER_uid' => $backendUserUid
+                    ])
+                ]
+            );
+
+        return $keyword;
+    }
+
+    /**
+     * easy function to just return the number of hours
+     * a preview link is valid, based on the TSconfig value "options.workspaces.previewLinkTTLHours"
+     * by default, it's 48hs
+     *
+     * @return int The hours as a number
+     */
+    protected function getPreviewLinkLifetime(): int
+    {
+        $ttlHours = (int)$this->getBackendUser()->getTSConfigVal('options.workspaces.previewLinkTTLHours');
+        return $ttlHours ?: 24 * 2;
+    }
+
+    /**
+     * Find the Live-Uid for a given page,
+     * the results are cached at run-time to avoid too many database-queries
+     *
+     * @throws \InvalidArgumentException
+     * @param int $uid
+     * @return int
+     */
+    protected function getLivePageUid(int $uid): int
+    {
+        if (!isset($this->pageCache[$uid])) {
+            $pageRecord = BackendUtility::getRecord('pages', $uid);
+            if (is_array($pageRecord)) {
+                $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
+            } else {
+                throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was: ' . $uid, 1290628113);
+            }
+        }
+        return $this->pageCache[$uid];
+    }
+
+    /**
+     * Get the available languages of a certain page, including language=0 if the user has access to it.
+     *
+     * @param int $pageId
+     * @return array assoc array with the languageId as key and the languageTitle as value
+     */
+    protected function getAvailableLanguages(int $pageId): array
+    {
+        $languageOptions = [];
+        $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
+        $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
+
+        if ($this->getBackendUser()->checkLanguageAccess(0)) {
+            // Use configured label for default language
+            $languageOptions[0] = $systemLanguages[0]['title'];
+        }
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getQueryBuilderForTable('pages');
+        $queryBuilder->getRestrictions()
+            ->removeAll()
+            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
+            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
+
+        $result = $queryBuilder->select('sys_language_uid')
+            ->from('pages')
+            ->where(
+                $queryBuilder->expr()->eq(
+                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
+                    $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
+                )
+            )
+            ->execute();
+
+        while ($row = $result->fetch()) {
+            $languageId = (int)$row['sys_language_uid'];
+            // Only add links to active languages the user has access to
+            if (isset($systemLanguages[$languageId]) && $this->getBackendUser()->checkLanguageAccess($languageId)) {
+                $languageOptions[$languageId] = $systemLanguages[$languageId]['title'];
+            }
+        }
+
+        return $languageOptions;
+    }
+
+    /**
+     * @return BackendUserAuthentication
+     */
+    protected function getBackendUser(): BackendUserAuthentication
+    {
+        return $GLOBALS['BE_USER'];
+    }
+}
index 4e519da..96d4691 100644 (file)
@@ -25,6 +25,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Object\ObjectManager;
 use TYPO3\CMS\Extbase\SignalSlot\Dispatcher;
 use TYPO3\CMS\Workspaces\Domain\Model\CombinedRecord;
+use TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder;
 
 /**
  * Grid data service
@@ -157,7 +158,7 @@ class GridDataService implements LoggerAwareInterface
                     }
 
                     $isDeletedPage = $table === 'pages' && $recordState === 'deleted';
-                    $viewUrl = WorkspaceService::viewSingleRecord($table, $record['uid'], $origRecord, $versionRecord);
+                    $viewUrl = GeneralUtility::makeInstance(PreviewUriBuilder::class)->buildUriForElement($table, $record['uid'], $origRecord, $versionRecord);
                     $versionArray = [];
                     $versionArray['table'] = $table;
                     $versionArray['id'] = $table . ':' . $record['uid'];
index 4bc0327..6ec21a4 100644 (file)
@@ -14,13 +14,10 @@ namespace TYPO3\CMS\Workspaces\Service;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
-use TYPO3\CMS\Backend\Routing\UriBuilder;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Database\Connection;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
 use TYPO3\CMS\Core\Database\Query\Restriction\RootLevelRestriction;
 use TYPO3\CMS\Core\Database\QueryView;
@@ -29,7 +26,6 @@ use TYPO3\CMS\Core\Type\Bitmask\Permission;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
 use TYPO3\CMS\Core\Versioning\VersionState;
-use TYPO3\CMS\Workspaces\Hook\PreviewHook;
 
 /**
  * Workspace service
@@ -39,11 +35,6 @@ class WorkspaceService implements SingletonInterface
     /**
      * @var array
      */
-    protected $pageCache = [];
-
-    /**
-     * @var array
-     */
     protected $versionsOnPageCache = [];
 
     /**
@@ -54,11 +45,12 @@ class WorkspaceService implements SingletonInterface
     const TABLE_WORKSPACE = 'sys_workspace';
     const SELECT_ALL_WORKSPACES = -98;
     const LIVE_WORKSPACE_ID = 0;
+
     /**
      * retrieves the available workspaces from the database and checks whether
      * they're available to the current BE user
      *
-     * @return array array of worspaces available to the current user
+     * @return array array of workspaces available to the current user
      */
     public function getAvailableWorkspaces()
     {
@@ -732,183 +724,6 @@ class WorkspaceService implements SingletonInterface
     }
 
     /**
-     * Generates a view link for a page.
-     *
-     * @param string $table Table to be used
-     * @param int $uid Uid of the version(!) record
-     * @param array $liveRecord Optional live record data
-     * @param array $versionRecord Optional version record data
-     * @return string
-     */
-    public static function viewSingleRecord($table, $uid, array $liveRecord = null, array $versionRecord = null)
-    {
-        if ($table === 'pages') {
-            return BackendUtility::viewOnClick(BackendUtility::getLiveVersionIdOfRecord('pages', $uid));
-        }
-
-        if ($liveRecord === null) {
-            $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
-        }
-        if ($versionRecord === null) {
-            $versionRecord = BackendUtility::getRecord($table, $uid);
-        }
-        if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
-            $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
-        }
-
-        // Directly use pid value and consider move placeholders
-        $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
-        $additionalParameters = '&previewWS=' . $versionRecord['t3ver_wsid'];
-        // Add language parameter if record is a localization
-        if (BackendUtility::isTableLocalizable($table)) {
-            $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
-            if ($versionRecord[$languageField] > 0) {
-                $additionalParameters .= '&L=' . $versionRecord[$languageField];
-            }
-        }
-
-        $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
-        $viewUrl = '';
-
-        // Directly use determined direct page id
-        if ($table === 'tt_content') {
-            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
-        } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
-            // Analyze Page TSconfig options.workspaces.previewPageId
-            if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
-                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
-            } else {
-                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
-            }
-            // Extract possible settings (e.g. "field:pid")
-            list($previewKey, $previewValue) = explode(':', $previewConfiguration, 2);
-            if ($previewKey === 'field') {
-                $previewPageId = (int)$liveRecord[$previewValue];
-            } else {
-                $previewPageId = (int)$previewConfiguration;
-            }
-            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
-        } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
-            // Call user function to render the single record view
-            $_params = [
-                'table' => $table,
-                'uid' => $uid,
-                'record' => $liveRecord,
-                'liveRecord' => $liveRecord,
-                'versionRecord' => $versionRecord,
-            ];
-            $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
-            $null = null;
-            $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
-        }
-
-        return $viewUrl;
-    }
-
-    /**
-     * Determine whether this page for the current
-     *
-     * @param int $pageUid
-     * @param int $workspaceUid
-     * @return bool
-     */
-    public function canCreatePreviewLink($pageUid, $workspaceUid)
-    {
-        $result = true;
-        if ($pageUid > 0 && $workspaceUid > 0) {
-            $pageRecord = BackendUtility::getRecord('pages', $pageUid);
-            BackendUtility::workspaceOL('pages', $pageRecord, $workspaceUid);
-            if (VersionState::cast($pageRecord['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
-                $result = false;
-            }
-        } else {
-            $result = false;
-        }
-        return $result;
-    }
-
-    /**
-     * Generates a workspace preview link.
-     *
-     * @param int $uid The ID of the record to be linked
-     * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
-     */
-    public function generateWorkspacePreviewLink($uid)
-    {
-        $previewObject = GeneralUtility::makeInstance(PreviewHook::class);
-        $timeToLiveHours = $previewObject->getPreviewLinkLifetime();
-        $previewKeyword = $previewObject->compilePreviewKeyword($GLOBALS['BE_USER']->user['uid'], $timeToLiveHours * 3600, $this->getCurrentWorkspace());
-        $linkParams = [
-            'ADMCMD_prev' => $previewKeyword,
-            'id' => $uid
-        ];
-        return BackendUtility::getViewDomain($uid) . '/index.php?' . GeneralUtility::implodeArrayForUrl('', $linkParams);
-    }
-
-    /**
-     * Generates a workspace splitted 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 '/'
-     */
-    public function generateWorkspaceSplittedPreviewLink($uid, $addDomain = false)
-    {
-        // 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' => '']);
-        if ($addDomain === true) {
-            $viewScript = $uriBuilder->buildUriFromRoute('workspace_previewcontrols', ['id' => $uid]);
-            return BackendUtility::getViewDomain($uid) . 'index.php?redirect_url=' . urlencode($viewScript);
-        }
-        return $viewScript;
-    }
-
-    /**
-     * Generate workspace preview links for all available languages of a page
-     *
-     * @param int $uid
-     * @return array
-     */
-    public function generateWorkspacePreviewLinksForAllLanguages($uid)
-    {
-        $previewUrl = $this->generateWorkspacePreviewLink($uid);
-        $previewLanguages = $this->getAvailableLanguages($uid);
-        $previewLinks = [];
-
-        foreach ($previewLanguages as $languageUid => $language) {
-            $previewLinks[$language] = $previewUrl . '&L=' . $languageUid;
-        }
-
-        return $previewLinks;
-    }
-
-    /**
-     * Find the Live-Uid for a given page,
-     * the results are cached at run-time to avoid too many database-queries
-     *
-     * @throws \InvalidArgumentException
-     * @param int $uid
-     * @return int
-     */
-    public function getLivePageUid($uid)
-    {
-        if (!isset($this->pageCache[$uid])) {
-            $pageRecord = BackendUtility::getRecord('pages', $uid);
-            if (is_array($pageRecord)) {
-                $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? $pageRecord['t3ver_oid'] : $uid;
-            } else {
-                throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was: ' . $uid, 1290628113);
-            }
-        }
-        return $this->pageCache[$uid];
-    }
-
-    /**
      * Determines whether a page has workspace versions.
      *
      * @param int $workspaceId
@@ -1103,49 +918,4 @@ class WorkspaceService implements SingletonInterface
             ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
         return $queryBuilder;
     }
-
-    /**
-     * Get the available languages of a certain page
-     *
-     * @param int $pageId
-     * @return array
-     */
-    public function getAvailableLanguages($pageId)
-    {
-        $languageOptions = [];
-        $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
-        $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
-
-        if ($GLOBALS['BE_USER']->checkLanguageAccess(0)) {
-            // Use configured label for default language
-            $languageOptions[0] = $systemLanguages[0]['title'];
-        }
-
-        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
-            ->getQueryBuilderForTable('pages');
-        $queryBuilder->getRestrictions()
-            ->removeAll()
-            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
-            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
-
-        $result = $queryBuilder->select('sys_language_uid')
-            ->from('pages')
-            ->where(
-                $queryBuilder->expr()->eq(
-                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
-                    $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
-                )
-            )
-            ->execute();
-
-        while ($row = $result->fetch()) {
-            $languageId = (int)$row['sys_language_uid'];
-            // Only add links to active languages the user has access to
-            if (isset($systemLanguages[$languageId]) && $GLOBALS['BE_USER']->checkLanguageAccess($languageId)) {
-                $languageOptions[$languageId] = $systemLanguages[$languageId]['title'];
-            }
-        }
-
-        return $languageOptions;
-    }
 }
index 0394c52..60fa1c8 100644 (file)
@@ -9,7 +9,7 @@ return [
     'TYPO3\\CMS\\Version\\Dependency\\EventCallback' => \TYPO3\CMS\Workspaces\Dependency\EventCallback::class,
     'TYPO3\\CMS\\Version\\Dependency\\ReferenceEntity' => \TYPO3\CMS\Workspaces\Dependency\ReferenceEntity::class,
     'TYPO3\\CMS\\Version\\Hook\\DataHandlerHook' => \TYPO3\CMS\Workspaces\Hook\DataHandlerHook::class,
-    'TYPO3\\CMS\\Version\\Hook\\PreviewHook' => \TYPO3\CMS\Workspaces\Hook\PreviewHook::class,
+    'TYPO3\\CMS\\Version\\Hook\\PreviewHook' => \TYPO3\CMS\Workspaces\Preview\PreviewUriBuilder::class,
     'TYPO3\\CMS\\Version\\Task\\AutoPublishTask' => \TYPO3\CMS\Workspaces\Task\AutoPublishTask::class,
     'TYPO3\\CMS\\Version\\Utility\\WorkspacesUtility' => \TYPO3\CMS\Workspaces\Service\WorkspaceService::class,
 ];