[BUGFIX] Ensure most site related exceptions are handled
[Packages/TYPO3.CMS.git] / typo3 / sysext / viewpage / Classes / Controller / ViewModuleController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Viewpage\Controller;
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\ResponseInterface;
19 use Psr\Http\Message\ServerRequestInterface;
20 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
24 use TYPO3\CMS\Core\Database\ConnectionPool;
25 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
27 use TYPO3\CMS\Core\Exception\SiteNotFoundException;
28 use TYPO3\CMS\Core\Http\HtmlResponse;
29 use TYPO3\CMS\Core\Imaging\Icon;
30 use TYPO3\CMS\Core\Imaging\IconFactory;
31 use TYPO3\CMS\Core\Localization\LanguageService;
32 use TYPO3\CMS\Core\Page\PageRenderer;
33 use TYPO3\CMS\Core\Routing\InvalidRouteArgumentsException;
34 use TYPO3\CMS\Core\Routing\SiteMatcher;
35 use TYPO3\CMS\Core\Site\Entity\Site;
36 use TYPO3\CMS\Core\Type\Bitmask\Permission;
37 use TYPO3\CMS\Core\Utility\GeneralUtility;
38 use TYPO3\CMS\Extbase\Mvc\View\ViewInterface;
39 use TYPO3\CMS\Fluid\View\StandaloneView;
40 use TYPO3\CMS\Frontend\Page\PageRepository;
41
42 /**
43 * Controller for viewing the frontend
44 * @internal This is a specific Backend Controller implementation and is not considered part of the Public TYPO3 API.
45 */
46 class ViewModuleController
47 {
48 /**
49 * ModuleTemplate object
50 *
51 * @var ModuleTemplate
52 */
53 protected $moduleTemplate;
54
55 /**
56 * View
57 *
58 * @var ViewInterface
59 */
60 protected $view;
61
62 /**
63 * Initialize module template and language service
64 */
65 public function __construct()
66 {
67 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
68 $this->getLanguageService()->includeLLFile('EXT:viewpage/Resources/Private/Language/locallang.xlf');
69 $pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
70 $pageRenderer->addInlineLanguageLabelFile('EXT:viewpage/Resources/Private/Language/locallang.xlf');
71 }
72
73 /**
74 * Initialize view
75 *
76 * @param string $templateName
77 */
78 protected function initializeView(string $templateName)
79 {
80 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
81 $this->view->getRequest()->setControllerExtensionName('Viewpage');
82 $this->view->setTemplate($templateName);
83 $this->view->setTemplateRootPaths(['EXT:viewpage/Resources/Private/Templates/ViewModule']);
84 $this->view->setPartialRootPaths(['EXT:viewpage/Resources/Private/Partials']);
85 $this->view->setLayoutRootPaths(['EXT:viewpage/Resources/Private/Layouts']);
86 }
87
88 /**
89 * Registers the docheader
90 *
91 * @param int $pageId
92 * @param int $languageId
93 * @param string $targetUrl
94 */
95 protected function registerDocHeader(int $pageId, int $languageId, string $targetUrl)
96 {
97 $languages = $this->getPreviewLanguages($pageId);
98 if (count($languages) > 1) {
99 $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
100 $languageMenu->setIdentifier('_langSelector');
101 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
102 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
103 foreach ($languages as $value => $label) {
104 $href = (string)$uriBuilder->buildUriFromRoute(
105 'web_ViewpageView',
106 [
107 'id' => $pageId,
108 'language' => (int)$value
109 ]
110 );
111 $menuItem = $languageMenu->makeMenuItem()
112 ->setTitle($label)
113 ->setHref($href);
114 if ($languageId === (int)$value) {
115 $menuItem->setActive(true);
116 }
117 $languageMenu->addMenuItem($menuItem);
118 }
119 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
120 }
121
122 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
123 $showButton = $buttonBar->makeLinkButton()
124 ->setHref($targetUrl)
125 ->setOnClick('window.open(this.href, \'newTYPO3frontendWindow\').focus();return false;')
126 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
127 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-page', Icon::SIZE_SMALL));
128 $buttonBar->addButton($showButton);
129
130 $refreshButton = $buttonBar->makeLinkButton()
131 ->setHref('javascript:document.getElementById(\'tx_viewpage_iframe\').contentWindow.location.reload(true);')
132 ->setTitle($this->getLanguageService()->sL('LLL:EXT:viewpage/Resources/Private/Language/locallang.xlf:refreshPage'))
133 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-refresh', Icon::SIZE_SMALL));
134 $buttonBar->addButton($refreshButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
135
136 // Shortcut
137 $mayMakeShortcut = $this->getBackendUser()->mayMakeShortcut();
138 if ($mayMakeShortcut) {
139 $getVars = ['id', 'route'];
140
141 $shortcutButton = $buttonBar->makeShortcutButton()
142 ->setModuleName('web_ViewpageView')
143 ->setGetVariables($getVars);
144 $buttonBar->addButton($shortcutButton, ButtonBar::BUTTON_POSITION_RIGHT);
145 }
146 }
147
148 /**
149 * Show selected page from pagetree in iframe
150 *
151 * @param ServerRequestInterface $request
152 * @return ResponseInterface
153 */
154 public function showAction(ServerRequestInterface $request): ResponseInterface
155 {
156 $pageId = (int)($request->getParsedBody()['id'] ?? $request->getQueryParams()['id'] ?? 0);
157 $languageId = $this->getCurrentLanguage($pageId, $request->getParsedBody()['language'] ?? $request->getQueryParams()['language'] ?? null);
158
159 $this->initializeView('show');
160
161 $targetUrl = $this->getTargetUrl($pageId, $languageId);
162 $this->registerDocHeader($pageId, $languageId, $targetUrl);
163
164 $this->moduleTemplate->setBodyTag('<body class="typo3-module-viewpage">');
165 $this->moduleTemplate->setModuleName('typo3-module-viewpage');
166 $this->moduleTemplate->setModuleId('typo3-module-viewpage');
167
168 $iconFactory = GeneralUtility::makeInstance(IconFactory::class);
169 $icons = [];
170 $icons['orientation'] = $iconFactory->getIcon('actions-device-orientation-change', Icon::SIZE_SMALL)->render('inline');
171 $icons['fullscreen'] = $iconFactory->getIcon('actions-fullscreen', Icon::SIZE_SMALL)->render('inline');
172 $icons['expand'] = $iconFactory->getIcon('actions-expand', Icon::SIZE_SMALL)->render('inline');
173 $icons['desktop'] = $iconFactory->getIcon('actions-device-desktop', Icon::SIZE_SMALL)->render('inline');
174 $icons['tablet'] = $iconFactory->getIcon('actions-device-tablet', Icon::SIZE_SMALL)->render('inline');
175 $icons['mobile'] = $iconFactory->getIcon('actions-device-mobile', Icon::SIZE_SMALL)->render('inline');
176 $icons['unidentified'] = $iconFactory->getIcon('actions-device-unidentified', Icon::SIZE_SMALL)->render('inline');
177
178 $current = ($this->getBackendUser()->uc['moduleData']['web_view']['States']['current'] ?: []);
179 $current['label'] = ($current['label'] ?? $this->getLanguageService()->sL('LLL:EXT:viewpage/Resources/Private/Language/locallang.xlf:custom'));
180 $current['width'] = (isset($current['width']) && (int)$current['width'] >= 300 ? (int)$current['width'] : 320);
181 $current['height'] = (isset($current['height']) && (int)$current['height'] >= 300 ? (int)$current['height'] : 480);
182
183 $custom = ($this->getBackendUser()->uc['moduleData']['web_view']['States']['custom'] ?: []);
184 $custom['width'] = (isset($current['custom']) && (int)$current['custom'] >= 300 ? (int)$current['custom'] : 320);
185 $custom['height'] = (isset($current['custom']) && (int)$current['custom'] >= 300 ? (int)$current['custom'] : 480);
186
187 $this->view->assign('icons', $icons);
188 $this->view->assign('current', $current);
189 $this->view->assign('custom', $custom);
190 $this->view->assign('presetGroups', $this->getPreviewPresets($pageId));
191 $this->view->assign('url', $targetUrl);
192
193 $this->moduleTemplate->setContent($this->view->render());
194 return new HtmlResponse($this->moduleTemplate->renderContent());
195 }
196
197 /**
198 * Determine the url to view
199 *
200 * @param int $pageId
201 * @param int $languageId
202 * @return string
203 */
204 protected function getTargetUrl(int $pageId, int $languageId): string
205 {
206 $permissionClause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
207 $pageRecord = BackendUtility::readPageAccess($pageId, $permissionClause);
208 if ($pageRecord) {
209 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($pageRecord);
210 $rootLine = BackendUtility::BEgetRootLine($pageId);
211 // Mount point overlay: Set new target page id and mp parameter
212 $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
213 $additionalGetVars = $this->getAdminCommand($pageId);
214 $siteMatcher = GeneralUtility::makeInstance(SiteMatcher::class);
215 try {
216 $site = $siteMatcher->matchByPageId($pageId, $rootLine);
217 } catch (SiteNotFoundException $e) {
218 $site = null;
219 }
220 $finalPageIdToShow = $pageId;
221 $mountPointInformation = $pageRepository->getMountPointInfo($pageId);
222 if ($mountPointInformation && $mountPointInformation['overlay']) {
223 // New page id
224 $finalPageIdToShow = $mountPointInformation['mount_pid'];
225 $additionalGetVars .= '&MP=' . $mountPointInformation['MPvar'];
226 }
227 $additionalGetVars .= $this->getTypeParameterIfSet($finalPageIdToShow);
228 if ($site instanceof Site) {
229 $additionalQueryParams = [];
230 parse_str($additionalGetVars, $additionalQueryParams);
231 $additionalQueryParams['_language'] = $site->getLanguageById($languageId);
232 try {
233 $uri = (string)$site->getRouter()->generateUri($finalPageIdToShow, $additionalQueryParams);
234 } catch (InvalidRouteArgumentsException $e) {
235 return '#';
236 }
237 } else {
238 $uri = BackendUtility::getPreviewUrl($finalPageIdToShow, '', $rootLine, '', '', $additionalGetVars);
239 }
240 return $uri;
241 }
242 return '#';
243 }
244
245 /**
246 * Get admin command
247 *
248 * @param int $pageId
249 * @return string
250 */
251 protected function getAdminCommand(int $pageId): string
252 {
253 // The page will show only if there is a valid page and if this page may be viewed by the user
254 $pageinfo = BackendUtility::readPageAccess($pageId, $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW));
255 $addCommand = '';
256 if (is_array($pageinfo)) {
257 $addCommand = '&ADMCMD_editIcons=1' . BackendUtility::ADMCMD_previewCmds($pageinfo);
258 }
259 return $addCommand;
260 }
261
262 /**
263 * With page TS config it is possible to force a specific type id via mod.web_view.type
264 * for a page id or a page tree.
265 * The method checks if a type is set for the given id and returns the additional GET string.
266 *
267 * @param int $pageId
268 * @return string
269 */
270 protected function getTypeParameterIfSet(int $pageId): string
271 {
272 $typeParameter = '';
273 $typeId = (int)(BackendUtility::getPagesTSconfig($pageId)['mod.']['web_view.']['type'] ?? 0);
274 if ($typeId > 0) {
275 $typeParameter = '&type=' . $typeId;
276 }
277 return $typeParameter;
278 }
279
280 /**
281 * Get domain name for requested page id
282 *
283 * @param int $pageId
284 * @return string|null Domain name from first sys_domains-Record or from TCEMAIN.previewDomain, NULL if neither is configured
285 */
286 protected function getDomainName(int $pageId)
287 {
288 $previewDomainConfig = BackendUtility::getPagesTSconfig($pageId)['TCEMAIN.']['previewDomain'] ?? '';
289 return $previewDomainConfig ?: BackendUtility::firstDomainRecord(BackendUtility::BEgetRootLine($pageId));
290 }
291
292 /**
293 * Get available presets for page id
294 *
295 * @param int $pageId
296 * @return array
297 */
298 protected function getPreviewPresets(int $pageId): array
299 {
300 $presetGroups = [
301 'desktop' => [],
302 'tablet' => [],
303 'mobile' => [],
304 'unidentified' => []
305 ];
306 $previewFrameWidthConfig = BackendUtility::getPagesTSconfig($pageId)['mod.']['web_view.']['previewFrameWidths.'] ?? [];
307 foreach ($previewFrameWidthConfig as $item => $conf) {
308 $data = [
309 'key' => substr($item, 0, -1),
310 'label' => $conf['label'] ?? null,
311 'type' => $conf['type'] ?? 'unknown',
312 'width' => (isset($conf['width']) && (int)$conf['width'] > 0 && strpos($conf['width'], '%') === false) ? (int)$conf['width'] : null,
313 'height' => (isset($conf['height']) && (int)$conf['height'] > 0 && strpos($conf['height'], '%') === false) ? (int)$conf['height'] : null,
314 ];
315 $width = (int)substr($item, 0, -1);
316 if (!isset($data['width']) && $width > 0) {
317 $data['width'] = $width;
318 }
319 if (!isset($data['label'])) {
320 $data['label'] = $data['key'];
321 } elseif (strpos($data['label'], 'LLL:') === 0) {
322 $data['label'] = $this->getLanguageService()->sL(trim($data['label']));
323 }
324
325 if (array_key_exists($data['type'], $presetGroups)) {
326 $presetGroups[$data['type']][$data['key']] = $data;
327 } else {
328 $presetGroups['unidentified'][$data['key']] = $data;
329 }
330 }
331
332 return $presetGroups;
333 }
334
335 /**
336 * Returns the preview languages
337 *
338 * @param int $pageId
339 * @return array
340 */
341 protected function getPreviewLanguages(int $pageId): array
342 {
343 $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
344 $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
345 $modSharedTSconfig = BackendUtility::getPagesTSconfig($pageId)['mod.']['SHARED.'] ?? [];
346 if ($modSharedTSconfig['view.']['disableLanguageSelector'] === '1') {
347 return [];
348 }
349 $languages = [
350 0 => isset($modSharedTSconfig['defaultLanguageLabel'])
351 ? $modSharedTSconfig['defaultLanguageLabel'] . ' (' . $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage') . ')'
352 : $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_mod_web_list.xlf:defaultLanguage')
353 ];
354 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
355 $queryBuilder->getRestrictions()
356 ->removeAll()
357 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
358
359 if (!$this->getBackendUser()->isAdmin()) {
360 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
361 }
362
363 $result = $queryBuilder->select('sys_language.uid', 'sys_language.title')
364 ->from('sys_language')
365 ->join(
366 'sys_language',
367 'pages',
368 'o',
369 $queryBuilder->expr()->eq('o.' . $languageField, $queryBuilder->quoteIdentifier('sys_language.uid'))
370 )
371 ->where(
372 $queryBuilder->expr()->eq(
373 'o.' . $localizationParentField,
374 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
375 )
376 )
377 ->groupBy('sys_language.uid', 'sys_language.title', 'sys_language.sorting')
378 ->orderBy('sys_language.sorting')
379 ->execute();
380
381 while ($row = $result->fetch()) {
382 if ($this->getBackendUser()->checkLanguageAccess($row['uid'])) {
383 $languages[$row['uid']] = $row['title'];
384 }
385 }
386 return $languages;
387 }
388
389 /**
390 * Returns the current language
391 *
392 * @param int $pageId
393 * @param string $languageParam
394 * @return int
395 */
396 protected function getCurrentLanguage(int $pageId, string $languageParam = null): int
397 {
398 $languageId = (int)$languageParam;
399 if ($languageParam === null) {
400 $states = $this->getBackendUser()->uc['moduleData']['web_view']['States'];
401 $languages = $this->getPreviewLanguages($pageId);
402 if (isset($states['languageSelectorValue']) && isset($languages[$states['languageSelectorValue']])) {
403 $languageId = (int)$states['languageSelectorValue'];
404 }
405 } else {
406 $this->getBackendUser()->uc['moduleData']['web_view']['States']['languageSelectorValue'] = $languageId;
407 $this->getBackendUser()->writeUC($this->getBackendUser()->uc);
408 }
409 return $languageId;
410 }
411
412 /**
413 * @return BackendUserAuthentication
414 */
415 protected function getBackendUser(): BackendUserAuthentication
416 {
417 return $GLOBALS['BE_USER'];
418 }
419
420 /**
421 * @return LanguageService
422 */
423 protected function getLanguageService(): LanguageService
424 {
425 return $GLOBALS['LANG'];
426 }
427 }