4ad27830104c0b2ba9112fc67b76cfdba816fd2c
[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 $this->backendModuleRepository = GeneralUtility::makeInstance(BackendModuleRepository::class);
113 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
114
115 // Set debug flag for BE development only
116 $this->debug = (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] === 1;
117 // Initializes the backend modules structure for use later.
118 $this->moduleLoader = GeneralUtility::makeInstance(ModuleLoader::class);
119 $this->moduleLoader->load($GLOBALS['TBE_MODULES']);
120 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
121 $this->pageRenderer->loadExtJS();
122 // included for the module menu JavaScript, please note that this is subject to change
123 $this->pageRenderer->loadJquery();
124 $this->pageRenderer->addExtDirectCode();
125 // Add default BE javascript
126 $this->jsFiles = [
127 'locallang' => $this->getLocalLangFileName(),
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->addInlineSetting('ShowItem', 'moduleUrl', BackendUtility::getModuleUrl('show_item'));
167 $this->pageRenderer->addInlineSetting('RecordHistory', 'moduleUrl', BackendUtility::getModuleUrl('record_history'));
168 $this->pageRenderer->addInlineSetting('NewRecord', 'moduleUrl', BackendUtility::getModuleUrl('db_new'));
169 $this->pageRenderer->addInlineSetting('FormEngine', 'moduleUrl', BackendUtility::getModuleUrl('record_edit'));
170 $this->pageRenderer->addInlineSetting('RecordCommit', 'moduleUrl', BackendUtility::getModuleUrl('tce_db'));
171 $this->pageRenderer->addInlineSetting('WebLayout', 'moduleUrl', BackendUtility::getModuleUrl('web_layout'));
172
173 $this->css = '';
174
175 $this->initializeToolbarItems();
176 $this->executeHook('constructPostProcess');
177 }
178
179 /**
180 * Initialize toolbar item objects
181 *
182 * @throws \RuntimeException
183 */
184 protected function initializeToolbarItems()
185 {
186 $toolbarItemInstances = [];
187 foreach ($GLOBALS['TYPO3_CONF_VARS']['BE']['toolbarItems'] ?? [] as $className) {
188 $toolbarItemInstance = GeneralUtility::makeInstance($className);
189 if (!$toolbarItemInstance instanceof ToolbarItemInterface) {
190 throw new \RuntimeException(
191 'class ' . $className . ' is registered as toolbar item but does not implement'
192 . ToolbarItemInterface::class,
193 1415958218
194 );
195 }
196 $index = (int)$toolbarItemInstance->getIndex();
197 if ($index < 0 || $index > 100) {
198 throw new \RuntimeException(
199 'getIndex() must return an integer between 0 and 100',
200 1415968498
201 );
202 }
203 // Find next free position in array
204 while (array_key_exists($index, $toolbarItemInstances)) {
205 $index++;
206 }
207 $toolbarItemInstances[$index] = $toolbarItemInstance;
208 }
209 ksort($toolbarItemInstances);
210 $this->toolbarItems = $toolbarItemInstances;
211 }
212
213 /**
214 * Injects the request object for the current request or subrequest
215 * As this controller goes only through the render() method, it is rather simple for now
216 *
217 * @param ServerRequestInterface $request the current request
218 * @param ResponseInterface $response
219 * @return ResponseInterface the response with the content
220 */
221 public function mainAction(ServerRequestInterface $request, ResponseInterface $response)
222 {
223 $this->render();
224 $response->getBody()->write($this->content);
225 return $response;
226 }
227
228 /**
229 * Main function generating the BE scaffolding
230 */
231 public function render()
232 {
233 $this->executeHook('renderPreProcess');
234
235 // Prepare the scaffolding, at this point extension may still add javascript and css
236 $view = $this->getFluidTemplateObject($this->templatePath . 'Backend/Main.html');
237
238 $view->assign('moduleMenu', $this->generateModuleMenu());
239 $view->assign('topbar', $this->renderTopbar());
240
241 /******************************************************
242 * Now put the complete backend document together
243 ******************************************************/
244 foreach ($this->cssFiles as $cssFileName => $cssFile) {
245 $this->pageRenderer->addCssFile($cssFile);
246 // Load additional css files to overwrite existing core styles
247 if (!empty($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName])) {
248 $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheets'][$cssFileName]);
249 }
250 }
251 if (!empty($this->css)) {
252 $this->pageRenderer->addCssInlineBlock('BackendInlineCSS', $this->css);
253 }
254 foreach ($this->jsFiles as $jsFile) {
255 $this->pageRenderer->addJsFile($jsFile);
256 }
257 $this->generateJavascript();
258 $this->pageRenderer->addJsInlineCode('BackendInlineJavascript', $this->js, false);
259 $this->loadResourcesForRegisteredNavigationComponents();
260 // @todo: remove this when ExtJS is removed
261 $states = $this->getBackendUser()->uc['BackendComponents']['States'];
262 $this->pageRenderer->addExtOnReadyCode('
263 require([\'TYPO3/CMS/Backend/Storage/Persistent\'], function(PersistentStorage) {
264 var TYPO3ExtJSStateProviderBridge = function() {};
265 Ext.extend(TYPO3ExtJSStateProviderBridge, Ext.state.Provider, {
266 state: {},
267 queue: [],
268 dirty: false,
269 prefix: "BackendComponents.States.",
270 initState: function(state) {
271 if (Ext.isArray(state)) {
272 Ext.each(state, function(item) {
273 this.state[item.name] = item.value;
274 }, this);
275 } else if (Ext.isObject(state)) {
276 Ext.iterate(state, function(key, value) {
277 this.state[key] = value;
278 }, this);
279 } else {
280 this.state = {};
281 }
282 var me = this;
283 window.setInterval(function() {
284 me.submitState(me)
285 }, 750);
286 },
287 get: function(name, defaultValue) {
288 return PersistentStorage.isset(this.prefix + name) ? PersistentStorage.get(this.prefix + name) : defaultValue;
289 },
290 clear: function(name) {
291 PersistentStorage.unset(this.prefix + name);
292 },
293 set: function(name, value) {
294 if (!name) {
295 return;
296 }
297 this.queueChange(name, value);
298 },
299 queueChange: function(name, value) {
300 var o = {};
301 var i;
302 var found = false;
303
304 var lastValue = this.state[name];
305 for (i = 0; i < this.queue.length; i++) {
306 if (this.queue[i].name === name) {
307 lastValue = this.queue[i].value;
308 }
309 }
310 var changed = undefined === lastValue || lastValue !== value;
311
312 if (changed) {
313 o.name = name;
314 o.value = value;
315 for (i = 0; i < this.queue.length; i++) {
316 if (this.queue[i].name === o.name) {
317 this.queue[i] = o;
318 found = true;
319 }
320 }
321 if (false === found) {
322 this.queue.push(o);
323 }
324 this.dirty = true;
325 }
326 },
327 submitState: function(context) {
328 if (!context.dirty) {
329 return;
330 }
331 for (var i = 0; i < context.queue.length; ++i) {
332 PersistentStorage.set(context.prefix + context.queue[i].name, context.queue[i].value).done(function() {
333 if (!context.dirty) {
334 context.queue = [];
335 }
336 });
337 }
338 context.dirty = false;
339 }
340 });
341 Ext.state.Manager.setProvider(new TYPO3ExtJSStateProviderBridge());
342 Ext.state.Manager.getProvider().initState(' . (!empty($states) ? json_encode($states) : []) . ');
343 });');
344 // Set document title:
345 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] ? $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'] . ' [TYPO3 CMS ' . TYPO3_version . ']' : 'TYPO3 CMS ' . TYPO3_version;
346 // Renders the module page
347 $this->content = $this->getDocumentTemplate()->render($title, $view->render());
348 $hookConfiguration = ['content' => &$this->content];
349 $this->executeHook('renderPostProcess', $hookConfiguration);
350 }
351
352 /**
353 * Renders the topbar, containing the backend logo, sitename etc.
354 *
355 * @return string
356 */
357 protected function renderTopbar()
358 {
359 $view = $this->getFluidTemplateObject($this->partialPath . 'Backend/Topbar.html');
360
361 // Extension Configuration to find the TYPO3 logo in the left corner
362 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
363 $logoPath = '';
364 if (!empty($extConf['backendLogo'])) {
365 $customBackendLogo = GeneralUtility::getFileAbsFileName($extConf['backendLogo']);
366 if (!empty($customBackendLogo)) {
367 $logoPath = $customBackendLogo;
368 }
369 }
370 // if no custom logo was set or the path is invalid, use the original one
371 if (empty($logoPath)) {
372 $logoPath = GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Public/Images/typo3_logo_orange.svg');
373 $logoWidth = 22;
374 $logoHeight = 22;
375 } else {
376 // set width/height for custom logo
377 $imageInfo = GeneralUtility::makeInstance(ImageInfo::class, $logoPath);
378 $logoWidth = $imageInfo->getWidth() ?? '22';
379 $logoHeight = $imageInfo->getHeight() ?? '22';
380
381 // High-resolution?
382 if (strpos($logoPath, '@2x.') !== false) {
383 $logoWidth /= 2;
384 $logoHeight /= 2;
385 }
386 }
387
388 $view->assign('logoUrl', PathUtility::getAbsoluteWebPath($logoPath));
389 $view->assign('logoWidth', $logoWidth);
390 $view->assign('logoHeight', $logoHeight);
391 $view->assign('applicationVersion', TYPO3_version);
392 $view->assign('siteName', $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']);
393 $view->assign('toolbar', $this->renderToolbar());
394
395 return $view->render();
396 }
397
398 /**
399 * Loads the css and javascript files of all registered navigation widgets
400 */
401 protected function loadResourcesForRegisteredNavigationComponents()
402 {
403 if (!is_array($GLOBALS['TBE_MODULES']['_navigationComponents'])) {
404 return;
405 }
406 $loadedComponents = [];
407 foreach ($GLOBALS['TBE_MODULES']['_navigationComponents'] as $module => $info) {
408 if (in_array($info['componentId'], $loadedComponents)) {
409 continue;
410 }
411 $loadedComponents[] = $info['componentId'];
412 $component = strtolower(substr($info['componentId'], strrpos($info['componentId'], '-') + 1));
413 $componentDirectory = 'components/' . $component . '/';
414 if ($info['isCoreComponent']) {
415 $componentDirectory = 'Resources/Public/JavaScript/extjs/' . $componentDirectory;
416 $info['extKey'] = 'backend';
417 }
418 $absoluteComponentPath = ExtensionManagementUtility::extPath($info['extKey']) . $componentDirectory;
419 $relativeComponentPath = PathUtility::getRelativePath(PATH_site . TYPO3_mainDir, $absoluteComponentPath);
420 $cssFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'css/', 'css');
421 if (file_exists($absoluteComponentPath . 'css/loadorder.txt')) {
422 // Don't allow inclusion outside directory
423 $loadOrder = str_replace('../', '', file_get_contents($absoluteComponentPath . 'css/loadorder.txt'));
424 $cssFilesOrdered = GeneralUtility::trimExplode(LF, $loadOrder, true);
425 $cssFiles = array_merge($cssFilesOrdered, $cssFiles);
426 }
427 foreach ($cssFiles as $cssFile) {
428 $this->pageRenderer->addCssFile($relativeComponentPath . 'css/' . $cssFile);
429 }
430 $jsFiles = GeneralUtility::getFilesInDir($absoluteComponentPath . 'javascript/', 'js');
431 if (file_exists($absoluteComponentPath . 'javascript/loadorder.txt')) {
432 // Don't allow inclusion outside directory
433 $loadOrder = str_replace('../', '', file_get_contents($absoluteComponentPath . 'javascript/loadorder.txt'));
434 $jsFilesOrdered = GeneralUtility::trimExplode(LF, $loadOrder, true);
435 $jsFiles = array_merge($jsFilesOrdered, $jsFiles);
436 }
437 foreach ($jsFiles as $jsFile) {
438 $this->pageRenderer->addJsFile($relativeComponentPath . 'javascript/' . $jsFile);
439 }
440 }
441 }
442
443 /**
444 * Renders the items in the top toolbar
445 *
446 * @return string top toolbar elements as HTML
447 */
448 protected function renderToolbar()
449 {
450 $toolbar = [];
451 foreach ($this->toolbarItems as $toolbarItem) {
452 /** @var \TYPO3\CMS\Backend\Toolbar\ToolbarItemInterface $toolbarItem */
453 if ($toolbarItem->checkAccess()) {
454 $hasDropDown = (bool)$toolbarItem->hasDropDown();
455 $additionalAttributes = (array)$toolbarItem->getAdditionalAttributes();
456
457 $liAttributes = [];
458
459 // Merge class: Add dropdown class if hasDropDown, add classes from additional attributes
460 $classes = [];
461 $classes[] = 'toolbar-item';
462 $classes[] = 't3js-toolbar-item';
463 if (isset($additionalAttributes['class'])) {
464 $classes[] = $additionalAttributes['class'];
465 unset($additionalAttributes['class']);
466 }
467 $liAttributes[] = 'class="' . implode(' ', $classes) . '"';
468
469 // Add further attributes
470 foreach ($additionalAttributes as $name => $value) {
471 $liAttributes[] = $name . '="' . $value . '"';
472 }
473
474 // Create a unique id from class name
475 $className = get_class($toolbarItem);
476 $className = GeneralUtility::underscoredToLowerCamelCase($className);
477 $className = GeneralUtility::camelCaseToLowerCaseUnderscored($className);
478 $className = str_replace(['_', '\\'], '-', $className);
479 $liAttributes[] = 'id="' . $className . '"';
480
481 $toolbar[] = '<li ' . implode(' ', $liAttributes) . '>';
482
483 if ($hasDropDown) {
484 $toolbar[] = '<a href="#" class="toolbar-item-link dropdown-toggle" data-toggle="dropdown">';
485 $toolbar[] = $toolbarItem->getItem();
486 $toolbar[] = '</a>';
487 $toolbar[] = '<div class="dropdown-menu" role="menu">';
488 $toolbar[] = $toolbarItem->getDropDown();
489 $toolbar[] = '</div>';
490 } else {
491 $toolbar[] = $toolbarItem->getItem();
492 }
493 $toolbar[] = '</li>';
494 }
495 }
496 return implode(LF, $toolbar);
497 }
498
499 /**
500 * Returns the file name to the LLL JavaScript, containing the localized labels,
501 * which can be used in JavaScript code.
502 *
503 * @return string File name of the JS file, relative to TYPO3_mainDir
504 * @throws \RuntimeException
505 */
506 protected function getLocalLangFileName()
507 {
508 $code = $this->generateLocalLang();
509 $filePath = 'typo3temp/assets/js/backend-' . sha1($code) . '.js';
510 if (!file_exists(PATH_site . $filePath)) {
511 // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
512 $error = GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filePath, $code);
513 if ($error !== null) {
514 throw new \RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1295193026);
515 }
516 }
517 return '../' . $filePath;
518 }
519
520 /**
521 * Reads labels required in JavaScript code from the localization system and returns them as JSON
522 * array in TYPO3.LLL.
523 *
524 * @return string JavaScript code containing the LLL labels in TYPO3.LLL
525 */
526 protected function generateLocalLang()
527 {
528 $lang = $this->getLanguageService();
529 $coreLabels = [
530 'waitTitle' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_logging_in'),
531 'refresh_login_failed' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_failed'),
532 'refresh_login_failed_message' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_failed_message'),
533 'refresh_login_title' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_title'),
534 'login_expired' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_expired'),
535 'refresh_login_username' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_username'),
536 'refresh_login_password' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_password'),
537 'refresh_login_emptyPassword' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_emptyPassword'),
538 'refresh_login_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_button'),
539 'refresh_exit_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_exit_button'),
540 'please_wait' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.please_wait'),
541 'be_locked' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.be_locked'),
542 'login_about_to_expire' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_about_to_expire'),
543 'login_about_to_expire_title' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_about_to_expire_title'),
544 'refresh_login_logout_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_logout_button'),
545 'refresh_login_refresh_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_refresh_button'),
546 'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:csh_tooltip_loading')
547 ];
548 $labels = [
549 'fileUpload' => [
550 'windowTitle',
551 'buttonSelectFiles',
552 'buttonCancelAll',
553 'infoComponentMaxFileSize',
554 'infoComponentFileUploadLimit',
555 'infoComponentFileTypeLimit',
556 'infoComponentOverrideFiles',
557 'processRunning',
558 'uploadWait',
559 'uploadStarting',
560 'uploadProgress',
561 'uploadSuccess',
562 'errorQueueLimitExceeded',
563 'errorQueueFileSizeLimit',
564 'errorQueueZeroByteFile',
565 'errorQueueInvalidFiletype',
566 'errorUploadHttp',
567 'errorUploadMissingUrl',
568 'errorUploadIO',
569 'errorUploadSecurityError',
570 'errorUploadLimit',
571 'errorUploadFailed',
572 'errorUploadFileIDNotFound',
573 'errorUploadFileValidation',
574 'errorUploadFileCancelled',
575 'errorUploadStopped',
576 'allErrorMessageTitle',
577 'allErrorMessageText',
578 'allError401',
579 'allError2038'
580 ],
581 'liveSearch' => [
582 'title',
583 'helpTitle',
584 'emptyText',
585 'loadingText',
586 'listEmptyText',
587 'showAllResults',
588 'helpDescription',
589 'helpDescriptionPages',
590 'helpDescriptionContent'
591 ],
592 'viewPort' => [
593 'tooltipModuleMenuSplit',
594 'tooltipNavigationContainerSplitDrag',
595 'tooltipNavigationContainerSplitClick',
596 'tooltipDebugPanelSplitDrag'
597 ]
598 ];
599 $generatedLabels = [];
600 $generatedLabels['core'] = $coreLabels;
601 // First loop over all categories (fileUpload, liveSearch, ..)
602 foreach ($labels as $categoryName => $categoryLabels) {
603 // Then loop over every single label
604 foreach ($categoryLabels as $label) {
605 // LLL identifier must be called $categoryName_$label, e.g. liveSearch_loadingText
606 $generatedLabels[$categoryName][$label] = $this->getLanguageService()->getLL($categoryName . '_' . $label);
607 }
608 }
609 return 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
610 }
611
612 /**
613 * Generates the JavaScript code for the backend.
614 */
615 protected function generateJavascript()
616 {
617 $beUser = $this->getBackendUser();
618 // Needed for FormEngine manipulation (date picker)
619 $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
620 $this->pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
621
622 // If another page module was specified, replace the default Page module with the new one
623 $newPageModule = trim($beUser->getTSConfigVal('options.overridePageModule'));
624 $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
625 if (!$beUser->check('modules', $pageModule)) {
626 $pageModule = '';
627 }
628 $t3Configuration = [
629 'username' => htmlspecialchars($beUser->user['username']),
630 'pageModule' => $pageModule,
631 'inWorkspace' => $beUser->workspace !== 0,
632 'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] : false
633 ];
634 $this->js .= '
635 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
636 /**
637 * Frameset Module object
638 *
639 * Used in main modules with a frameset for submodules to keep the ID between modules
640 * Typically that is set by something like this in a Web>* sub module:
641 * if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
642 * if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
643 */
644 var fsMod = {
645 recentIds: [], // used by frameset modules to track the most recent used id for list frame.
646 navFrameHighlightedID: [], // used by navigation frames to track which row id was highlighted last time
647 currentBank: "0"
648 };
649
650 top.goToModule = function(modName, cMR_flag, addGetVars) {
651 TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
652 }
653 ' . $this->setStartupModule();
654 // Check editing of page:
655 $this->handlePageEditing();
656 }
657
658 /**
659 * Checking if the "&edit" variable was sent so we can open it for editing the page.
660 */
661 protected function handlePageEditing()
662 {
663 $beUser = $this->getBackendUser();
664 // EDIT page:
665 $editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
666 if ($editId) {
667 // Looking up the page to edit, checking permissions:
668 $where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
669 if (MathUtility::canBeInterpretedAsInteger($editId)) {
670 $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
671 } else {
672 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
673 $queryBuilder->getRestrictions()
674 ->removeAll()
675 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
676 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
677
678 $editRecord = $queryBuilder->select('*')
679 ->from('pages')
680 ->where(
681 $queryBuilder->expr()->eq(
682 'alias',
683 $queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
684 ),
685 $queryBuilder->expr()->orX(
686 $beUser->getPagePermsClause(Permission::PAGE_EDIT),
687 $beUser->getPagePermsClause(Permission::CONTENT_EDIT)
688 )
689 )
690 ->setMaxResults(1)
691 ->execute()
692 ->fetch();
693
694 if ($editRecord !== false) {
695 BackendUtility::workspaceOL('pages', $editRecord);
696 }
697 }
698 // If the page was accessible, then let the user edit it.
699 if (is_array($editRecord) && $beUser->isInWebMount($editRecord['uid'])) {
700 // Setting JS code to open editing:
701 $this->js .= '
702 // Load page to edit:
703 window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
704 ';
705 // Checking page edit parameter:
706 if (!$beUser->getTSConfigVal('options.bookmark_onEditId_dontSetPageTree')) {
707 $bookmarkKeepExpanded = $beUser->getTSConfigVal('options.bookmark_onEditId_keepExistingExpanded');
708 // Expanding page tree:
709 BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
710 }
711 } else {
712 $this->js .= '
713 // Warning about page editing:
714 require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
715 Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
716 text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:close')) . ',
717 active: true,
718 btnClass: "btn-info",
719 name: "cancel",
720 trigger: function () {
721 Modal.currentModal.trigger("modal-dismiss");
722 }
723 }])
724 });';
725 }
726 }
727 }
728
729 /**
730 * Sets the startup module from either GETvars module and modParams or user configuration.
731 *
732 * @return string the JavaScript code for the startup module
733 */
734 protected function setStartupModule()
735 {
736 $startModule = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('module'));
737 $startModuleParameters = '';
738 if (!$startModule) {
739 $beUser = $this->getBackendUser();
740 // start module on first login, will be removed once used the first time
741 if (isset($beUser->uc['startModuleOnFirstLogin'])) {
742 $startModule = $beUser->uc['startModuleOnFirstLogin'];
743 unset($beUser->uc['startModuleOnFirstLogin']);
744 $beUser->writeUC();
745 } elseif ($beUser->uc['startModule']) {
746 $startModule = $beUser->uc['startModule'];
747 }
748
749 // check if the start module has additional parameters, so a redirect to a specific
750 // action is possible
751 if (strpos($startModule, '->') !== false) {
752 list($startModule, $startModuleParameters) = explode('->', $startModule, 2);
753 }
754 }
755
756 $moduleParameters = GeneralUtility::_GET('modParams');
757 // if no GET parameters are set, check if there are parameters given from the UC
758 if (!$moduleParameters && $startModuleParameters) {
759 $moduleParameters = $startModuleParameters;
760 }
761
762 if ($startModule) {
763 return '
764 // start in module:
765 top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
766 ';
767 }
768 return '';
769 }
770
771 /**
772 * Adds a css snippet to the backend
773 *
774 * @param string $css Css snippet
775 * @throws \InvalidArgumentException
776 */
777 public function addCss($css)
778 {
779 if (!is_string($css)) {
780 throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
781 }
782 $this->css .= $css;
783 }
784
785 /**
786 * Executes defined hooks functions for the given identifier.
787 *
788 * These hook identifiers are valid:
789 * + constructPostProcess
790 * + renderPreProcess
791 * + renderPostProcess
792 *
793 * @param string $identifier Specific hook identifier
794 * @param array $hookConfiguration Additional configuration passed to hook functions
795 */
796 protected function executeHook($identifier, array $hookConfiguration = [])
797 {
798 $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
799 foreach ($options[$identifier] ?? [] as $hookFunction) {
800 GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
801 }
802 }
803
804 /**
805 * loads all modules from the repository
806 * and renders it with a template
807 *
808 * @return string
809 */
810 protected function generateModuleMenu()
811 {
812 // get all modules except the user modules for the side menu
813 $moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
814
815 $view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
816 $view->assign('modules', $moduleStorage);
817 return $view->render();
818 }
819
820 /**
821 * Returns the Module menu for the AJAX request
822 *
823 * @param ServerRequestInterface $request
824 * @param ResponseInterface $response
825 * @return ResponseInterface
826 */
827 public function getModuleMenu(ServerRequestInterface $request, ResponseInterface $response)
828 {
829 $content = $this->generateModuleMenu();
830
831 $response->getBody()->write(json_encode(['menu' => $content]));
832 return $response;
833 }
834
835 /**
836 * Returns the toolbar for the AJAX request
837 *
838 * @param ServerRequestInterface $request
839 * @param ResponseInterface $response
840 * @return ResponseInterface
841 */
842 public function getTopbar(ServerRequestInterface $request, ResponseInterface $response)
843 {
844 $response->getBody()->write(json_encode(['topbar' => $this->renderTopbar()]));
845 return $response;
846 }
847
848 /**
849 * returns a new standalone view, shorthand function
850 *
851 * @param string $templatePathAndFileName optional the path to set the template path and filename
852 * @return \TYPO3\CMS\Fluid\View\StandaloneView
853 */
854 protected function getFluidTemplateObject($templatePathAndFileName = null)
855 {
856 $view = GeneralUtility::makeInstance(StandaloneView::class);
857 $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
858 if ($templatePathAndFileName) {
859 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
860 }
861 return $view;
862 }
863
864 /**
865 * Returns LanguageService
866 *
867 * @return \TYPO3\CMS\Core\Localization\LanguageService
868 */
869 protected function getLanguageService()
870 {
871 return $GLOBALS['LANG'];
872 }
873
874 /**
875 * Returns the current BE user.
876 *
877 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
878 */
879 protected function getBackendUser()
880 {
881 return $GLOBALS['BE_USER'];
882 }
883
884 /**
885 * Returns an instance of DocumentTemplate
886 *
887 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
888 */
889 protected function getDocumentTemplate()
890 {
891 return $GLOBALS['TBE_TEMPLATE'];
892 }
893 }