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