Completed
Push — master ( 64542d...27c7de )
by
unknown
12:47
created

PreviewUriBuilder::buildUriForElement()   C

Complexity

Conditions 13
Paths 169

Size

Total Lines 60
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 13
eloc 38
nc 169
nop 4
dl 0
loc 60
rs 6.0416
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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