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