[TASK] Port TYPO3.LLL usages to TYPO3.lang
[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->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 * Returns the file name to the LLL JavaScript, containing the localized labels,
504 * which can be used in JavaScript code.
505 *
506 * @return string File name of the JS file, relative to TYPO3_mainDir
507 * @throws \RuntimeException
508 */
509 protected function getLocalLangFileName()
510 {
511 $code = $this->generateLocalLang();
512 $filePath = 'typo3temp/assets/js/backend-' . sha1($code) . '.js';
513 if (!file_exists(PATH_site . $filePath)) {
514 // writeFileToTypo3tempDir() returns NULL on success (please double-read!)
515 $error = GeneralUtility::writeFileToTypo3tempDir(PATH_site . $filePath, $code);
516 if ($error !== null) {
517 throw new \RuntimeException('Locallang JS file could not be written to ' . $filePath . '. Reason: ' . $error, 1295193026);
518 }
519 }
520 return '../' . $filePath;
521 }
522
523 /**
524 * Reads labels required in JavaScript code from the localization system and returns them as JSON
525 * array in TYPO3.LLL.
526 *
527 * @return string JavaScript code containing the LLL labels in TYPO3.LLL
528 */
529 protected function generateLocalLang()
530 {
531 $lang = $this->getLanguageService();
532 $coreLabels = [
533 'waitTitle' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_logging_in'),
534 'refresh_login_failed' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_failed'),
535 'refresh_login_failed_message' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_failed_message'),
536 'refresh_login_title' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_title'),
537 'login_expired' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_expired'),
538 'refresh_login_username' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_username'),
539 'refresh_login_password' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_password'),
540 'refresh_login_emptyPassword' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_emptyPassword'),
541 'refresh_login_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_button'),
542 'refresh_exit_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_exit_button'),
543 'please_wait' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.please_wait'),
544 'be_locked' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.be_locked'),
545 'login_about_to_expire' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_about_to_expire'),
546 'login_about_to_expire_title' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.login_about_to_expire_title'),
547 'refresh_login_logout_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_logout_button'),
548 'refresh_login_refresh_button' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:mess.refresh_login_refresh_button'),
549 'csh_tooltip_loading' => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:csh_tooltip_loading')
550 ];
551 $labels = [
552 'fileUpload' => [
553 'windowTitle',
554 'buttonSelectFiles',
555 'buttonCancelAll',
556 'infoComponentMaxFileSize',
557 'infoComponentFileUploadLimit',
558 'infoComponentFileTypeLimit',
559 'infoComponentOverrideFiles',
560 'processRunning',
561 'uploadWait',
562 'uploadStarting',
563 'uploadProgress',
564 'uploadSuccess',
565 'errorQueueLimitExceeded',
566 'errorQueueFileSizeLimit',
567 'errorQueueZeroByteFile',
568 'errorQueueInvalidFiletype',
569 'errorUploadHttp',
570 'errorUploadMissingUrl',
571 'errorUploadIO',
572 'errorUploadSecurityError',
573 'errorUploadLimit',
574 'errorUploadFailed',
575 'errorUploadFileIDNotFound',
576 'errorUploadFileValidation',
577 'errorUploadFileCancelled',
578 'errorUploadStopped',
579 'allErrorMessageTitle',
580 'allErrorMessageText',
581 'allError401',
582 'allError2038'
583 ],
584 'liveSearch' => [
585 'title',
586 'helpTitle',
587 'emptyText',
588 'loadingText',
589 'listEmptyText',
590 'showAllResults',
591 'helpDescription',
592 'helpDescriptionPages',
593 'helpDescriptionContent'
594 ],
595 'viewPort' => [
596 'tooltipModuleMenuSplit',
597 'tooltipNavigationContainerSplitDrag',
598 'tooltipNavigationContainerSplitClick',
599 'tooltipDebugPanelSplitDrag'
600 ]
601 ];
602 $generatedLabels = [];
603 $generatedLabels['core'] = $coreLabels;
604 // First loop over all categories (fileUpload, liveSearch, ..)
605 foreach ($labels as $categoryName => $categoryLabels) {
606 // Then loop over every single label
607 foreach ($categoryLabels as $label) {
608 // LLL identifier must be called $categoryName_$label, e.g. liveSearch_loadingText
609 $generatedLabels[$categoryName][$label] = $this->getLanguageService()->getLL($categoryName . '_' . $label);
610 }
611 }
612 return 'TYPO3.LLL = ' . json_encode($generatedLabels) . ';';
613 }
614
615 /**
616 * Generates the JavaScript code for the backend.
617 */
618 protected function generateJavascript()
619 {
620 $beUser = $this->getBackendUser();
621 // Needed for FormEngine manipulation (date picker)
622 $dateFormat = ($GLOBALS['TYPO3_CONF_VARS']['SYS']['USdateFormat'] ? ['MM-DD-YYYY', 'HH:mm MM-DD-YYYY'] : ['DD-MM-YYYY', 'HH:mm DD-MM-YYYY']);
623 $this->pageRenderer->addInlineSetting('DateTimePicker', 'DateFormat', $dateFormat);
624
625 // If another page module was specified, replace the default Page module with the new one
626 $newPageModule = trim($beUser->getTSConfigVal('options.overridePageModule'));
627 $pageModule = BackendUtility::isModuleSetInTBE_MODULES($newPageModule) ? $newPageModule : 'web_layout';
628 if (!$beUser->check('modules', $pageModule)) {
629 $pageModule = '';
630 }
631 $t3Configuration = [
632 'username' => htmlspecialchars($beUser->user['username']),
633 'pageModule' => $pageModule,
634 'inWorkspace' => $beUser->workspace !== 0,
635 'showRefreshLoginPopup' => isset($GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup']) ? (int)$GLOBALS['TYPO3_CONF_VARS']['BE']['showRefreshLoginPopup'] : false
636 ];
637 $this->js .= '
638 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
639 /**
640 * Frameset Module object
641 *
642 * Used in main modules with a frameset for submodules to keep the ID between modules
643 * Typically that is set by something like this in a Web>* sub module:
644 * if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
645 * if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
646 */
647 var fsMod = {
648 recentIds: [], // used by frameset modules to track the most recent used id for list frame.
649 navFrameHighlightedID: [], // used by navigation frames to track which row id was highlighted last time
650 currentBank: "0"
651 };
652
653 top.goToModule = function(modName, cMR_flag, addGetVars) {
654 TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
655 }
656 ' . $this->setStartupModule();
657 // Check editing of page:
658 $this->handlePageEditing();
659 }
660
661 /**
662 * Checking if the "&edit" variable was sent so we can open it for editing the page.
663 */
664 protected function handlePageEditing()
665 {
666 $beUser = $this->getBackendUser();
667 // EDIT page:
668 $editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
669 if ($editId) {
670 // Looking up the page to edit, checking permissions:
671 $where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
672 if (MathUtility::canBeInterpretedAsInteger($editId)) {
673 $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
674 } else {
675 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
676 $queryBuilder->getRestrictions()
677 ->removeAll()
678 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
679 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
680
681 $editRecord = $queryBuilder->select('*')
682 ->from('pages')
683 ->where(
684 $queryBuilder->expr()->eq(
685 'alias',
686 $queryBuilder->createNamedParameter($editId, \PDO::PARAM_STR)
687 ),
688 $queryBuilder->expr()->orX(
689 $beUser->getPagePermsClause(Permission::PAGE_EDIT),
690 $beUser->getPagePermsClause(Permission::CONTENT_EDIT)
691 )
692 )
693 ->setMaxResults(1)
694 ->execute()
695 ->fetch();
696
697 if ($editRecord !== false) {
698 BackendUtility::workspaceOL('pages', $editRecord);
699 }
700 }
701 // If the page was accessible, then let the user edit it.
702 if (is_array($editRecord) && $beUser->isInWebMount($editRecord['uid'])) {
703 // Setting JS code to open editing:
704 $this->js .= '
705 // Load page to edit:
706 window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
707 ';
708 // Checking page edit parameter:
709 if (!$beUser->getTSConfigVal('options.bookmark_onEditId_dontSetPageTree')) {
710 $bookmarkKeepExpanded = $beUser->getTSConfigVal('options.bookmark_onEditId_keepExistingExpanded');
711 // Expanding page tree:
712 BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
713 }
714 } else {
715 $this->js .= '
716 // Warning about page editing:
717 require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
718 Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
719 text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:close')) . ',
720 active: true,
721 btnClass: "btn-info",
722 name: "cancel",
723 trigger: function () {
724 Modal.currentModal.trigger("modal-dismiss");
725 }
726 }])
727 });';
728 }
729 }
730 }
731
732 /**
733 * Sets the startup module from either GETvars module and modParams or user configuration.
734 *
735 * @return string the JavaScript code for the startup module
736 */
737 protected function setStartupModule()
738 {
739 $startModule = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('module'));
740 $startModuleParameters = '';
741 if (!$startModule) {
742 $beUser = $this->getBackendUser();
743 // start module on first login, will be removed once used the first time
744 if (isset($beUser->uc['startModuleOnFirstLogin'])) {
745 $startModule = $beUser->uc['startModuleOnFirstLogin'];
746 unset($beUser->uc['startModuleOnFirstLogin']);
747 $beUser->writeUC();
748 } elseif ($beUser->uc['startModule']) {
749 $startModule = $beUser->uc['startModule'];
750 }
751
752 // check if the start module has additional parameters, so a redirect to a specific
753 // action is possible
754 if (strpos($startModule, '->') !== false) {
755 list($startModule, $startModuleParameters) = explode('->', $startModule, 2);
756 }
757 }
758
759 $moduleParameters = GeneralUtility::_GET('modParams');
760 // if no GET parameters are set, check if there are parameters given from the UC
761 if (!$moduleParameters && $startModuleParameters) {
762 $moduleParameters = $startModuleParameters;
763 }
764
765 if ($startModule) {
766 return '
767 // start in module:
768 top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
769 ';
770 }
771 return '';
772 }
773
774 /**
775 * Adds a css snippet to the backend
776 *
777 * @param string $css Css snippet
778 * @throws \InvalidArgumentException
779 */
780 public function addCss($css)
781 {
782 if (!is_string($css)) {
783 throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
784 }
785 $this->css .= $css;
786 }
787
788 /**
789 * Executes defined hooks functions for the given identifier.
790 *
791 * These hook identifiers are valid:
792 * + constructPostProcess
793 * + renderPreProcess
794 * + renderPostProcess
795 *
796 * @param string $identifier Specific hook identifier
797 * @param array $hookConfiguration Additional configuration passed to hook functions
798 */
799 protected function executeHook($identifier, array $hookConfiguration = [])
800 {
801 $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
802 foreach ($options[$identifier] ?? [] as $hookFunction) {
803 GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
804 }
805 }
806
807 /**
808 * loads all modules from the repository
809 * and renders it with a template
810 *
811 * @return string
812 */
813 protected function generateModuleMenu()
814 {
815 // get all modules except the user modules for the side menu
816 $moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
817
818 $view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
819 $view->assign('modules', $moduleStorage);
820 return $view->render();
821 }
822
823 /**
824 * Returns the Module menu for the AJAX request
825 *
826 * @param ServerRequestInterface $request
827 * @param ResponseInterface $response
828 * @return ResponseInterface
829 */
830 public function getModuleMenu(ServerRequestInterface $request, ResponseInterface $response)
831 {
832 $content = $this->generateModuleMenu();
833
834 $response->getBody()->write(json_encode(['menu' => $content]));
835 return $response;
836 }
837
838 /**
839 * Returns the toolbar for the AJAX request
840 *
841 * @param ServerRequestInterface $request
842 * @param ResponseInterface $response
843 * @return ResponseInterface
844 */
845 public function getTopbar(ServerRequestInterface $request, ResponseInterface $response)
846 {
847 $response->getBody()->write(json_encode(['topbar' => $this->renderTopbar()]));
848 return $response;
849 }
850
851 /**
852 * returns a new standalone view, shorthand function
853 *
854 * @param string $templatePathAndFileName optional the path to set the template path and filename
855 * @return \TYPO3\CMS\Fluid\View\StandaloneView
856 */
857 protected function getFluidTemplateObject($templatePathAndFileName = null)
858 {
859 $view = GeneralUtility::makeInstance(StandaloneView::class);
860 $view->setPartialRootPaths([GeneralUtility::getFileAbsFileName('EXT:backend/Resources/Private/Partials')]);
861 if ($templatePathAndFileName) {
862 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
863 }
864 return $view;
865 }
866
867 /**
868 * Returns LanguageService
869 *
870 * @return \TYPO3\CMS\Core\Localization\LanguageService
871 */
872 protected function getLanguageService()
873 {
874 return $GLOBALS['LANG'];
875 }
876
877 /**
878 * Returns the current BE user.
879 *
880 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
881 */
882 protected function getBackendUser()
883 {
884 return $GLOBALS['BE_USER'];
885 }
886
887 /**
888 * Returns an instance of DocumentTemplate
889 *
890 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
891 */
892 protected function getDocumentTemplate()
893 {
894 return $GLOBALS['TBE_TEMPLATE'];
895 }
896 }