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