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