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