bb2f43c4e9dfac2e9a101519ac04cd338bc73599
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / PageLayoutController.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Backend\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\Module\ModuleLoader;
21 use TYPO3\CMS\Backend\Routing\UriBuilder;
22 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
23 use TYPO3\CMS\Backend\Template\ModuleTemplate;
24 use TYPO3\CMS\Backend\Utility\BackendUtility;
25 use TYPO3\CMS\Backend\View\BackendLayoutView;
26 use TYPO3\CMS\Backend\View\PageLayoutView;
27 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
28 use TYPO3\CMS\Core\Compatibility\PublicMethodDeprecationTrait;
29 use TYPO3\CMS\Core\Compatibility\PublicPropertyDeprecationTrait;
30 use TYPO3\CMS\Core\Database\ConnectionPool;
31 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
32 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
33 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
34 use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
35 use TYPO3\CMS\Core\DataHandling\DataHandler;
36 use TYPO3\CMS\Core\Http\HtmlResponse;
37 use TYPO3\CMS\Core\Imaging\Icon;
38 use TYPO3\CMS\Core\Imaging\IconFactory;
39 use TYPO3\CMS\Core\Localization\LanguageService;
40 use TYPO3\CMS\Core\Page\PageRenderer;
41 use TYPO3\CMS\Core\Type\Bitmask\Permission;
42 use TYPO3\CMS\Core\Utility\GeneralUtility;
43 use TYPO3\CMS\Core\Utility\MathUtility;
44 use TYPO3\CMS\Core\Versioning\VersionState;
45 use TYPO3\CMS\Fluid\View\StandaloneView;
46 use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
47 use TYPO3\CMS\Frontend\Page\PageRepository;
48
49 /**
50 * Script Class for Web > Layout module
51 */
52 class PageLayoutController
53 {
54 use PublicMethodDeprecationTrait;
55 use PublicPropertyDeprecationTrait;
56
57 /**
58 * @var array
59 */
60 private $deprecatedPublicMethods = [
61 'init' => 'Using PageLayoutController::init() is deprecated and will not be possible anymore in TYPO3 v10.',
62 'main' => 'Using PageLayoutController::main() is deprecated and will not be possible anymore in TYPO3 v10.',
63 'menuConfig' => 'Using PageLayoutController::menuConfig() is deprecated and will not be possible anymore in TYPO3 v10.',
64 'renderContent' => 'Using PageLayoutController::renderContent() is deprecated and will not be possible anymore in TYPO3 v10.',
65 'clearCache' => 'Using PageLayoutController::clearCache() is deprecated and will not be possible anymore in TYPO3 v10.',
66 'getModuleTemplate' => 'Using PageLayoutController::getModuleTemplate() is deprecated and will not be possible anymore in TYPO3 v10.',
67 'getLocalizedPageTitle' => 'Using PageLayoutController::getLocalizedPageTitle() is deprecated and will not be possible anymore in TYPO3 v10.',
68 'getNumberOfHiddenElements' => 'Using PageLayoutController::getNumberOfHiddenElements() is deprecated and will not be possible anymore in TYPO3 v10.',
69 'local_linkThisScript' => 'Using PageLayoutController::local_linkThisScript() is deprecated and will not be possible anymore in TYPO3 v10.',
70 'pageIsNotLockedForEditors' => 'Using PageLayoutController::pageIsNotLockedForEditors() is deprecated and will not be possible anymore in TYPO3 v10.',
71 'contentIsNotLockedForEditors' => 'Using PageLayoutController::contentIsNotLockedForEditors() is deprecated and will not be possible anymore in TYPO3 v10.',
72 ];
73
74 /**
75 * @var array
76 */
77 private $deprecatedPublicProperties = [
78 'pointer' => 'Using PageLayoutController::$pointer is deprecated and will not be possible anymore in TYPO3 v10.',
79 'imagemode' => 'Using PageLayoutController::$imagemode is deprecated and will not be possible anymore in TYPO3 v10.',
80 'search_field' => 'Using PageLayoutController::$search_field is deprecated and will not be possible anymore in TYPO3 v10.',
81 'search_levels' => 'Using PageLayoutController::$search_levels is deprecated and will not be possible anymore in TYPO3 v10.',
82 'showLimit' => 'Using PageLayoutController::$showLimit is deprecated and will not be possible anymore in TYPO3 v10.',
83 'returnUrl' => 'Using PageLayoutController::$returnUrl is deprecated and will not be possible anymore in TYPO3 v10.',
84 'clear_cache' => 'Using PageLayoutController::$clear_cache is deprecated and will not be possible anymore in TYPO3 v10.',
85 'popView' => 'Using PageLayoutController::$popView is deprecated and will not be possible anymore in TYPO3 v10.',
86 'perms_clause' => 'Using PageLayoutController::$perms_clause is deprecated and will not be possible anymore in TYPO3 v10.',
87 'modTSconfig' => 'Using PageLayoutController::$modTSconfig is deprecated and will not be possible anymore in TYPO3 v10.',
88 'modSharedTSconfig' => 'Using PageLayoutController::$modSharedTSconfig is deprecated and will not be possible anymore in TYPO3 v10.',
89 'descrTable' => 'Using PageLayoutController::$descrTable is deprecated and will not be possible anymore in TYPO3 v10.',
90 'colPosList' => 'Using PageLayoutController::$colPosList is deprecated and will not be possible anymore in TYPO3 v10.',
91 'EDIT_CONTENT' => 'Using PageLayoutController::$EDIT_CONTENT is deprecated and will not be possible anymore in TYPO3 v10.',
92 'CALC_PERMS' => 'Using PageLayoutController::$CALC_PERMS is deprecated and will not be possible anymore in TYPO3 v10.',
93 'current_sys_language' => 'Using PageLayoutController::$current_sys_language is deprecated and will not be possible anymore in TYPO3 v10.',
94 'MCONF' => 'Using PageLayoutController::$MCONF is deprecated and will not be possible anymore in TYPO3 v10.',
95 'MOD_MENU' => 'Using PageLayoutController::$MOD_MENU is deprecated and will not be possible anymore in TYPO3 v10.',
96 'content' => 'Using PageLayoutController::$content is deprecated and will not be possible anymore in TYPO3 v10.',
97 'activeColPosList' => 'Using PageLayoutController::$activeColPosList is deprecated and will not be possible anymore in TYPO3 v10.',
98 ];
99
100 /**
101 * Page Id for which to make the listing
102 *
103 * @var int
104 * @internal
105 */
106 public $id;
107
108 /**
109 * Pointer - for browsing list of records.
110 *
111 * @var int
112 */
113 protected $pointer;
114
115 /**
116 * Thumbnails or not
117 *
118 * @var string
119 */
120 protected $imagemode;
121
122 /**
123 * Search-fields
124 *
125 * @var string
126 */
127 protected $search_field;
128
129 /**
130 * Search-levels
131 *
132 * @var int
133 */
134 protected $search_levels;
135
136 /**
137 * Show-limit
138 *
139 * @var int
140 */
141 protected $showLimit;
142
143 /**
144 * Return URL
145 *
146 * @var string
147 */
148 protected $returnUrl;
149
150 /**
151 * Clear-cache flag - if set, clears page cache for current id.
152 *
153 * @var bool
154 */
155 protected $clear_cache;
156
157 /**
158 * PopView id - for opening a window with the page
159 *
160 * @var bool
161 */
162 protected $popView;
163
164 /**
165 * Page select perms clause
166 *
167 * @var string
168 */
169 protected $perms_clause;
170
171 /**
172 * Module TSconfig
173 *
174 * @var array
175 */
176 protected $modTSconfig = [];
177
178 /**
179 * Module shared TSconfig
180 *
181 * @var array
182 */
183 protected $modSharedTSconfig = [];
184
185 /**
186 * Current ids page record
187 *
188 * @var array
189 * @internal
190 */
191 public $pageinfo;
192
193 /**
194 * "Pseudo" Description -table name
195 *
196 * @var string
197 */
198 protected $descrTable;
199
200 /**
201 * List of column-integers to edit. Is set from TSconfig, default is "1,0,2,3"
202 *
203 * @var string
204 */
205 protected $colPosList;
206
207 /**
208 * Flag: If content can be edited or not.
209 *
210 * @var bool
211 */
212 protected $EDIT_CONTENT;
213
214 /**
215 * Users permissions integer for this page.
216 *
217 * @var int
218 */
219 protected $CALC_PERMS;
220
221 /**
222 * Currently selected language for editing content elements
223 *
224 * @var int
225 */
226 protected $current_sys_language;
227
228 /**
229 * Module configuration
230 *
231 * @var array
232 */
233 protected $MCONF = [];
234
235 /**
236 * Menu configuration
237 *
238 * @var array
239 */
240 protected $MOD_MENU = [];
241
242 /**
243 * Module settings (session variable)
244 *
245 * @var array
246 * @internal
247 */
248 public $MOD_SETTINGS = [];
249
250 /**
251 * Module output accumulation
252 *
253 * @var string
254 */
255 protected $content;
256
257 /**
258 * List of column-integers accessible to the current BE user.
259 * Is set from TSconfig, default is $colPosList
260 *
261 * @var string
262 */
263 protected $activeColPosList;
264
265 /**
266 * @var string
267 */
268 protected $editSelect;
269
270 /**
271 * Caches the available languages in a colPos
272 *
273 * @var array
274 */
275 protected $languagesInColumnCache = [];
276
277 /**
278 * @var IconFactory
279 */
280 protected $iconFactory;
281
282 /**
283 * The name of the module
284 *
285 * @var string
286 */
287 protected $moduleName = 'web_layout';
288
289 /**
290 * @var ModuleTemplate
291 */
292 protected $moduleTemplate;
293
294 /**
295 * @var ButtonBar
296 */
297 protected $buttonBar;
298
299 /**
300 * @var string
301 */
302 protected $searchContent;
303
304 /**
305 * Injects the request object for the current request or subrequest
306 * As this controller goes only through the main() method, it is rather simple for now
307 *
308 * @param ServerRequestInterface $request the current request
309 * @return ResponseInterface the response with the content
310 */
311 public function mainAction(ServerRequestInterface $request): ResponseInterface
312 {
313 $GLOBALS['SOBE'] = $this;
314 $this->init($request);
315 $this->clearCache();
316 $this->main($request);
317 return new HtmlResponse($this->moduleTemplate->renderContent());
318 }
319
320 /**
321 * Initializing the module
322 * @param ServerRequestInterface $request
323 */
324 protected function init(ServerRequestInterface $request = null): void
325 {
326 $request = $request ?: $GLOBALS['TYPO3_REQUEST'];
327 // Set the GPvars from outside
328 $parsedBody = $request->getParsedBody();
329 $queryParams = $request->getQueryParams();
330
331 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
332 $this->iconFactory = $this->moduleTemplate->getIconFactory();
333 $this->buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
334 $this->getLanguageService()->includeLLFile('EXT:backend/Resources/Private/Language/locallang_layout.xlf');
335 // Setting module configuration / page select clause
336 $this->MCONF['name'] = $this->moduleName;
337 $this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
338 // Get session data
339 $sessionData = $this->getBackendUser()->getSessionData(__CLASS__);
340 $this->search_field = !empty($sessionData['search_field']) ? $sessionData['search_field'] : '';
341
342 $this->id = (int)($parsedBody['id'] ?? $queryParams['id'] ?? 0);
343 $this->pointer = $parsedBody['pointer'] ?? $queryParams['pointer'] ?? null;
344 $this->imagemode = $parsedBody['imagemode'] ?? $queryParams['imagemode'] ?? null;
345 $this->clear_cache = $parsedBody['clear_cache'] ?? $queryParams['clear_cache'] ?? null;
346 $this->popView = $parsedBody['popView'] ?? $queryParams['popView'] ?? null;
347 $this->search_field = $parsedBody['search_field'] ?? $queryParams['search_field'] ?? null;
348 $this->search_levels = $parsedBody['search_levels'] ?? $queryParams['search_levels'] ?? null;
349 $this->showLimit = $parsedBody['showLimit'] ?? $queryParams['showLimit'] ?? null;
350 $returnUrl = $parsedBody['returnUrl'] ?? $queryParams['returnUrl'] ?? null;
351 $this->returnUrl = GeneralUtility::sanitizeLocalUrl($returnUrl);
352
353 $sessionData['search_field'] = $this->search_field;
354 // Store session data
355 $this->getBackendUser()->setAndSaveSessionData(__CLASS__, $sessionData);
356 // Load page info array:
357 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
358 // Initialize menu
359 $this->menuConfig($request);
360 // Setting sys language from session var:
361 $this->current_sys_language = (int)$this->MOD_SETTINGS['language'];
362 // CSH / Descriptions:
363 $this->descrTable = '_MOD_' . $this->moduleName;
364 }
365
366 /**
367 * Initialize menu array
368 * @param ServerRequestInterface $request
369 */
370 protected function menuConfig(ServerRequestInterface $request = null): void
371 {
372 $request = $request ?: $GLOBALS['TYPO3_REQUEST'];
373 // Set the GPvars from outside
374 $parsedBody = $request->getParsedBody();
375 $queryParams = $request->getQueryParams();
376
377 $lang = $this->getLanguageService();
378 // MENU-ITEMS:
379 $this->MOD_MENU = [
380 'tt_content_showHidden' => '',
381 'function' => [
382 1 => $lang->getLL('m_function_1'),
383 2 => $lang->getLL('m_function_2')
384 ],
385 'language' => [
386 0 => $lang->getLL('m_default')
387 ]
388 ];
389 // initialize page/be_user TSconfig settings
390 $pageTsConfig = BackendUtility::getPagesTSconfig($this->id);
391 $this->modSharedTSconfig['properties'] = $pageTsConfig['mod.']['SHARED.'] ?? [];
392 $this->modTSconfig['properties'] = $pageTsConfig['mod.']['web_layout.'] ?? [];
393
394 // 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.
395 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
396 $queryBuilder->getRestrictions()->removeAll();
397 if ($this->id) {
398 $queryBuilder->select('sys_language.uid AS uid', 'sys_language.title AS title')
399 ->from('sys_language')
400 ->join(
401 'sys_language',
402 'pages',
403 'pages',
404 $queryBuilder->expr()->eq(
405 'sys_language.uid',
406 $queryBuilder->quoteIdentifier('pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'])
407 )
408 )
409 ->where(
410 $queryBuilder->expr()->eq(
411 'pages.deleted',
412 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
413 ),
414 $queryBuilder->expr()->eq(
415 'pages.' . $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
416 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
417 ),
418 $queryBuilder->expr()->orX(
419 $queryBuilder->expr()->gte(
420 'pages.t3ver_state',
421 $queryBuilder->createNamedParameter(
422 (string)new VersionState(VersionState::DEFAULT_STATE),
423 \PDO::PARAM_INT
424 )
425 ),
426 $queryBuilder->expr()->eq(
427 'pages.t3ver_wsid',
428 $queryBuilder->createNamedParameter($this->getBackendUser()->workspace, \PDO::PARAM_INT)
429 )
430 )
431 )
432 ->groupBy(
433 'pages.' . $GLOBALS['TCA']['pages']['ctrl']['languageField'],
434 'sys_language.uid',
435 'sys_language.pid',
436 'sys_language.tstamp',
437 'sys_language.hidden',
438 'sys_language.title',
439 'sys_language.language_isocode',
440 'sys_language.static_lang_isocode',
441 'sys_language.flag',
442 'sys_language.sorting'
443 )
444 ->orderBy('sys_language.sorting');
445 if (!$this->getBackendUser()->isAdmin()) {
446 $queryBuilder->andWhere(
447 $queryBuilder->expr()->eq(
448 'sys_language.hidden',
449 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
450 )
451 );
452 }
453 $statement = $queryBuilder->execute();
454 } else {
455 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(HiddenRestriction::class));
456 $statement = $queryBuilder->select('uid', 'title')
457 ->from('sys_language')
458 ->orderBy('sorting')
459 ->execute();
460 }
461 while ($lRow = $statement->fetch()) {
462 if ($this->getBackendUser()->checkLanguageAccess($lRow['uid'])) {
463 $this->MOD_MENU['language'][$lRow['uid']] = $lRow['title'];
464 }
465 }
466 // Setting alternative default label:
467 if ((!empty($this->modSharedTSconfig['properties']['defaultLanguageLabel']) || !empty($this->modTSconfig['properties']['defaultLanguageLabel'])) && isset($this->MOD_MENU['language'][0])) {
468 $this->MOD_MENU['language'][0] = $this->modTSconfig['properties']['defaultLanguageLabel'] ? $this->modTSconfig['properties']['defaultLanguageLabel'] : $this->modSharedTSconfig['properties']['defaultLanguageLabel'];
469 }
470 // Initialize the avaiable actions
471 $actions = $this->initActions();
472 // Clean up settings
473 $this->MOD_SETTINGS = BackendUtility::getModuleData($this->MOD_MENU, $parsedBody['SET'] ?? $queryParams['SET'] ?? [], $this->moduleName);
474 // For all elements to be shown in draft workspaces & to also show hidden elements by default if user hasn't disabled the option
475 if ($this->getBackendUser()->workspace != 0
476 || !isset($this->MOD_SETTINGS['tt_content_showHidden'])
477 || $this->MOD_SETTINGS['tt_content_showHidden'] !== '0'
478 ) {
479 $this->MOD_SETTINGS['tt_content_showHidden'] = 1;
480 }
481 // Make action menu from available actions
482 $this->makeActionMenu($actions);
483 }
484
485 /**
486 * Initializes the available actions this module provides
487 *
488 * @return array the available actions
489 */
490 protected function initActions(): array
491 {
492 $actions = [
493 1 => $this->getLanguageService()->getLL('m_function_1'),
494 2 => $this->getLanguageService()->getLL('m_function_2')
495 ];
496 // Find if there are ANY languages at all (and if not, remove the language option from function menu).
497 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
498 if ($this->getBackendUser()->isAdmin()) {
499 $queryBuilder->getRestrictions()->removeAll();
500 }
501
502 $count = $queryBuilder
503 ->count('uid')
504 ->from('sys_language')
505 ->execute()
506 ->fetchColumn(0);
507
508 if (!$count) {
509 unset($actions['2']);
510 }
511 // Page / user TSconfig blinding of menu-items
512 $blindActions = $this->modTSconfig['properties']['menu.']['functions.'] ?? [];
513 foreach ($blindActions as $key => $value) {
514 if (!$value && array_key_exists($key, $actions)) {
515 unset($actions[$key]);
516 }
517 }
518
519 return $actions;
520 }
521
522 /**
523 * This creates the dropdown menu with the different actions this module is able to provide.
524 * For now they are Columns, Quick Edit and Languages.
525 *
526 * @param array $actions array with the available actions
527 */
528 protected function makeActionMenu(array $actions): void
529 {
530 $actionMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
531 $actionMenu->setIdentifier('actionMenu');
532 $actionMenu->setLabel('');
533
534 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
535
536 $defaultKey = null;
537 $foundDefaultKey = false;
538 foreach ($actions as $key => $action) {
539 $menuItem = $actionMenu
540 ->makeMenuItem()
541 ->setTitle($action)
542 ->setHref((string)$uriBuilder->buildUriFromRoute($this->moduleName) . '&id=' . $this->id . '&SET[function]=' . $key);
543
544 if (!$foundDefaultKey) {
545 $defaultKey = $key;
546 $foundDefaultKey = true;
547 }
548 if ((int)$this->MOD_SETTINGS['function'] === $key) {
549 $menuItem->setActive(true);
550 $defaultKey = null;
551 }
552 $actionMenu->addMenuItem($menuItem);
553 }
554 if (isset($defaultKey)) {
555 $this->MOD_SETTINGS['function'] = $defaultKey;
556 }
557 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($actionMenu);
558 }
559
560 /**
561 * Clears page cache for the current id, $this->id
562 */
563 protected function clearCache(): void
564 {
565 if ($this->clear_cache && !empty($this->pageinfo)) {
566 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
567 $dataHandler->start([], []);
568 $dataHandler->clear_cacheCmd($this->id);
569 }
570 }
571
572 /**
573 * Generate the flashmessages for current pid
574 *
575 * @return string HTML content with flashmessages
576 */
577 protected function getHeaderFlashMessagesForCurrentPid(): string
578 {
579 $content = '';
580 $lang = $this->getLanguageService();
581
582 $view = GeneralUtility::makeInstance(StandaloneView::class);
583 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
584
585 // If page is a folder
586 if ($this->pageinfo['doktype'] == PageRepository::DOKTYPE_SYSFOLDER) {
587 $moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
588 $moduleLoader->load($GLOBALS['TBE_MODULES']);
589 $modules = $moduleLoader->modules;
590 if (is_array($modules['web']['sub']['list'])) {
591 $title = $lang->getLL('goToListModule');
592 $message = '<p>' . $lang->getLL('goToListModuleMessage') . '</p>';
593 $message .= '<a class="btn btn-info" href="javascript:top.goToModule(\'web_list\',1);">' . $lang->getLL('goToListModule') . '</a>';
594 $view->assignMultiple([
595 'title' => $title,
596 'message' => $message,
597 'state' => InfoboxViewHelper::STATE_INFO
598 ]);
599 $content .= $view->render();
600 }
601 } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_SHORTCUT) {
602 $shortcutMode = (int)$this->pageinfo['shortcut_mode'];
603 $pageRepository = GeneralUtility::makeInstance(PageRepository::class);
604 $targetPage = [];
605
606 if ($this->pageinfo['shortcut'] || $shortcutMode) {
607 switch ($shortcutMode) {
608 case PageRepository::SHORTCUT_MODE_NONE:
609 $targetPage = $pageRepository->getPage($this->pageinfo['shortcut']);
610 break;
611 case PageRepository::SHORTCUT_MODE_FIRST_SUBPAGE:
612 $targetPage = reset($pageRepository->getMenu($this->pageinfo['shortcut'] ?: $this->pageinfo['uid']));
613 break;
614 case PageRepository::SHORTCUT_MODE_PARENT_PAGE:
615 $targetPage = $pageRepository->getPage($this->pageinfo['pid']);
616 break;
617 }
618
619 $message = '';
620 if ($shortcutMode === PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
621 $message .= sprintf($lang->getLL('pageIsRandomInternalLinkMessage'));
622 } else {
623 $linkToPid = $this->local_linkThisScript(['id' => $targetPage['uid']]);
624 $path = BackendUtility::getRecordPath($targetPage['uid'], $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW), 1000);
625 $linkedPath = '<a href="' . htmlspecialchars($linkToPid) . '">' . htmlspecialchars($path) . '</a>';
626 $message .= sprintf($lang->getLL('pageIsInternalLinkMessage'), $linkedPath);
627 }
628
629 $message .= ' (' . htmlspecialchars($lang->sL(BackendUtility::getLabelFromItemlist('pages', 'shortcut_mode', $shortcutMode))) . ')';
630
631 $view->assignMultiple([
632 'title' => $this->pageinfo['title'],
633 'message' => $message,
634 'state' => InfoboxViewHelper::STATE_INFO
635 ]);
636 $content .= $view->render();
637 } else {
638 if (empty($targetPage) && $shortcutMode !== PageRepository::SHORTCUT_MODE_RANDOM_SUBPAGE) {
639 $view->assignMultiple([
640 'title' => $this->pageinfo['title'],
641 'message' => $lang->getLL('pageIsMisconfiguredInternalLinkMessage'),
642 'state' => InfoboxViewHelper::STATE_ERROR
643 ]);
644 $content .= $view->render();
645 }
646 }
647 } elseif ($this->pageinfo['doktype'] === PageRepository::DOKTYPE_LINK) {
648 if (empty($this->pageinfo['url'])) {
649 $view->assignMultiple([
650 'title' => $this->pageinfo['title'],
651 'message' => $lang->getLL('pageIsMisconfiguredExternalLinkMessage'),
652 'state' => InfoboxViewHelper::STATE_ERROR
653 ]);
654 $content .= $view->render();
655 } else {
656 $externalUrl = htmlspecialchars(GeneralUtility::makeInstance(PageRepository::class)->getExtURL($this->pageinfo));
657 if ($externalUrl !== false) {
658 $externalUrlHtml = '<a href="' . $externalUrl . '" target="_blank" rel="noopener">' . $externalUrl . '</a>';
659 $view->assignMultiple([
660 'title' => $this->pageinfo['title'],
661 'message' => sprintf($lang->getLL('pageIsExternalLinkMessage'), $externalUrlHtml),
662 'state' => InfoboxViewHelper::STATE_INFO
663 ]);
664 $content .= $view->render();
665 }
666 }
667 }
668 // If content from different pid is displayed
669 if ($this->pageinfo['content_from_pid']) {
670 $contentPage = BackendUtility::getRecord('pages', (int)$this->pageinfo['content_from_pid']);
671 $linkToPid = $this->local_linkThisScript(['id' => $this->pageinfo['content_from_pid']]);
672 $title = BackendUtility::getRecordTitle('pages', $contentPage);
673 $link = '<a href="' . htmlspecialchars($linkToPid) . '">' . htmlspecialchars($title) . ' (PID ' . (int)$this->pageinfo['content_from_pid'] . ')</a>';
674 $message = sprintf($lang->getLL('content_from_pid_title'), $link);
675 $view->assignMultiple([
676 'title' => $title,
677 'message' => $message,
678 'state' => InfoboxViewHelper::STATE_INFO
679 ]);
680 $content .= $view->render();
681 } else {
682 $links = $this->getPageLinksWhereContentIsAlsoShownOn($this->pageinfo['uid']);
683 if (!empty($links)) {
684 $message = sprintf($lang->getLL('content_on_pid_title'), $links);
685 $view->assignMultiple([
686 'title' => '',
687 'message' => $message,
688 'state' => InfoboxViewHelper::STATE_INFO
689 ]);
690 $content .= $view->render();
691 }
692 }
693 return $content;
694 }
695
696 /**
697 * Get all pages with links where the content of a page $pageId is also shown on
698 *
699 * @param int $pageId
700 * @return string
701 */
702 protected function getPageLinksWhereContentIsAlsoShownOn($pageId): string
703 {
704 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
705 $queryBuilder->getRestrictions()->removeAll();
706 $queryBuilder->getRestrictions()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
707 $queryBuilder
708 ->select('*')
709 ->from('pages')
710 ->where($queryBuilder->expr()->eq('content_from_pid', $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)));
711
712 $links = [];
713 $rows = $queryBuilder->execute()->fetchAll();
714 if (!empty($rows)) {
715 foreach ($rows as $row) {
716 $linkToPid = $this->local_linkThisScript(['id' => $row['uid']]);
717 $title = BackendUtility::getRecordTitle('pages', $row);
718 $link = '<a href="' . htmlspecialchars($linkToPid) . '">' . htmlspecialchars($title) . ' (PID ' . (int)$row['uid'] . ')</a>';
719 $links[] = $link;
720 }
721 }
722 return implode(', ', $links);
723 }
724
725 /**
726 * @return string $title
727 */
728 protected function getLocalizedPageTitle(): string
729 {
730 if ($this->current_sys_language > 0) {
731 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
732 ->getQueryBuilderForTable('pages');
733 $queryBuilder->getRestrictions()
734 ->removeAll()
735 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
736 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
737 $localizedPage = $queryBuilder
738 ->select('*')
739 ->from('pages')
740 ->where(
741 $queryBuilder->expr()->eq(
742 $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
743 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
744 ),
745 $queryBuilder->expr()->eq(
746 $GLOBALS['TCA']['pages']['ctrl']['languageField'],
747 $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT)
748 )
749 )
750 ->setMaxResults(1)
751 ->execute()
752 ->fetch();
753 BackendUtility::workspaceOL('pages', $localizedPage);
754 return $localizedPage['title'];
755 }
756 return $this->pageinfo['title'];
757 }
758
759 /**
760 * Main function.
761 * Creates some general objects and calls other functions for the main rendering of module content.
762 *
763 * @param ServerRequestInterface $request
764 */
765 protected function main(ServerRequestInterface $request = null): void
766 {
767 $request = $request ?: $GLOBALS['TYPO3_REQUEST'];
768 $lang = $this->getLanguageService();
769 // Access check...
770 // The page will show only if there is a valid page and if this page may be viewed by the user
771 $access = is_array($this->pageinfo);
772 // Content
773 $content = '';
774 if ($this->id && $access) {
775 // Initialize permission settings:
776 $this->CALC_PERMS = $this->getBackendUser()->calcPerms($this->pageinfo);
777 $this->EDIT_CONTENT = $this->isContentEditable();
778
779 $this->moduleTemplate->getDocHeaderComponent()->setMetaInformation($this->pageinfo);
780
781 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
782
783 $this->moduleTemplate->addJavaScriptCode('mainJsFunctions', '
784 if (top.fsMod) {
785 top.fsMod.recentIds["web"] = ' . (int)$this->id . ';
786 top.fsMod.navFrameHighlightedID["web"] = top.fsMod.currentBank + "_" + ' . (int)$this->id . ';
787 }
788 ' . ($this->popView ? BackendUtility::viewOnClick($this->id, '', BackendUtility::BEgetRootLine($this->id)) : '') . '
789 function deleteRecord(table,id,url) { //
790 window.location.href = ' . GeneralUtility::quoteJSvalue((string)$uriBuilder->buildUriFromRoute('tce_db') . '&cmd[')
791 . ' + table + "][" + id + "][delete]=1&redirect=" + encodeURIComponent(url);
792 return false;
793 }
794 ');
795
796 // Find backend layout / columns
797 $backendLayout = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getSelectedBackendLayout', $this->id, $this);
798 if (!empty($backendLayout['__colPosList'])) {
799 $this->colPosList = implode(',', $backendLayout['__colPosList']);
800 }
801 // Removing duplicates, if any
802 $this->colPosList = array_unique(GeneralUtility::intExplode(',', $this->colPosList));
803 // Accessible columns
804 if (isset($this->modSharedTSconfig['properties']['colPos_list']) && trim($this->modSharedTSconfig['properties']['colPos_list']) !== '') {
805 $this->activeColPosList = array_unique(GeneralUtility::intExplode(',', trim($this->modSharedTSconfig['properties']['colPos_list'])));
806 // Match with the list which is present in the colPosList for the current page
807 if (!empty($this->colPosList) && !empty($this->activeColPosList)) {
808 $this->activeColPosList = array_unique(array_intersect(
809 $this->activeColPosList,
810 $this->colPosList
811 ));
812 }
813 } else {
814 $this->activeColPosList = $this->colPosList;
815 }
816 $this->activeColPosList = implode(',', $this->activeColPosList);
817 $this->colPosList = implode(',', $this->colPosList);
818
819 $content .= $this->getHeaderFlashMessagesForCurrentPid();
820
821 // Render the primary module content:
822 if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) {
823 $content .= '<form action="' . htmlspecialchars((string)$uriBuilder->buildUriFromRoute($this->moduleName, ['id' => $this->id, 'imagemode' => $this->imagemode])) . '" id="PageLayoutController" method="post">';
824 // Page title
825 $content .= '<h1 class="t3js-title-inlineedit">' . htmlspecialchars($this->getLocalizedPageTitle()) . '</h1>';
826 // All other listings
827 $content .= $this->renderContent();
828 }
829 $content .= '</form>';
830 $content .= $this->searchContent;
831 // Setting up the buttons for the docheader
832 $this->makeButtons($request);
833
834 // Create LanguageMenu
835 $this->makeLanguageMenu();
836 } else {
837 $this->moduleTemplate->addJavaScriptCode(
838 'mainJsFunctions',
839 'if (top.fsMod) top.fsMod.recentIds["web"] = ' . (int)$this->id . ';'
840 );
841 $content .= '<h1>' . htmlspecialchars($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) . '</h1>';
842 $view = GeneralUtility::makeInstance(StandaloneView::class);
843 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Templates/InfoBox.html'));
844 $view->assignMultiple([
845 'title' => $lang->getLL('clickAPage_header'),
846 'message' => $lang->getLL('clickAPage_content'),
847 'state' => InfoboxViewHelper::STATE_INFO
848 ]);
849 $content .= $view->render();
850 }
851 // Set content
852 $this->moduleTemplate->setContent($content);
853 }
854
855 /**
856 * Rendering content
857 *
858 * @return string
859 */
860 protected function renderContent(): string
861 {
862 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
863
864 $this->moduleTemplate->getPageRenderer()->loadJquery();
865 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
866 $dbList = GeneralUtility::makeInstance(PageLayoutView::class);
867 $dbList->thumbs = $this->imagemode;
868 $dbList->no_noWrap = 1;
869 $dbList->descrTable = $this->descrTable;
870 $this->pointer = MathUtility::forceIntegerInRange($this->pointer, 0, 100000);
871 $dbList->script = (string)$uriBuilder->buildUriFromRoute($this->moduleName);
872 $dbList->showIcon = 0;
873 $dbList->setLMargin = 0;
874 $dbList->doEdit = $this->EDIT_CONTENT;
875 $dbList->ext_CALC_PERMS = $this->CALC_PERMS;
876 $dbList->agePrefixes = $this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.minutesHoursDaysYears');
877 $dbList->id = $this->id;
878 $dbList->nextThree = MathUtility::forceIntegerInRange($this->modTSconfig['properties']['editFieldsAtATime'], 0, 10);
879 $dbList->option_newWizard = empty($this->modTSconfig['properties']['disableNewContentElementWizard']);
880 $dbList->defLangBinding = !empty($this->modTSconfig['properties']['defLangBinding']);
881 if (!$dbList->nextThree) {
882 $dbList->nextThree = 1;
883 }
884 // Create menu for selecting a table to jump to (this is, if more than just pages/tt_content elements are found on the page!)
885 // also fills $dbList->activeTables
886 $dbList->getTableMenu($this->id);
887 // Initialize other variables:
888 $tableOutput = [];
889 $tableJSOutput = [];
890 $CMcounter = 0;
891 // Traverse the list of table names which has records on this page (that array is populated
892 // by the $dblist object during the function getTableMenu()):
893 foreach ($dbList->activeTables as $table => $value) {
894 $h_func = '';
895 $h_func_b = '';
896 if (!isset($dbList->externalTables[$table])) {
897 // Boolean: Display up/down arrows and edit icons for tt_content records
898 $dbList->tt_contentConfig['showCommands'] = 1;
899 // Boolean: Display info-marks or not
900 $dbList->tt_contentConfig['showInfo'] = 1;
901 // Setting up the tt_content columns to show:
902 if (is_array($GLOBALS['TCA']['tt_content']['columns']['colPos']['config']['items'])) {
903 $colList = [];
904 $tcaItems = GeneralUtility::callUserFunction(BackendLayoutView::class . '->getColPosListItemsParsed', $this->id, $this);
905 foreach ($tcaItems as $temp) {
906 $colList[] = $temp[1];
907 }
908 } else {
909 // ... should be impossible that colPos has no array. But this is the fallback should it make any sense:
910 $colList = ['1', '0', '2', '3'];
911 }
912 if ($this->colPosList !== '') {
913 $colList = array_intersect(GeneralUtility::intExplode(',', $this->colPosList), $colList);
914 }
915 // The order of the rows: Default is left(1), Normal(0), right(2), margin(3)
916 $dbList->tt_contentConfig['cols'] = implode(',', $colList);
917 $dbList->tt_contentConfig['activeCols'] = $this->activeColPosList;
918 $dbList->tt_contentConfig['showHidden'] = $this->MOD_SETTINGS['tt_content_showHidden'];
919 $dbList->tt_contentConfig['sys_language_uid'] = (int)$this->current_sys_language;
920 // If the function menu is set to "Language":
921 if ($this->MOD_SETTINGS['function'] == 2) {
922 $dbList->tt_contentConfig['languageMode'] = 1;
923 $dbList->tt_contentConfig['languageCols'] = $this->MOD_MENU['language'];
924 $dbList->tt_contentConfig['languageColsPointer'] = $this->current_sys_language;
925 }
926 // Toggle hidden ContentElements
927 $numberOfHiddenElements = $this->getNumberOfHiddenElements($dbList->tt_contentConfig);
928 if ($numberOfHiddenElements > 0) {
929 $h_func_b = '
930 <div class="checkbox">
931 <label for="checkTt_content_showHidden">
932 <input type="checkbox" id="checkTt_content_showHidden" class="checkbox" name="SET[tt_content_showHidden]" value="1" ' . ($this->MOD_SETTINGS['tt_content_showHidden'] ? 'checked="checked"' : '') . ' />
933 ' . htmlspecialchars($this->getLanguageService()->getLL('hiddenCE')) . ' (<span class="t3js-hidden-counter">' . $numberOfHiddenElements . '</span>)
934 </label>
935 </div>';
936 }
937 } else {
938 if (isset($this->MOD_SETTINGS) && isset($this->MOD_MENU)) {
939 $h_func = BackendUtility::getFuncMenu($this->id, 'SET[' . $table . ']', $this->MOD_SETTINGS[$table], $this->MOD_MENU[$table], '', '');
940 }
941 }
942 // Start the dblist object:
943 $dbList->itemsLimitSingleTable = 1000;
944 $dbList->start($this->id, $table, $this->pointer, $this->search_field, $this->search_levels, $this->showLimit);
945 $dbList->counter = $CMcounter;
946 $dbList->ext_function = $this->MOD_SETTINGS['function'];
947 // Generate the list of elements here:
948 $dbList->generateList();
949 // Adding the list content to the tableOutput variable:
950 $tableOutput[$table] = $h_func . $dbList->HTMLcode . $h_func_b;
951 // ... and any accumulated JavaScript goes the same way!
952 $tableJSOutput[$table] = $dbList->JScode;
953 // Increase global counter:
954 $CMcounter += $dbList->counter;
955 // Reset variables after operation:
956 $dbList->HTMLcode = '';
957 $dbList->JScode = '';
958 }
959 // END: traverse tables
960 // For Context Sensitive Menus:
961 // Init the content
962 $content = '';
963 // Additional header content
964 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawHeaderHook'] ?? [] as $hook) {
965 $params = [];
966 $content .= GeneralUtility::callUserFunction($hook, $params, $this);
967 }
968 // Add the content for each table we have rendered (traversing $tableOutput variable)
969 foreach ($tableOutput as $table => $output) {
970 $content .= $output;
971 }
972 // Making search form:
973 if (!$this->modTSconfig['properties']['disableSearchBox'] && ($dbList->counter > 0 || $this->currentPageHasSubPages())) {
974 $this->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ToggleSearchToolbox');
975 $toggleSearchFormButton = $this->buttonBar->makeLinkButton()
976 ->setClasses('t3js-toggle-search-toolbox')
977 ->setTitle($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.title.searchIcon'))
978 ->setIcon($this->iconFactory->getIcon('actions-search', Icon::SIZE_SMALL))
979 ->setHref('#');
980 $this->buttonBar->addButton($toggleSearchFormButton, ButtonBar::BUTTON_POSITION_LEFT, 4);
981 $this->searchContent = $dbList->getSearchBox();
982 }
983 // Additional footer content
984 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/db_layout.php']['drawFooterHook'] ?? [] as $hook) {
985 $params = [];
986 $content .= GeneralUtility::callUserFunction($hook, $params, $this);
987 }
988 return $content;
989 }
990
991 /**
992 * @return ModuleTemplate
993 */
994 protected function getModuleTemplate(): ModuleTemplate
995 {
996 return $this->moduleTemplate;
997 }
998
999 /***************************
1000 *
1001 * Sub-content functions, rendering specific parts of the module content.
1002 *
1003 ***************************/
1004 /**
1005 * This creates the buttons for the modules
1006 * @param ServerRequestInterface $request
1007 */
1008 protected function makeButtons(ServerRequestInterface $request): void
1009 {
1010 if ($this->MOD_SETTINGS['function'] == 1 || $this->MOD_SETTINGS['function'] == 2) {
1011 // Add CSH (Context Sensitive Help) icon to tool bar
1012 $contextSensitiveHelpButton = $this->buttonBar->makeHelpButton()
1013 ->setModuleName($this->descrTable)
1014 ->setFieldName('columns_' . $this->MOD_SETTINGS['function']);
1015 $this->buttonBar->addButton($contextSensitiveHelpButton);
1016 }
1017 $lang = $this->getLanguageService();
1018 // View page
1019 if (!VersionState::cast($this->pageinfo['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
1020 $viewButton = $this->buttonBar->makeLinkButton()
1021 ->setOnClick(BackendUtility::viewOnClick($this->pageinfo['uid'], '', BackendUtility::BEgetRootLine($this->pageinfo['uid'])))
1022 ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
1023 ->setIcon($this->iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL))
1024 ->setHref('#');
1025
1026 $this->buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1027 }
1028 // Shortcut
1029 $shortcutButton = $this->buttonBar->makeShortcutButton()
1030 ->setModuleName($this->moduleName)
1031 ->setGetVariables([
1032 'id',
1033 'route',
1034 'edit_record',
1035 'pointer',
1036 'new_unique_uid',
1037 'search_field',
1038 'search_levels',
1039 'showLimit'
1040 ])
1041 ->setSetVariables(array_keys($this->MOD_MENU));
1042 $this->buttonBar->addButton($shortcutButton);
1043
1044 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1045 // Cache
1046 if (empty($this->modTSconfig['properties']['disableAdvanced'])) {
1047 $clearCacheButton = $this->buttonBar->makeLinkButton()
1048 ->setHref((string)$uriBuilder->buildUriFromRoute($this->moduleName, ['id' => $this->pageinfo['uid'], 'clear_cache' => '1']))
1049 ->setTitle($lang->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.clear_cache'))
1050 ->setIcon($this->iconFactory->getIcon('actions-system-cache-clear', Icon::SIZE_SMALL));
1051 $this->buttonBar->addButton($clearCacheButton, ButtonBar::BUTTON_POSITION_RIGHT, 1);
1052 }
1053 if (empty($this->modTSconfig['properties']['disableIconToolbar'])) {
1054 // Edit page properties and page language overlay icons
1055 if ($this->isPageEditable() && $this->getBackendUser()->checkLanguageAccess(0)) {
1056 /** @var \TYPO3\CMS\Core\Http\NormalizedParams */
1057 $normalizedParams = $request->getAttribute('normalizedParams');
1058 // Edit localized pages only when one specific language is selected
1059 if ($this->MOD_SETTINGS['function'] == 1 && $this->current_sys_language > 0) {
1060 $localizationParentField = $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'];
1061 $languageField = $GLOBALS['TCA']['pages']['ctrl']['languageField'];
1062 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
1063 ->getQueryBuilderForTable('pages');
1064 $queryBuilder->getRestrictions()
1065 ->removeAll()
1066 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1067 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1068 $overlayRecord = $queryBuilder
1069 ->select('uid')
1070 ->from('pages')
1071 ->where(
1072 $queryBuilder->expr()->eq(
1073 $localizationParentField,
1074 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1075 ),
1076 $queryBuilder->expr()->eq(
1077 $languageField,
1078 $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT)
1079 )
1080 )
1081 ->setMaxResults(1)
1082 ->execute()
1083 ->fetch();
1084 // Edit button
1085 $urlParameters = [
1086 'edit' => [
1087 'pages' => [
1088 $overlayRecord['uid'] => 'edit'
1089 ]
1090 ],
1091 'returnUrl' => $normalizedParams->getRequestUri(),
1092 ];
1093
1094 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1095 $editLanguageButton = $this->buttonBar->makeLinkButton()
1096 ->setHref($url)
1097 ->setTitle($lang->getLL('editPageLanguageOverlayProperties'))
1098 ->setIcon($this->iconFactory->getIcon('mimetypes-x-content-page-language-overlay', Icon::SIZE_SMALL));
1099 $this->buttonBar->addButton($editLanguageButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1100 }
1101 $urlParameters = [
1102 'edit' => [
1103 'pages' => [
1104 $this->id => 'edit'
1105 ]
1106 ],
1107 'returnUrl' => $normalizedParams->getRequestUri(),
1108 ];
1109 $url = (string)$uriBuilder->buildUriFromRoute('record_edit', $urlParameters);
1110 $editPageButton = $this->buttonBar->makeLinkButton()
1111 ->setHref($url)
1112 ->setTitle($lang->getLL('editPageProperties'))
1113 ->setIcon($this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL));
1114 $this->buttonBar->addButton($editPageButton, ButtonBar::BUTTON_POSITION_LEFT, 3);
1115 }
1116 }
1117 }
1118
1119 /*******************************
1120 *
1121 * Other functions
1122 *
1123 ******************************/
1124 /**
1125 * Returns the number of hidden elements (including those hidden by start/end times)
1126 * on the current page (for the current sys_language)
1127 *
1128 * @param array $contentConfig
1129 * @return int
1130 */
1131 protected function getNumberOfHiddenElements(array $contentConfig = []): int
1132 {
1133 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
1134 $queryBuilder->getRestrictions()
1135 ->removeAll()
1136 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1137 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1138
1139 $queryBuilder
1140 ->count('uid')
1141 ->from('tt_content')
1142 ->where(
1143 $queryBuilder->expr()->eq(
1144 'pid',
1145 $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)
1146 )
1147 );
1148
1149 if (!empty($contentConfig['languageCols']) && is_array($contentConfig['languageCols'])) {
1150 // Multi-language view is active
1151 if ($this->current_sys_language > 0) {
1152 $queryBuilder->andWhere(
1153 $queryBuilder->expr()->in(
1154 'sys_language_uid',
1155 [0, $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT)]
1156 )
1157 );
1158 }
1159 } else {
1160 $queryBuilder->andWhere(
1161 $queryBuilder->expr()->eq(
1162 'sys_language_uid',
1163 $queryBuilder->createNamedParameter($this->current_sys_language, \PDO::PARAM_INT)
1164 )
1165 );
1166 }
1167
1168 if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['disabled'])) {
1169 $andWhere[] = $queryBuilder->expr()->neq(
1170 'hidden',
1171 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1172 );
1173 }
1174
1175 if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['starttime'])) {
1176 $andWhere[] = $queryBuilder->expr()->andX(
1177 $queryBuilder->expr()->neq(
1178 'starttime',
1179 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1180 ),
1181 $queryBuilder->expr()->gt(
1182 'starttime',
1183 $queryBuilder->createNamedParameter($GLOBALS['SIM_ACCESS_TIME'], \PDO::PARAM_INT)
1184 )
1185 );
1186 }
1187
1188 if (!empty($GLOBALS['TCA']['tt_content']['ctrl']['enablecolumns']['endtime'])) {
1189 $andWhere[] = $queryBuilder->expr()->andX(
1190 $queryBuilder->expr()->neq(
1191 'endtime',
1192 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
1193 ),
1194 $queryBuilder->expr()->lte(
1195 'endtime',
1196 $queryBuilder->createNamedParameter($GLOBALS['SIM_ACCESS_TIME'], \PDO::PARAM_INT)
1197 )
1198 );
1199 }
1200
1201 if (!empty($andWhere)) {
1202 $queryBuilder->andWhere(
1203 $queryBuilder->expr()->orX(...$andWhere)
1204 );
1205 }
1206
1207 $count = $queryBuilder
1208 ->execute()
1209 ->fetchColumn(0);
1210
1211 return (int)$count;
1212 }
1213
1214 /**
1215 * Returns URL to the current script.
1216 * In particular the "popView" and "new_unique_uid" Get vars are unset.
1217 *
1218 * @param array $params Parameters array, merged with global GET vars.
1219 * @return string URL
1220 */
1221 protected function local_linkThisScript($params): string
1222 {
1223 $params['popView'] = '';
1224 $params['new_unique_uid'] = '';
1225 return GeneralUtility::linkThisScript($params);
1226 }
1227
1228 /**
1229 * Check if page can be edited by current user
1230 *
1231 * @return bool
1232 */
1233 protected function isPageEditable(): bool
1234 {
1235 return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::PAGE_EDIT);
1236 }
1237
1238 /**
1239 * Check if page can be edited by current user
1240 *
1241 * @return bool
1242 */
1243 protected function pageIsNotLockedForEditors(): bool
1244 {
1245 return $this->isPageEditable();
1246 }
1247
1248 /**
1249 * Check if content can be edited by current user
1250 *
1251 * @return bool
1252 */
1253 protected function isContentEditable(): bool
1254 {
1255 return !$this->pageinfo['editlock'] && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT);
1256 }
1257
1258 /**
1259 * Check if content can be edited by current user
1260 *
1261 * @return bool
1262 */
1263 protected function contentIsNotLockedForEditors(): bool
1264 {
1265 return $this->isContentEditable();
1266 }
1267
1268 /**
1269 * Returns LanguageService
1270 *
1271 * @return \TYPO3\CMS\Core\Localization\LanguageService
1272 */
1273 protected function getLanguageService(): LanguageService
1274 {
1275 return $GLOBALS['LANG'];
1276 }
1277
1278 /**
1279 * Returns the current BE user.
1280 *
1281 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
1282 */
1283 protected function getBackendUser(): BackendUserAuthentication
1284 {
1285 return $GLOBALS['BE_USER'];
1286 }
1287
1288 /**
1289 * Returns current PageRenderer
1290 *
1291 * @return PageRenderer
1292 */
1293 protected function getPageRenderer(): PageRenderer
1294 {
1295 return GeneralUtility::makeInstance(PageRenderer::class);
1296 }
1297
1298 /**
1299 * Make the LanguageMenu
1300 */
1301 protected function makeLanguageMenu(): void
1302 {
1303 if (count($this->MOD_MENU['language']) > 1) {
1304 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
1305 $lang = $this->getLanguageService();
1306 $languageMenu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
1307 $languageMenu->setIdentifier('languageMenu');
1308 foreach ($this->MOD_MENU['language'] as $key => $language) {
1309 $menuItem = $languageMenu
1310 ->makeMenuItem()
1311 ->setTitle($language)
1312 ->setHref((string)$uriBuilder->buildUriFromRoute($this->moduleName) . '&id=' . $this->id . '&SET[language]=' . $key);
1313 if ((int)$this->current_sys_language === $key) {
1314 $menuItem->setActive(true);
1315 }
1316 $languageMenu->addMenuItem($menuItem);
1317 }
1318 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($languageMenu);
1319 }
1320 }
1321
1322 /**
1323 * Checks whether the current page has sub pages
1324 *
1325 * @return bool
1326 */
1327 protected function currentPageHasSubPages(): bool
1328 {
1329 /** @var QueryBuilder $queryBuilder */
1330 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
1331 $queryBuilder->getRestrictions()
1332 ->removeAll()
1333 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
1334 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
1335
1336 // get workspace id
1337 $workspaceId = (int)$this->getBackendUser()->workspace;
1338 $comparisonExpression = $workspaceId === 0 ? 'neq' : 'eq';
1339
1340 $count = $queryBuilder
1341 ->count('uid')
1342 ->from('pages')
1343 ->where(
1344 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($this->id, \PDO::PARAM_INT)),
1345 $queryBuilder->expr()->eq(
1346 't3ver_wsid',
1347 $queryBuilder->createNamedParameter($workspaceId, \PDO::PARAM_INT)
1348 ),
1349 $queryBuilder->expr()->{$comparisonExpression}(
1350 'pid',
1351 $queryBuilder->createNamedParameter(-1, \PDO::PARAM_INT)
1352 )
1353 )
1354 ->execute()
1355 ->fetchColumn(0);
1356
1357 return (bool)$count;
1358 }
1359 }