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