Completed
Push — master ( 734e6b...e66cb2 )
by
unknown
17:00
created

PreviewUriBuilder::getPreviewLinkLifetime()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types = 1);
3
namespace TYPO3\CMS\Workspaces\Preview;
4
5
/*
6
 * This file is part of the TYPO3 CMS project.
7
 *
8
 * It is free software; you can redistribute it and/or modify it under
9
 * the terms of the GNU General Public License, either version 2
10
 * of the License, or any later version.
11
 *
12
 * For the full copyright and license information, please read the
13
 * LICENSE.txt file that was distributed with this source code.
14
 *
15
 * The TYPO3 project - inspiring people to share!
16
 */
17
18
use Psr\Http\Message\UriInterface;
19
use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
20
use TYPO3\CMS\Backend\Routing\UriBuilder;
21
use TYPO3\CMS\Backend\Utility\BackendUtility;
22
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23
use TYPO3\CMS\Core\Database\ConnectionPool;
24
use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
25
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
27
use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
28
use TYPO3\CMS\Core\Routing\UnableToLinkToPageException;
29
use TYPO3\CMS\Core\Site\SiteFinder;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Core\Versioning\VersionState;
32
use TYPO3\CMS\Workspaces\Service\WorkspaceService;
33
34
/**
35
 * Create links to pages when in a workspace for previewing purposes
36
 *
37
 * @internal
38
 */
39
class PreviewUriBuilder
40
{
41
    /**
42
     * @var array
43
     */
44
    protected $pageCache = [];
45
46
    /**
47
     * @var WorkspaceService
48
     */
49
    protected $workspaceService;
50
51
    /**
52
     * @var int
53
     */
54
    protected $previewLinkLifetime;
55
56
    public function __construct()
57
    {
58
        $this->workspaceService = GeneralUtility::makeInstance(WorkspaceService::class);
59
        $this->previewLinkLifetime = $this->workspaceService->getPreviewLinkLifetime();
60
    }
61
62
    /**
63
     * Generates a workspace preview link.
64
     *
65
     * @param int $uid The ID of the record to be linked
66
     * @param int $languageId the language to link to
67
     * @return string the full domain including the protocol http:// or https://, but without the trailing '/'
68
     */
69
    public function buildUriForPage(int $uid, int $languageId = 0): string
70
    {
71
        $previewKeyword = $this->compilePreviewKeyword(
72
            $this->previewLinkLifetime * 3600,
73
            $this->workspaceService->getCurrentWorkspace()
74
        );
75
76
        $siteFinder = GeneralUtility::makeInstance(SiteFinder::class);
77
        try {
78
            $site = $siteFinder->getSiteByPageId($uid);
79
            try {
80
                $language = $site->getLanguageById($languageId);
81
            } catch (\InvalidArgumentException $e) {
82
                $language = $site->getDefaultLanguage();
83
            }
84
            $uri = $site->getRouter()->generateUri($uid, ['ADMCMD_prev' => $previewKeyword, '_language' => $language], '');
85
            return (string)$uri;
86
        } catch (SiteNotFoundException | InvalidRouteArgumentsException $e) {
87
            throw new UnableToLinkToPageException('The page ' . $uid . ' had no proper connection to a site, no link could be built.', 1559794916);
88
        }
89
    }
90
91
    /**
92
     * Generate workspace preview links for all available languages of a page
93
     *
94
     * @param int $pageId
95
     * @return array
96
     */
97
    public function buildUrisForAllLanguagesOfPage(int $pageId): array
98
    {
99
        $previewLanguages = $this->getAvailableLanguages($pageId);
100
        $previewLinks = [];
101
102
        foreach ($previewLanguages as $languageUid => $language) {
103
            $previewLinks[$language] = $this->buildUriForPage($pageId, $languageUid);
104
        }
105
106
        return $previewLinks;
107
    }
108
109
    /**
110
     * Generates a workspace split-bar preview link.
111
     *
112
     * @param int $uid The ID of the record to be linked
113
     * @return UriInterface
114
     * @throws \TYPO3\CMS\Backend\Routing\Exception\RouteNotFoundException
115
     */
116
    public function buildUriForWorkspaceSplitPreview(int $uid): UriInterface
117
    {
118
        // In case a $pageUid is submitted we need to make sure it points to a live-page
119
        if ($uid > 0) {
120
            $uid = $this->getLivePageUid($uid);
121
        }
122
        $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
123
        return $uriBuilder->buildUriFromRoute(
124
            'workspace_previewcontrols',
125
            ['id' => $uid],
126
            UriBuilder::ABSOLUTE_URL
127
        );
128
    }
129
130
    /**
131
     * Generates a view Uri for an element.
132
     *
133
     * @param string $table Table to be used
134
     * @param int $uid Uid of the version(!) record
135
     * @param array $liveRecord Optional live record data
136
     * @param array $versionRecord Optional version record data
137
     * @return string
138
     */
139
    public function buildUriForElement(string $table, int $uid, array $liveRecord = null, array $versionRecord = null): string
140
    {
141
        $movePlaceholder = [];
142
        if ($table === 'pages') {
143
            return BackendUtility::viewOnClick(BackendUtility::getLiveVersionIdOfRecord('pages', $uid));
144
        }
145
146
        if ($liveRecord === null) {
147
            $liveRecord = BackendUtility::getLiveVersionOfRecord($table, $uid);
148
        }
149
        if ($versionRecord === null) {
150
            $versionRecord = BackendUtility::getRecord($table, $uid);
151
        }
152
        if (VersionState::cast($versionRecord['t3ver_state'])->equals(VersionState::MOVE_POINTER)) {
153
            $movePlaceholder = BackendUtility::getMovePlaceholder($table, $liveRecord['uid'], 'pid');
154
        }
155
156
        // Directly use pid value and consider move placeholders
157
        $previewPageId = (empty($movePlaceholder['pid']) ? $liveRecord['pid'] : $movePlaceholder['pid']);
158
        $additionalParameters = '&previewWS=' . $versionRecord['t3ver_wsid'];
159
        // Add language parameter if record is a localization
160
        if (BackendUtility::isTableLocalizable($table)) {
161
            $languageField = $GLOBALS['TCA'][$table]['ctrl']['languageField'];
162
            if ($versionRecord[$languageField] > 0) {
163
                $additionalParameters .= '&L=' . $versionRecord[$languageField];
164
            }
165
        }
166
167
        $pageTsConfig = BackendUtility::getPagesTSconfig($previewPageId);
168
        $viewUrl = '';
169
170
        // Directly use determined direct page id
171
        if ($table === 'tt_content') {
172
            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
173
        } elseif (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table]) || !empty($pageTsConfig['options.']['workspaces.']['previewPageId'])) {
174
            // Analyze Page TSconfig options.workspaces.previewPageId
175
            if (!empty($pageTsConfig['options.']['workspaces.']['previewPageId.'][$table])) {
176
                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId.'][$table];
177
            } else {
178
                $previewConfiguration = $pageTsConfig['options.']['workspaces.']['previewPageId'];
179
            }
180
            // Extract possible settings (e.g. "field:pid")
181
            [$previewKey, $previewValue] = explode(':', $previewConfiguration, 2);
182
            if ($previewKey === 'field') {
183
                $previewPageId = (int)$liveRecord[$previewValue];
184
            } else {
185
                $previewPageId = (int)$previewConfiguration;
186
            }
187
            $viewUrl = BackendUtility::viewOnClick($previewPageId, '', null, '', '', $additionalParameters);
188
        } elseif (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'])) {
189
            // Call user function to render the single record view
190
            $_params = [
191
                'table' => $table,
192
                'uid' => $uid,
193
                'record' => $liveRecord,
194
                'liveRecord' => $liveRecord,
195
                'versionRecord' => $versionRecord,
196
            ];
197
            $_funcRef = $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['workspaces']['viewSingleRecord'];
198
            $null = null;
199
            $viewUrl = GeneralUtility::callUserFunction($_funcRef, $_params, $null);
200
        }
201
202
        return $viewUrl;
203
    }
204
205
    /**
206
     * Adds an entry to the sys_preview database table and return the preview keyword.
207
     *
208
     * @param int $ttl Time-To-Live for keyword
209
     * @param int|null $workspaceId Which workspace ID to preview.
210
     * @return string Returns keyword to use in URL for ADMCMD_prev=, a 32 byte MD5 hash keyword for the URL: "?ADMCMD_prev=[keyword]
211
     */
212
    protected function compilePreviewKeyword(int $ttl = 172800, int $workspaceId = null): string
213
    {
214
        $keyword = md5(uniqid(microtime(), true));
215
        GeneralUtility::makeInstance(ConnectionPool::class)
216
            ->getConnectionForTable('sys_preview')
217
            ->insert(
218
                'sys_preview',
219
                [
220
                    'keyword' => $keyword,
221
                    'tstamp' => $GLOBALS['EXEC_TIME'],
222
                    'endtime' => $GLOBALS['EXEC_TIME'] + $ttl,
223
                    'config' => json_encode([
224
                        'fullWorkspace' => $workspaceId
225
                    ])
226
                ]
227
            );
228
229
        return $keyword;
230
    }
231
232
    /**
233
     * Find the Live-Uid for a given page,
234
     * the results are cached at run-time to avoid too many database-queries
235
     *
236
     * @throws \InvalidArgumentException
237
     * @param int $uid
238
     * @return int
239
     */
240
    protected function getLivePageUid(int $uid): int
241
    {
242
        if (!isset($this->pageCache[$uid])) {
243
            $pageRecord = BackendUtility::getRecord('pages', $uid);
244
            if (is_array($pageRecord)) {
245
                $this->pageCache[$uid] = $pageRecord['t3ver_oid'] ? (int)$pageRecord['t3ver_oid'] : $uid;
246
            } else {
247
                throw new \InvalidArgumentException('uid is supposed to point to an existing page - given value was: ' . $uid, 1290628113);
248
            }
249
        }
250
        return $this->pageCache[$uid];
251
    }
252
253
    /**
254
     * Get the available languages of a certain page, including language=0 if the user has access to it.
255
     *
256
     * @param int $pageId
257
     * @return array assoc array with the languageId as key and the languageTitle as value
258
     */
259
    protected function getAvailableLanguages(int $pageId): array
260
    {
261
        $languageOptions = [];
262
        $translationConfigurationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
263
        $systemLanguages = $translationConfigurationProvider->getSystemLanguages($pageId);
264
265
        if ($this->getBackendUser()->checkLanguageAccess(0)) {
266
            // Use configured label for default language
267
            $languageOptions[0] = $systemLanguages[0]['title'];
268
        }
269
270
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
271
            ->getQueryBuilderForTable('pages');
272
        $queryBuilder->getRestrictions()
273
            ->removeAll()
274
            ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
275
            ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
276
277
        $result = $queryBuilder->select('sys_language_uid')
278
            ->from('pages')
279
            ->where(
280
                $queryBuilder->expr()->eq(
281
                    $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
282
                    $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
283
                )
284
            )
285
            ->execute();
286
287
        while ($row = $result->fetch()) {
288
            $languageId = (int)$row['sys_language_uid'];
289
            // Only add links to active languages the user has access to
290
            if (isset($systemLanguages[$languageId]) && $this->getBackendUser()->checkLanguageAccess($languageId)) {
291
                $languageOptions[$languageId] = $systemLanguages[$languageId]['title'];
292
            }
293
        }
294
295
        return $languageOptions;
296
    }
297
298
    /**
299
     * @return BackendUserAuthentication
300
     */
301
    protected function getBackendUser(): BackendUserAuthentication
302
    {
303
        return $GLOBALS['BE_USER'];
304
    }
305
}
306