Layout module */ class PageLayoutController { /** * Page Id for which to make the listing * * @var int */ public $id; /** * Pointer - for browsing list of records. * * @var int */ public $pointer; /** * Thumbnails or not * * @var string */ public $imagemode; /** * Search-fields * * @var string */ public $search_field; /** * Search-levels * * @var int */ public $search_levels; /** * Show-limit * * @var int */ public $showLimit; /** * Return URL * * @var string */ public $returnUrl; /** * Clear-cache flag - if set, clears page cache for current id. * * @var bool */ public $clear_cache; /** * PopView id - for opening a window with the page * * @var bool */ public $popView; /** * Page select perms clause * * @var string */ public $perms_clause; /** * Module TSconfig * * @var array */ public $modTSconfig; /** * Module shared TSconfig * * @var array */ public $modSharedTSconfig; /** * Current ids page record * * @var array */ public $pageinfo; /** * "Pseudo" Description -table name * * @var string */ public $descrTable; /** * List of column-integers to edit. Is set from TSconfig, default is "1,0,2,3" * * @var string */ public $colPosList; /** * Flag: If content can be edited or not. * * @var bool */ public $EDIT_CONTENT; /** * Users permissions integer for this page. * * @var int */ public $CALC_PERMS; /** * Currently selected language for editing content elements * * @var int */ public $current_sys_language; /** * Module configuration * * @var array */ public $MCONF = []; /** * Menu configuration * * @var array */ public $MOD_MENU = []; /** * Module settings (session variable) * * @var array */ public $MOD_SETTINGS = []; /** * Module output accumulation * * @var string */ public $content; /** * List of column-integers accessible to the current BE user. * Is set from TSconfig, default is $colPosList * * @var string */ public $activeColPosList; /** * @var string */ protected $editSelect; /** * Caches the available languages in a colPos * * @var array */ protected $languagesInColumnCache = []; /** * @var IconFactory */ protected $iconFactory; /** * The name of the module * * @var string */ protected $moduleName = 'web_layout'; /** * @var ModuleTemplate */ protected $moduleTemplate; /** * @var ButtonBar */ protected $buttonBar; /** * @var string */ protected $searchContent; /** * Initializing the module */ public function init() { $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class); $this->iconFactory = $this->moduleTemplate->getIconFactory(); $this->buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar(); $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf'); // Setting module configuration / page select clause $this->MCONF['name'] = $this->moduleName; $this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW); // Get session data $sessionData = $this->getBackendUser()->getSessionData(RecordList::class); $this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : ''; // GPvars: $this->id = (int)GeneralUtility::_GP('id'); $this->pointer = GeneralUtility::_GP('pointer'); $this->imagemode = GeneralUtility::_GP('imagemode'); $this->clear_cache = GeneralUtility::_GP('clear_cache'); $this->popView = GeneralUtility::_GP('popView'); $this->search_field = GeneralUtility::_GP('search_field'); $this->search_levels = GeneralUtility::_GP('search_levels'); $this->showLimit = GeneralUtility::_GP('showLimit'); $this->returnUrl = GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl')); $sessionData['search_field'] = $this->search_field; // Store session data $this->getBackendUser()->setAndSaveSessionData(RecordList::class, $sessionData); // Load page info array: $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause); // Initialize menu $this->menuConfig(); // Setting sys language from session var: $this->current_sys_language = (int)$this->MOD_SETTINGS['language']; // CSH / Descriptions: $this->descrTable = '_MOD_' . $this->moduleName; } /** * Initialize menu array */ public function menuConfig() { $lang = $this->getLanguageService(); // MENU-ITEMS: $this->MOD_MENU = [ 'tt_content_showHidden' => '', 'function' => [ 1 => $lang->getLL('m_function_1'), 2 => $lang->getLL('m_function_2') ], 'language' => [ 0 => $lang->getLL('m_default') ] ]; // initialize page/be_user TSconfig settings $this->modSharedTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.SHARED'); $this->modTSconfig = BackendUtility::getModTSconfig($this->id, 'mod.' . $this->moduleName); // First, select all localized page records on the current page. Each represents a possibility for a language on the page. Add these to language selector. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language'); $queryBuilder->getRestrictions()->removeAll(); if ($this->id) { $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title') ->from('sys_language') ->join( 'sys_language', 'pages', 'pages', $queryBuilder->expr()->eq( 'sys_language.uid', $queryBuilder->quoteIdentifier('pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField']) ) ) ->where( $queryBuilder->expr()->eq( 'pages.deleted', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) ), $queryBuilder->expr()->eq( 'pages.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'], $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT) ), $queryBuilder->expr()->orX( $queryBuilder->expr()->gte( 'pages.t3ver_state', $queryBuilder->createNamedParameter( (string)new VersionState(VersionState::DEFAULT_STATE), \PDO::PARAM_INT ) ), $queryBuilder->expr()->eq( 'pages.t3ver_wsid', $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT) ) ) ) ->groupBy( 'pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'], 'sys_language.uid', 'sys_language.pid', 'sys_language.tstamp', 'sys_language.hidden', 'sys_language.title', 'sys_language.language_isocode', 'sys_language.static_lang_isocode', 'sys_language.flag', 'sys_language.sorting' ) ->orderBy('sys_language.sorting'); if (!$this->getBackendUser()->isAdmin()) { $queryBuilder->andWhere( $queryBuilder->expr()->eq( 'sys_language.hidden', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) ) ); } $statement = $queryBuilder->execute(); } else { $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class)); $statement = $queryBuilder->select('uid', 'title') ->from('sys_language') ->orderBy('sorting') ->execute(); } while ($lRow = $statement->fetch()) { if ($this->getBackendUser()->checkLanguageAccess($lRow['uid'])) { $this->MOD_MENU['language'][$lRow['uid']] = $lRow['title']; } } // Setting alternative default label: if (($this->modSharedTSconfig['properties']['defaultLanguageLabel'] || $this->modTSconfig['properties']['defaultLanguageLabel']) && isset($this->MOD_MENU['language'][0])) { $this->MOD_MENU['language'][0] = $this->modTSconfig['properties']['defaultLanguageLabel'] ? $this->modTSconfig['properties']['defaultLanguageLabel'] : $this->modSharedTSconfig['properties']['defaultLanguageLabel']; } // Initialize the avaiable actions $actions = $this->initActions(); // Clean up settings $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, GeneralUtility::_GP('SET'), $this->moduleName); // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't disabled the option if ($this->getBackendUser()->workspace != 0 || $this->MOD_SETTINGS['tt_content_showHidden'] !== '0') { $this->MOD_SETTINGS['tt_content_showHidden'] = 1; } // Make action menu from available actions $this->makeActionMenu($actions); } /** * Initializes the available actions this module provides * * @return array the available actions */ protected function initActions() { $actions = [ 1 => $this->getLanguageService()->getLL('m_function_1'), 2 => $this->getLanguageService()->getLL('m_function_2') ]; // Find if there are ANY languages at all (and if not, remove the language option from function menu). $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language'); if ($this->getBackendUser()->isAdmin()) { $queryBuilder->getRestrictions()->removeAll(); } $count = $queryBuilder ->count('uid') ->from('sys_language') ->execute() ->fetchColumn(0); if (!$count) { unset($actions['2']); } // page/be_user TSconfig blinding of menu-items $actions = BackendUtility::unsetMenuItems($this->modTSconfig['properties'], $actions, 'menu.function'); return $actions; } /** * This creates the dropdown menu with the different actions this module is able to provide. * For now they are Columns, Quick Edit and Languages. * * @param array $actions array with the available actions */ protected function makeActionMenu(array $actions) { $actionMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu(); $actionMenu->setIdentifier('actionMenu'); $actionMenu->setLabel(''); /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); $defaultKey = null; $foundDefaultKey = false; foreach ($actions as $key => $action) { $menuItem = $actionMenu ->makeMenuItem() ->setTitle($action) ->setHref((string)$uriBuilder->buildUriFromRoute($this->moduleName) . '&id=' . $this->id . '&SET[function]=' . $key); if (!$foundDefaultKey) { $defaultKey = $key; $foundDefaultKey = true; } if ((int)$this->MOD_SETTINGS['function'] === $key) { $menuItem->setActive(true); $defaultKey = null; } $actionMenu->addMenuItem($menuItem); } if (isset($defaultKey)) { $this->MOD_SETTINGS['function'] = $defaultKey; } $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($actionMenu); } /** * Clears page cache for the current id, $this->id */ public function clearCache() { if ($this->clear_cache && !empty($this->pageinfo)) { $dataHandler = GeneralUtility::makeInstance(DataHandler::class); $dataHandler->start([], []); $dataHandler->clear_cacheCmd($this->id); } } /** * Generate the flashmessages for current pid * * @return string HTML content with flashmessages */ protected function getHeaderFlashMessagesForCurrentPid() { $content = ''; $lang = $this->getLanguageService(); $view = GeneralUtility::makeInstance(StandaloneView::class); $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html')); // If page is a folder if ($this->pageinfo['doktype'] == PageRepository::DOKTYPE_SYSFOLDER) { $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class); $moduleLoader->load($GLOBALS['TBE_MODULES']); $modules = $moduleLoader->modules; if (is_array($modules['web']['sub']['list'])) { $title = $lang->getLL('goToListModule'); $message = '
' . $lang->getLL('goToListModuleMessage') . '
'; $message .= '' . $lang->getLL('goToListModule') . ''; $view->assignMultiple([ 'title' => $title, 'message' => $message, 'state' => InfoboxViewHelper::STATE_INFO ]); $content .= $view->render(); } } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_SHORTCUT) { $shortcutMode = (int)$this->pageinfo['shortcut_mode']; $pageRepository = GeneralUtility::makeInstance(PageRepository::class); $targetPage = []; if ($this->pageinfo['shortcut'] || $shortcutMode) { switch ($shortcutMode) { case PageRepository::SHORTCUT_MODE_NONE: $targetPage = $pageRepository->getPage($this->pageinfo['shortcut']); break; case PageRepository::SHORTCUT_MODE_FIRST_SUBPAGE: $targetPage = reset($pageRepository->getMenu($this->pageinfo['shortcut'] ?: $this->pageinfo['uid'])); break; case PageRepository::SHORTCUT_MODE_PARENT_PAGE: $targetPage = $pageRepository->getPage($this->pageinfo['pid']); break; } $message = ''; if ($shortcutMode === PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) { $message .= sprintf($lang->getLL('pageIsRandomInternalLinkMessage')); } else { $linkToPid = $this->local_linkThisScript(['id' => $targetPage['uid']]); $path = BackendUtility::getRecordPath($targetPage['uid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 1000); $linkedPath = '' . htmlspecialchars($path) . ''; $message .= sprintf($lang->getLL('pageIsInternalLinkMessage'), $linkedPath); } $message .= ' (' . htmlspecialchars($lang->sL(BackendUtility::getLabelFromItemlist('pages', 'shortcut_mode', $shortcutMode))) . ')'; $view->assignMultiple([ 'title' => $this->pageinfo['title'], 'message' => $message, 'state' => InfoboxViewHelper::STATE_INFO ]); $content .= $view->render(); } else { if (empty($targetPage) && $shortcutMode !== PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) { $view->assignMultiple([ 'title' => $this->pageinfo['title'], 'message' => $lang->getLL('pageIsMisconfiguredInternalLinkMessage'), 'state' => InfoboxViewHelper::STATE_ERROR ]); $content .= $view->render(); } } } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_LINK) { if (empty($this->pageinfo['url'])) { $view->assignMultiple([ 'title' => $this->pageinfo['title'], 'message' => $lang->getLL('pageIsMisconfiguredExternalLinkMessage'), 'state' => InfoboxViewHelper::STATE_ERROR ]); $content .= $view->render(); } else { $externalUrl = htmlspecialchars(GeneralUtility::makeInstance(PageRepository::class)->getExtURL($this->pageinfo)); if ($externalUrl !== false) { $externalUrlHtml = '' . $externalUrl . ''; $view->assignMultiple([ 'title' => $this->pageinfo['title'], 'message' => sprintf($lang->getLL('pageIsExternalLinkMessage'), $externalUrlHtml), 'state' => InfoboxViewHelper::STATE_INFO ]); $content .= $view->render(); } } } // If content from different pid is displayed if ($this->pageinfo['content_from_pid']) { $contentPage = BackendUtility::getRecord('pages', (int)$this->pageinfo['content_from_pid']); $linkToPid = $this->local_linkThisScript(['id' => $this->pageinfo['content_from_pid']]); $title = BackendUtility::getRecordTitle('pages', $contentPage); $link = '' . htmlspecialchars($title) . ' (PID ' . (int)$this->pageinfo['content_from_pid'] . ')'; $message = sprintf($lang->getLL('content_from_pid_title'), $link); $view->assignMultiple([ 'title' => $title, 'message' => $message, 'state' => InfoboxViewHelper::STATE_INFO ]); $content .= $view->render(); } else { $links = $this->getPageLinksWhereContentIsAlsoShownOn($this->pageinfo['uid']); if (!empty($links)) { $message = sprintf($lang->getLL('content_on_pid_title'), $links); $view->assignMultiple([ 'title' => '', 'message' => $message, 'state' => InfoboxViewHelper::STATE_INFO ]); $content .= $view->render(); } } return $content; } /** * Get all pages with links where the content of a page $pageId is also shown on * * @param int $pageId * @return string */ protected function getPageLinksWhereContentIsAlsoShownOn($pageId) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages'); $queryBuilder->getRestrictions()->removeAll(); $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class)); $queryBuilder ->select('*') ->from('pages') ->where($queryBuilder->expr()->eq('content_from_pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT))); $links = []; $rows = $queryBuilder->execute()->fetchAll(); if (!empty($rows)) { foreach ($rows as $row) { $linkToPid = $this->local_linkThisScript(['id' => $row['uid']]); $title = BackendUtility::getRecordTitle('pages', $row); $link = '' . htmlspecialchars($title) . ' (PID ' . (int)$row['uid'] . ')'; $links[] = $link; } } return implode(', ', $links); } /** * @return string $title */ protected function getLocalizedPageTitle() { if ($this->current_sys_language > 0) { $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) ->getQueryBuilderForTable('pages'); $queryBuilder->getRestrictions() ->removeAll() ->add(GeneralUtility::makeInstance(DeletedRestriction::class)) ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class)); $localizedPage = $queryBuilder ->select('*') ->from('pages') ->where( $queryBuilder->expr()->eq( $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'], $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT) ), $queryBuilder->expr()->eq( $GLOBALS['TCA']['pages']['ctrl']['languageField'], $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT) ) ) ->setMaxResults(1) ->execute() ->fetch(); BackendUtility::workspaceOL('pages', $localizedPage); return $localizedPage['title']; } return $this->pageinfo['title']; } /** * Injects the request object for the current request or subrequest * As this controller goes only through the main() method, it is rather simple for now * * @param ServerRequestInterface $request the current request * @param ResponseInterface $response * @return ResponseInterface the response with the content */ public function mainAction(ServerRequestInterface $request, ResponseInterface $response) { $GLOBALS['SOBE'] = $this; $this->init(); $this->clearCache(); $this->main(); $response->getBody()->write($this->moduleTemplate->renderContent()); return $response; } /** * Main function. * Creates some general objects and calls other functions for the main rendering of module content. */ public function main() { $lang = $this->getLanguageService(); // Access check... // The page will show only if there is a valid page and if this page may be viewed by the user $access = is_array($this->pageinfo); // Content $content = ''; if ($this->id && $access) { // Initialize permission settings: $this->CALC_PERMS = $this->getBackendUser()->calcPerms($this->pageinfo); $this->EDIT_CONTENT = $this->contentIsNotLockedForEditors(); $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo); // override the default jumpToUrl $this->moduleTemplate->addJavaScriptCode('jumpToUrl', ' function jumpToUrl(URL,formEl) { if (document.editform && TBE_EDITOR.isFormChanged) { // Check if the function exists... (works in all browsers?) if (!TBE_EDITOR.isFormChanged()) { window.location.href = URL; } else if (formEl) { if (formEl.type=="checkbox") formEl.checked = formEl.checked ? 0 : 1; } } else { window.location.href = URL; } } '); /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */ $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class); $this->moduleTemplate->addJavaScriptCode('mainJsFunctions', ' if (top.fsMod) { top.fsMod.recentIds["web"] = ' . (int)$this->id . '; top.fsMod.navFrameHighlightedID["web"] = "pages' . (int)$this->id . '_"+top.fsMod.currentBank; ' . (int)$this->id . '; } ' . ($this->popView ? BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)) : '') . ' function deleteRecord(table,id,url) { // window.location.href = ' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('tce_db') . '&cmd[') . ' + table + "][" + id + "][delete]=1&redirect=" + encodeURIComponent(url); return false; } '); // Find backend layout / columns $backendLayout = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getSelectedBackendLayout', $this->id, $this); if (!empty($backendLayout['__colPosList'])) { $this->colPosList = implode(',', $backendLayout['__colPosList']); } // Removing duplicates, if any $this->colPosList = array_unique(GeneralUtility::intExplode(',', $this->colPosList)); // Accessible columns if (isset($this->modSharedTSconfig['properties']['colPos_list']) && trim($this->modSharedTSconfig['properties']['colPos_list']) !== '') { $this->activeColPosList = array_unique(GeneralUtility::intExplode(',', trim($this->modSharedTSconfig['properties']['colPos_list']))); // Match with the list which is present in the colPosList for the current page if (!empty($this->colPosList) && !empty($this->activeColPosList)) { $this->activeColPosList = array_unique(array_intersect( $this->activeColPosList, $this->colPosList )); } } else { $this->activeColPosList = $this->colPosList; } $this->activeColPosList = implode(',', $this->activeColPosList); $this->colPosList = implode(',', $this->colPosList); $content .= $this->getHeaderFlashMessagesForCurrentPid(); // Render the primary module content: if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) { $content .= ''; $content .= $this->searchContent; // Setting up the buttons for the docheader $this->makeButtons(); // @internal: This is an internal hook for compatibility7 only, this hook will be removed without further notice if ($this->MOD_SETTINGS['function'] != 1 && $this->MOD_SETTINGS['function'] != 2) { foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class]['renderActionHook'] ?? [] as $hook) { $params = [ 'deleteButton' => $this->deleteButton, '' ]; $content .= GeneralUtility::callUserFunction($hook, $params, $this); } } // Create LanguageMenu $this->makeLanguageMenu(); } else { $this->moduleTemplate->addJavaScriptCode( 'mainJsFunctions', 'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';' ); $content .= '