224ef97cc4eb19370183d1413fa068af2f535d31
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / BackendController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository;
20 use TYPO3\CMS\Backend\Module\ModuleLoader;
21 use TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
25 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26 use TYPO3\CMS\Core\Imaging\IconFactory;
27 use TYPO3\CMS\Core\Page\PageRenderer;
28 use TYPO3\CMS\Core\Type\Bitmask\Permission;
29 use TYPO3\CMS\Core\Type\File\ImageInfo;
30 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32 use TYPO3\CMS\Core\Utility\MathUtility;
33 use TYPO3\CMS\Core\Utility\PathUtility;
34 use TYPO3\CMS\Fluid\View\StandaloneView;
35
36 /**
37 * Class for rendering the TYPO3 backend
38 */
39 class BackendController
40 {
41 /**
42 * @var string
43 */
44 protected $content = '';
45
46 /**
47 * @var string
48 */
49 protected $css = '';
50
51 /**
52 * @var array
53 */
54 protected $cssFiles = [];
55
56 /**
57 * @var string
58 */
59 protected $js = '';
60
61 /**
62 * @var array
63 */
64 protected $jsFiles = [];
65
66 /**
67 * @var array
68 */
69 protected $toolbarItems = [];
70
71 /**
72 * @var bool
73 */
74 protected $debug;
75
76 /**
77 * @var string
78 */
79 protected $templatePath = 'EXT:backend/Resources/Private/Templates/';
80
81 /**
82 * @var string
83 */
84 protected $partialPath = 'EXT:backend/Resources/Private/Partials/';
85
86 /**
87 * @var \TYPO3\CMS\Backend\Domain\Repository\Module\BackendModuleRepository
88 */
89 protected $backendModuleRepository;
90
91 /**
92 * @var \TYPO3\CMS\Backend\Module\ModuleLoader Object for loading backend modules
93 */
94 protected $moduleLoader;
95
96 /**
97 * @var PageRenderer
98 */
99 protected $pageRenderer;
100
101 /**
102 * @var IconFactory
103 */
104 protected $iconFactory;
105
106 /**
107 * Constructor
108 */
109 public function __construct()
110 {
111 $this->getLanguageService()->includeLLFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
112
113 $this->backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
114 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
115
116 // Set debug flag for BE development only
117 $this->debug = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] === 1;
118 // Initializes the backend modules structure for use later.
119 $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
120 $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
121 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
122 $this->pageRenderer->loadExtJS();
123 // included for the module menu JavaScript, please note that this is subject to change
124 $this->pageRenderer->loadJquery();
125 $this->pageRenderer->addExtDirectCode();
126 // Add default BE javascript
127 $this->jsFiles = [
128 'md5' => 'EXT:backend/Resources/Public/JavaScript/md5.js',
129 'evalfield' => 'EXT:backend/Resources/Public/JavaScript/jsfunc.evalfield.js',
130 'backend' => 'EXT:backend/Resources/Public/JavaScript/backend.js',
131 ];
132 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LoginRefresh', 'function(LoginRefresh) {
133 LoginRefresh.setIntervalTime(' . MathUtility::forceIntegerInRange((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['sessionTimeout'] - 60, 60) . ');
134 LoginRefresh.setLoginFramesetUrl(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('login_frameset')) . ');
135 LoginRefresh.setLogoutUrl(' . GeneralUtility::quoteJSvalue(BackendUtility::getModuleUrl('logout')) . ');
136 LoginRefresh.initialize();
137 }');
138
139 // load module menu
140 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ModuleMenu');
141
142 // load Toolbar class
143 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Toolbar');
144
145 // load Utility class
146 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Utility');
147
148 // load Notification functionality
149 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Notification');
150
151 // load Modals
152 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Modal');
153
154 // load ContextMenu
155 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
156
157 // load the storage API and fill the UC into the PersistentStorage, so no additional AJAX call is needed
158 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage');
159 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Storage/Persistent', 'function(PersistentStorage) {
160 PersistentStorage.load(' . json_encode($this->getBackendUser()->uc) . ');
161 }');
162
163 // load debug console
164 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DebugConsole');
165
166 $this->pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_core.xlf');
167 $this->pageRenderer->addInlineLanguageLabelFile('EXT:lang/Resources/Private/Language/locallang_misc.xlf');
168
169 $this->pageRenderer->addInlineSetting('ShowItem', 'moduleUrl', BackendUtility::getModuleUrl('show_item'));
170 $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
171 $this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', BackendUtility::getModuleUrl('db_new'));
172 $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
173 $this->pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', BackendUtility::getModuleUrl('tce_db'));
174 $this->pageRenderer->addInlineSetting('WebLayout', 'moduleUrl', BackendUtility::getModuleUrl('web_layout'));
175
176 $this->css = '';
177
178 $this->initializeToolbarItems();
179 $this->executeHook('constructPostProcess');
180 }
181
182 /**
183 * Initialize toolbar item objects
184 *
185 * @throws \RuntimeException
186 */
187 protected function initializeToolbarItems()
188 {
189 $toolbarItemInstances = [];
190 foreach ($GLOBALS['TYPO3_CONF_VARS']['BE']['toolbarItems'] ?? [] as $className) {
191 $toolbarItemInstance = GeneralUtility::makeInstance($className);
192 if (!$toolbarItemInstance instanceof ToolbarItemInterface) {
193 throw new \RuntimeException(
194 'class ' . $className . ' is registered as toolbar item but does not implement'
195 . ToolbarItemInterface::class,
196 1415958218
197 );
198 }
199 $index = (int)$toolbarItemInstance->getIndex();
200 if ($index < 0 || $index > 100) {
201 throw new \RuntimeException(
202 'getIndex() must return an integer between 0 and 100',
203 1415968498
204 );
205 }
206 // Find next free position in array
207 while (array_key_exists($index, $toolbarItemInstances)) {
208 $index++;
209 }
210 $toolbarItemInstances[$index] = $toolbarItemInstance;
211 }
212 ksort($toolbarItemInstances);
213 $this->toolbarItems = $toolbarItemInstances;
214 }
215
216 /**
217 * Injects the request object for the current request or subrequest
218 * As this controller goes only through the render() method, it is rather simple for now
219 *
220 * @param ServerRequestInterface $request the current request
221 * @param ResponseInterface $response
222 * @return ResponseInterface the response with the content
223 */
224 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
225 {
226 $this->render();
227 $response->getBody()->write($this->content);
228 return $response;
229 }
230
231 /**
232 * Main function generating the BE scaffolding
233 */
234 public function render()
235 {
236 $this->executeHook('renderPreProcess');
237
238 // Prepare the scaffolding, at this point extension may still add javascript and css
239 $view = $this->getFluidTemplateObject($this->templatePath . 'Backend/Main.html');
240
241 $view->assign('moduleMenu', $this->generateModuleMenu());
242 $view->assign('topbar', $this->renderTopbar());
243
244 /******************************************************
245 * Now put the complete backend document together
246 ******************************************************/
247 foreach ($this->cssFiles as $cssFileName => $cssFile) {
248 $this->pageRenderer->addCssFile($cssFile);
249 // Load additional css files to overwrite existing core styles
250 if (!empty($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName])) {
251 $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName]);
252 }
253 }
254 if (!empty($this->css)) {
255 $this->pageRenderer->addCssInlineBlock('BackendInlineCSS', $this->css);
256 }
257 foreach ($this->jsFiles as $jsFile) {
258 $this->pageRenderer->addJsFile($jsFile);
259 }
260 $this->generateJavascript();
261 $this->pageRenderer->addJsInlineCode('BackendInlineJavascript', $this->js, false);
262 $this->loadResourcesForRegisteredNavigationComponents();
263 // @todo: remove this when ExtJS is removed
264 $states = $this->getBackendUser()->uc['BackendComponents']['States'];
265 $this->pageRenderer->addExtOnReadyCode('
266 require([\'TYPO3/CMS/Backend/Storage/Persistent\'], function(PersistentStorage) {
267 var TYPO3ExtJSStateProviderBridge = function() {};
268 Ext.extend(TYPO3ExtJSStateProviderBridge, Ext.state.Provider, {
269 state: {},
270 queue: [],
271 dirty: false,
272 prefix: "BackendComponents.States.",
273 initState: function(state) {
274 if (Ext.isArray(state)) {
275 Ext.each(state, function(item) {
276 this.state[item.name] = item.value;
277 }, this);
278 } else if (Ext.isObject(state)) {
279 Ext.iterate(state, function(key, value) {
280 this.state[key] = value;
281 }, this);
282 } else {
283 this.state = {};
284 }
285 var me = this;
286 window.setInterval(function() {
287 me.submitState(me)
288 }, 750);
289 },
290 get: function(name, defaultValue) {
291 return PersistentStorage.isset(this.prefix + name) ? PersistentStorage.get(this.prefix + name) : defaultValue;
292 },
293 clear: function(name) {
294 PersistentStorage.unset(this.prefix + name);
295 },
296 set: function(name, value) {
297 if (!name) {
298 return;
299 }
300 this.queueChange(name, value);
301 },
302 queueChange: function(name, value) {
303 var o = {};
304 var i;
305 var found = false;
306
307 var lastValue = this.state[name];
308 for (i = 0; i < this.queue.length; i++) {
309 if (this.queue[i].name === name) {
310 lastValue = this.queue[i].value;
311 }
312 }
313 var changed = undefined === lastValue || lastValue !== value;
314
315 if (changed) {
316 o.name = name;
317 o.value = value;
318 for (i = 0; i < this.queue.length; i++) {
319 if (this.queue[i].name === o.name) {
320 this.queue[i] = o;
321 found = true;
322 }
323 }
324 if (false === found) {
325 this.queue.push(o);
326 }
327 this.dirty = true;
328 }
329 },
330 submitState: function(context) {
331 if (!context.dirty) {
332 return;
333 }
334 for (var i = 0; i < context.queue.length; ++i) {
335 PersistentStorage.set(context.prefix + context.queue[i].name, context.queue[i].value).done(function() {
336 if (!context.dirty) {
337 context.queue = [];
338 }
339 });
340 }
341 context.dirty = false;
342 }
343 });
344 Ext.state.Manager.setProvider(new TYPO3ExtJSStateProviderBridge());
345 Ext.state.Manager.getProvider().initState(' . (!empty($states) ? json_encode($states) : []) . ');
346 });');
347 // Set document title:
348 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [TYPO3 CMS ' . TYPO3_version . ']' : 'TYPO3 CMS ' . TYPO3_version;
349 // Renders the module page
350 $this->content = $this->getDocumentTemplate()->render($title, $view->render());
351 $hookConfiguration = ['content' => &$this->content];
352 $this->executeHook('renderPostProcess', $hookConfiguration);
353 }
354
355 /**
356 * Renders the topbar, containing the backend logo, sitename etc.
357 *
358 * @return string
359 */
360 protected function renderTopbar()
361 {
362 $view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
363
364 // Extension Configuration to find the TYPO3 logo in the left corner
365 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
366 $logoPath = '';
367 if (!empty($extConf['backendLogo'])) {
368 $customBackendLogo = GeneralUtility::getFileAbsFileName($extConf['backendLogo']);
369 if (!empty($customBackendLogo)) {
370 $logoPath = $customBackendLogo;
371 }
372 }
373 // if no custom logo was set or the path is invalid, use the original one
374 if (empty($logoPath)) {
375 $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
376 $logoWidth = 22;
377 $logoHeight = 22;
378 } else {
379 // set width/height for custom logo
380 $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
381 $logoWidth = $imageInfo->getWidth() ?? '22';
382 $logoHeight = $imageInfo->getHeight() ?? '22';
383
384 // High-resolution?
385 if (strpos($logoPath, '@2x.') !== false) {
386 $logoWidth /= 2;
387 $logoHeight /= 2;
388 }
389 }
390
391 $view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
392 $view->assign('logoWidth', $logoWidth);
393 $view->assign('logoHeight', $logoHeight);
394 $view->assign('applicationVersion', TYPO3_version);
395 $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
396 $view->assign('toolbar', $this->renderToolbar());
397
398 return $view->render();
399 }
400
401 /**
402 * Loads the css and javascript files of all registered navigation widgets
403 */
404 protected function loadResourcesForRegisteredNavigationComponents()
405 {
406 if (!is_array($GLOBALS['TBE_MODULES']['_navigationComponents'])) {
407 return;
408 }
409 $loadedComponents = [];
410 foreach ($GLOBALS['TBE_MODULES']['_navigationComponents'] as $module => $info) {
411 if (in_array($info['componentId'], $loadedComponents)) {
412 continue;
413 }
414 $loadedComponents[] = $info['componentId'];
415 $component = strtolower(substr($info['componentId'], strrpos($info['componentId'], '-') + 1));
416 $componentDirectory = 'components/' . $component . '/';
417 if ($info['isCoreComponent']) {
418 $componentDirectory = 'Resources/Public/JavaScript/extjs/' . $componentDirectory;
419 $info['extKey'] = 'backend';
420 }
421 $absoluteComponentPath = ExtensionManagementUtility::extPath($info['extKey']) . $componentDirectory;
422 $relativeComponentPath = PathUtility::getRelativePath(PATH_site . TYPO3_mainDir, $absoluteComponentPath);
423 $cssFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'css/', 'css');
424 if (file_exists($absoluteComponentPath . 'css/loadorder.txt')) {
425 // Don't allow inclusion outside directory
426 $loadOrder = str_replace('../', '', file_get_contents($absoluteComponentPath . 'css/loadorder.txt'));
427 $cssFilesOrdered = GeneralUtility::trimExplode(LF, $loadOrder, true);
428 $cssFiles = array_merge($cssFilesOrdered, $cssFiles);
429 }
430 foreach ($cssFiles as $cssFile) {
431 $this->pageRenderer->addCssFile($relativeComponentPath . 'css/' . $cssFile);
432 }
433 $jsFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'javascript/', 'js');
434 if (file_exists($absoluteComponentPath . 'javascript/loadorder.txt')) {
435 // Don't allow inclusion outside directory
436 $loadOrder = str_replace('../', '', file_get_contents($absoluteComponentPath . 'javascript/loadorder.txt'));
437 $jsFilesOrdered = GeneralUtility::trimExplode(LF, $loadOrder, true);
438 $jsFiles = array_merge($jsFilesOrdered, $jsFiles);
439 }
440 foreach ($jsFiles as $jsFile) {
441 $this->pageRenderer->addJsFile($relativeComponentPath . 'javascript/' . $jsFile);
442 }
443 }
444 }
445
446 /**
447 * Renders the items in the top toolbar
448 *
449 * @return string top toolbar elements as HTML
450 */
451 protected function renderToolbar()
452 {
453 $toolbar = [];
454 foreach ($this->toolbarItems as $toolbarItem) {
455 /** @var \TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface $toolbarItem */
456 if ($toolbarItem->checkAccess()) {
457 $hasDropDown = (bool)$toolbarItem->hasDropDown();
458 $additionalAttributes = (array)$toolbarItem->getAdditionalAttributes();
459
460 $liAttributes = [];
461
462 // Merge class: Add dropdown class if hasDropDown, add classes from additional attributes
463 $classes = [];
464 $classes[] = 'toolbar-item';
465 $classes[] = 't3js-toolbar-item';
466 if (isset($additionalAttributes['class'])) {
467 $classes[] = $additionalAttributes['class'];
468 unset($additionalAttributes['class']);
469 }
470 $liAttributes[] = 'class="' . implode(' ', $classes) . '"';
471
472 // Add further attributes
473 foreach ($additionalAttributes as $name => $value) {
474 $liAttributes[] = $name . '="' . $value . '"';
475 }
476
477 // Create a unique id from class name
478 $className = get_class($toolbarItem);
479 $className = GeneralUtility::underscoredToLowerCamelCase($className);
480 $className = GeneralUtility::camelCaseToLowerCaseUnderscored($className);
481 $className = str_replace(['_', '\\'], '-', $className);
482 $liAttributes[] = 'id="' . $className . '"';
483
484 $toolbar[] = '<li ' . implode(' ', $liAttributes) . '>';
485
486 if ($hasDropDown) {
487 $toolbar[] = '<a href="#" class="toolbar-item-link dropdown-toggle" data-toggle="dropdown">';
488 $toolbar[] = $toolbarItem->getItem();
489 $toolbar[] = '</a>';
490 $toolbar[] = '<div class="dropdown-menu" role="menu">';
491 $toolbar[] = $toolbarItem->getDropDown();
492 $toolbar[] = '</div>';
493 } else {
494 $toolbar[] = $toolbarItem->getItem();
495 }
496 $toolbar[] = '</li>';
497 }
498 }
499 return implode(LF, $toolbar);
500 }
501
502 /**
503 * Generates the JavaScript code for the backend.
504 */
505 protected function generateJavascript()
506 {
507 $beUser = $this->getBackendUser();
508 // Needed for FormEngine manipulation (date picker)
509 $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
510 $this->pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
511
512 // If another page module was specified, replace the default Page module with the new one
513 $newPageModule = trim($beUser->getTSConfigVal('options.overridePageModule'));
514 $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
515 if (!$beUser->check('modules', $pageModule)) {
516 $pageModule = '';
517 }
518 $t3Configuration = [
519 'username' => htmlspecialchars($beUser->user['username']),
520 'pageModule' => $pageModule,
521 'inWorkspace' => $beUser->workspace !== 0,
522 'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] : false
523 ];
524 $this->js .= '
525 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
526 /**
527 * Frameset Module object
528 *
529 * Used in main modules with a frameset for submodules to keep the ID between modules
530 * Typically that is set by something like this in a Web>* sub module:
531 * if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
532 * if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
533 */
534 var fsMod = {
535 recentIds: [], // used by frameset modules to track the most recent used id for list frame.
536 navFrameHighlightedID: [], // used by navigation frames to track which row id was highlighted last time
537 currentBank: "0"
538 };
539
540 top.goToModule = function(modName, cMR_flag, addGetVars) {
541 TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
542 }
543 ' . $this->setStartupModule();
544 // Check editing of page:
545 $this->handlePageEditing();
546 }
547
548 /**
549 * Checking if the "&edit" variable was sent so we can open it for editing the page.
550 */
551 protected function handlePageEditing()
552 {
553 $beUser = $this->getBackendUser();
554 // EDIT page:
555 $editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
556 if ($editId) {
557 // Looking up the page to edit, checking permissions:
558 $where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
559 if (MathUtility::canBeInterpretedAsInteger($editId)) {
560 $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
561 } else {
562 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
563 $queryBuilder->getRestrictions()
564 ->removeAll()
565 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
566 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
567
568 $editRecord = $queryBuilder->select('*')
569 ->from('pages')
570 ->where(
571 $queryBuilder->expr()->eq(
572 'alias',
573 $queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
574 ),
575 $queryBuilder->expr()->orX(
576 $beUser->getPagePermsClause(Permission::PAGE_EDIT),
577 $beUser->getPagePermsClause(Permission::CONTENT_EDIT)
578 )
579 )
580 ->setMaxResults(1)
581 ->execute()
582 ->fetch();
583
584 if ($editRecord !== false) {
585 BackendUtility::workspaceOL('pages', $editRecord);
586 }
587 }
588 // If the page was accessible, then let the user edit it.
589 if (is_array($editRecord) && $beUser->isInWebMount($editRecord['uid'])) {
590 // Setting JS code to open editing:
591 $this->js .= '
592 // Load page to edit:
593 window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
594 ';
595 // Checking page edit parameter:
596 if (!$beUser->getTSConfigVal('options.bookmark_onEditId_dontSetPageTree')) {
597 $bookmarkKeepExpanded = $beUser->getTSConfigVal('options.bookmark_onEditId_keepExistingExpanded');
598 // Expanding page tree:
599 BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
600 }
601 } else {
602 $this->js .= '
603 // Warning about page editing:
604 require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
605 Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
606 text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:close')) . ',
607 active: true,
608 btnClass: "btn-info",
609 name: "cancel",
610 trigger: function () {
611 Modal.currentModal.trigger("modal-dismiss");
612 }
613 }])
614 });';
615 }
616 }
617 }
618
619 /**
620 * Sets the startup module from either GETvars module and modParams or user configuration.
621 *
622 * @return string the JavaScript code for the startup module
623 */
624 protected function setStartupModule()
625 {
626 $startModule = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('module'));
627 $startModuleParameters = '';
628 if (!$startModule) {
629 $beUser = $this->getBackendUser();
630 // start module on first login, will be removed once used the first time
631 if (isset($beUser->uc['startModuleOnFirstLogin'])) {
632 $startModule = $beUser->uc['startModuleOnFirstLogin'];
633 unset($beUser->uc['startModuleOnFirstLogin']);
634 $beUser->writeUC();
635 } elseif ($beUser->uc['startModule']) {
636 $startModule = $beUser->uc['startModule'];
637 }
638
639 // check if the start module has additional parameters, so a redirect to a specific
640 // action is possible
641 if (strpos($startModule, '->') !== false) {
642 list($startModule, $startModuleParameters) = explode('->', $startModule, 2);
643 }
644 }
645
646 $moduleParameters = GeneralUtility::_GET('modParams');
647 // if no GET parameters are set, check if there are parameters given from the UC
648 if (!$moduleParameters && $startModuleParameters) {
649 $moduleParameters = $startModuleParameters;
650 }
651
652 if ($startModule) {
653 return '
654 // start in module:
655 top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
656 ';
657 }
658 return '';
659 }
660
661 /**
662 * Adds a css snippet to the backend
663 *
664 * @param string $css Css snippet
665 * @throws \InvalidArgumentException
666 */
667 public function addCss($css)
668 {
669 if (!is_string($css)) {
670 throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
671 }
672 $this->css .= $css;
673 }
674
675 /**
676 * Executes defined hooks functions for the given identifier.
677 *
678 * These hook identifiers are valid:
679 * + constructPostProcess
680 * + renderPreProcess
681 * + renderPostProcess
682 *
683 * @param string $identifier Specific hook identifier
684 * @param array $hookConfiguration Additional configuration passed to hook functions
685 */
686 protected function executeHook($identifier, array $hookConfiguration = [])
687 {
688 $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
689 foreach ($options[$identifier] ?? [] as $hookFunction) {
690 GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
691 }
692 }
693
694 /**
695 * loads all modules from the repository
696 * and renders it with a template
697 *
698 * @return string
699 */
700 protected function generateModuleMenu()
701 {
702 // get all modules except the user modules for the side menu
703 $moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
704
705 $view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
706 $view->assign('modules', $moduleStorage);
707 return $view->render();
708 }
709
710 /**
711 * Returns the Module menu for the AJAX request
712 *
713 * @param ServerRequestInterface $request
714 * @param ResponseInterface $response
715 * @return ResponseInterface
716 */
717 public function getModuleMenu(ServerRequestInterface $request, ResponseInterface $response)
718 {
719 $content = $this->generateModuleMenu();
720
721 $response->getBody()->write(json_encode(['menu' => $content]));
722 return $response;
723 }
724
725 /**
726 * Returns the toolbar for the AJAX request
727 *
728 * @param ServerRequestInterface $request
729 * @param ResponseInterface $response
730 * @return ResponseInterface
731 */
732 public function getTopbar(ServerRequestInterface $request, ResponseInterface $response)
733 {
734 $response->getBody()->write(json_encode(['topbar' => $this->renderTopbar()]));
735 return $response;
736 }
737
738 /**
739 * returns a new standalone view, shorthand function
740 *
741 * @param string $templatePathAndFileName optional the path to set the template path and filename
742 * @return \TYPO3\CMS\Fluid\View\StandaloneView
743 */
744 protected function getFluidTemplateObject($templatePathAndFileName = null)
745 {
746 $view = GeneralUtility::makeInstance(StandaloneView::class);
747 $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
748 if ($templatePathAndFileName) {
749 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
750 }
751 return $view;
752 }
753
754 /**
755 * Returns LanguageService
756 *
757 * @return \TYPO3\CMS\Core\Localization\LanguageService
758 */
759 protected function getLanguageService()
760 {
761 return $GLOBALS['LANG'];
762 }
763
764 /**
765 * Returns the current BE user.
766 *
767 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
768 */
769 protected function getBackendUser()
770 {
771 return $GLOBALS['BE_USER'];
772 }
773
774 /**
775 * Returns an instance of DocumentTemplate
776 *
777 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
778 */
779 protected function getDocumentTemplate()
780 {
781 return $GLOBALS['TBE_TEMPLATE'];
782 }
783 }