[TASK] Remove hardcoded ContextHelpWindows settings
[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 ];
590 $this->js .= '
591 TYPO3.configuration = ' . json_encode($t3Configuration) . ';
592
593 /**
594 * TypoSetup object.
595 */
596 function typoSetup() { //
597 this.username = TYPO3.configuration.username;
598 this.uniqueID = TYPO3.configuration.uniqueID;
599 }
600 var TS = new typoSetup();
601 //backwards compatibility
602 /**
603 * Frameset Module object
604 *
605 * Used in main modules with a frameset for submodules to keep the ID between modules
606 * Typically that is set by something like this in a Web>* sub module:
607 * if (top.fsMod) top.fsMod.recentIds["web"] = "\'.(int)$this->id.\'";
608 * if (top.fsMod) top.fsMod.recentIds["file"] = "...(file reference/string)...";
609 */
610 function fsModules() { //
611 this.recentIds=new Array(); // used by frameset modules to track the most recent used id for list frame.
612 this.navFrameHighlightedID=new Array(); // used by navigation frames to track which row id was highlighted last time
613 this.currentBank="0";
614 }
615 var fsMod = new fsModules();
616
617 top.goToModule = function(modName, cMR_flag, addGetVars) {
618 TYPO3.ModuleMenu.App.showModule(modName, addGetVars);
619 }
620 ' . $this->setStartupModule();
621 // Check editing of page:
622 $this->handlePageEditing();
623 }
624
625 /**
626 * Checking if the "&edit" variable was sent so we can open it for editing the page.
627 *
628 * @return void
629 */
630 protected function handlePageEditing()
631 {
632 $beUser = $this->getBackendUser();
633 // EDIT page:
634 $editId = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('edit'));
635 $editRecord = '';
636 if ($editId) {
637 // Looking up the page to edit, checking permissions:
638 $where = ' AND (' . $beUser->getPagePermsClause(2) . ' OR ' . $beUser->getPagePermsClause(16) . ')';
639 if (MathUtility::canBeInterpretedAsInteger($editId)) {
640 $editRecord = BackendUtility::getRecordWSOL('pages', $editId, '*', $where);
641 } else {
642 $records = BackendUtility::getRecordsByField('pages', 'alias', $editId, $where);
643 if (is_array($records)) {
644 $editRecord = reset($records);
645 BackendUtility::workspaceOL('pages', $editRecord);
646 }
647 }
648 // If the page was accessible, then let the user edit it.
649 if (is_array($editRecord) && $beUser->isInWebMount($editRecord['uid'])) {
650 // Setting JS code to open editing:
651 $this->js .= '
652 // Load page to edit:
653 window.setTimeout("top.loadEditId(' . (int)$editRecord['uid'] . ');", 500);
654 ';
655 // Checking page edit parameter:
656 if (!$beUser->getTSConfigVal('options.bookmark_onEditId_dontSetPageTree')) {
657 $bookmarkKeepExpanded = $beUser->getTSConfigVal('options.bookmark_onEditId_keepExistingExpanded');
658 // Expanding page tree:
659 BackendUtility::openPageTree((int)$editRecord['pid'], !$bookmarkKeepExpanded);
660 }
661 } else {
662 $this->js .= '
663 // Warning about page editing:
664 require(["TYPO3/CMS/Backend/Modal", "TYPO3/CMS/Backend/Severity"], function(Modal, Severity) {
665 Modal.show("", ' . GeneralUtility::quoteJSvalue(sprintf($this->getLanguageService()->getLL('noEditPage'), $editId)) . ', Severity.notice, [{
666 text: ' . GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_common.xlf:close')) . ',
667 active: true,
668 btnClass: "btn-info",
669 name: "cancel",
670 trigger: function () {
671 Modal.currentModal.trigger("modal-dismiss");
672 }
673 }])
674 });';
675 }
676 }
677 }
678
679 /**
680 * Sets the startup module from either GETvars module and modParams or user configuration.
681 *
682 * @return string the JavaScript code for the startup module
683 */
684 protected function setStartupModule()
685 {
686 $startModule = preg_replace('/[^[:alnum:]_]/', '', GeneralUtility::_GET('module'));
687 $startModuleParameters = '';
688 if (!$startModule) {
689 $beUser = $this->getBackendUser();
690 // start module on first login, will be removed once used the first time
691 if (isset($beUser->uc['startModuleOnFirstLogin'])) {
692 $startModule = $beUser->uc['startModuleOnFirstLogin'];
693 unset($beUser->uc['startModuleOnFirstLogin']);
694 $beUser->writeUC();
695 } elseif ($beUser->uc['startModule']) {
696 $startModule = $beUser->uc['startModule'];
697 }
698
699 // check if the start module has additional parameters, so a redirect to a specific
700 // action is possible
701 if (strpos($startModule, '->') !== false) {
702 list($startModule, $startModuleParameters) = explode('->', $startModule, 2);
703 }
704 }
705
706 $moduleParameters = GeneralUtility::_GET('modParams');
707 // if no GET parameters are set, check if there are parameters given from the UC
708 if (!$moduleParameters && $startModuleParameters) {
709 $moduleParameters = $startModuleParameters;
710 }
711
712 if ($startModule) {
713 return '
714 // start in module:
715 top.startInModule = [' . GeneralUtility::quoteJSvalue($startModule) . ', ' . GeneralUtility::quoteJSvalue($moduleParameters) . '];
716 ';
717 } else {
718 return '';
719 }
720 }
721
722 /**
723 * Adds a javascript snippet to the backend
724 *
725 * @param string $javascript Javascript snippet
726 * @return void
727 * @throws \InvalidArgumentException
728 */
729 public function addJavascript($javascript)
730 {
731 // @todo do we need more checks?
732 if (!is_string($javascript)) {
733 throw new \InvalidArgumentException('parameter $javascript must be of type string', 1195129553);
734 }
735 $this->js .= $javascript;
736 }
737
738 /**
739 * Adds a javscript file to the backend after it has been checked that it exists
740 *
741 * @param string $javascriptFile Javascript file reference
742 * @return bool TRUE if the javascript file was successfully added, FALSE otherwise
743 */
744 public function addJavascriptFile($javascriptFile)
745 {
746 $jsFileAdded = false;
747 // @todo add more checks if necessary
748 if (file_exists(GeneralUtility::resolveBackPath(PATH_typo3 . $javascriptFile))) {
749 $this->jsFiles[] = $javascriptFile;
750 $jsFileAdded = true;
751 }
752 return $jsFileAdded;
753 }
754
755 /**
756 * Adds a css snippet to the backend
757 *
758 * @param string $css Css snippet
759 * @return void
760 * @throws \InvalidArgumentException
761 */
762 public function addCss($css)
763 {
764 if (!is_string($css)) {
765 throw new \InvalidArgumentException('parameter $css must be of type string', 1195129642);
766 }
767 $this->css .= $css;
768 }
769
770 /**
771 * Adds a css file to the backend after it has been checked that it exists
772 *
773 * @param string $cssFileName The css file's name with out the .css ending
774 * @param string $cssFile Css file reference
775 * @return bool TRUE if the css file was added, FALSE otherwise
776 */
777 public function addCssFile($cssFileName, $cssFile)
778 {
779 $cssFileAdded = false;
780 if (empty($this->cssFiles[$cssFileName])) {
781 $this->cssFiles[$cssFileName] = $cssFile;
782 $cssFileAdded = true;
783 }
784 return $cssFileAdded;
785 }
786
787 /**
788 * Executes defined hooks functions for the given identifier.
789 *
790 * These hook identifiers are valid:
791 * + constructPostProcess
792 * + renderPreProcess
793 * + renderPostProcess
794 *
795 * @param string $identifier Specific hook identifier
796 * @param array $hookConfiguration Additional configuration passed to hook functions
797 * @return void
798 */
799 protected function executeHook($identifier, array $hookConfiguration = [])
800 {
801 $options = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/backend.php'];
802 if (isset($options[$identifier]) && is_array($options[$identifier])) {
803 foreach ($options[$identifier] as $hookFunction) {
804 GeneralUtility::callUserFunction($hookFunction, $hookConfiguration, $this);
805 }
806 }
807 }
808
809 /**
810 * loads all modules from the repository
811 * and renders it with a template
812 *
813 * @return string
814 */
815 protected function generateModuleMenu()
816 {
817 // get all modules except the user modules for the side menu
818 $moduleStorage = $this->backendModuleRepository->loadAllowedModules(['user', 'help']);
819
820 $view = $this->getFluidTemplateObject($this->templatePath . 'ModuleMenu/Main.html');
821 $view->assign('modules', $moduleStorage);
822 return $view->render();
823 }
824
825 /**
826 * Returns the Module menu for the AJAX request
827 *
828 * @param ServerRequestInterface $request
829 * @param ResponseInterface $response
830 * @return ResponseInterface
831 */
832 public function getModuleMenu(ServerRequestInterface $request, ResponseInterface $response)
833 {
834 $content = $this->generateModuleMenu();
835
836 $response->getBody()->write(json_encode(['menu' => $content]));
837 return $response;
838 }
839
840 /**
841 * returns a new standalone view, shorthand function
842 *
843 * @param string $templatePathAndFileName optional the path to set the template path and filename
844 * @return \TYPO3\CMS\Fluid\View\StandaloneView
845 */
846 protected function getFluidTemplateObject($templatePathAndFileName = null)
847 {
848 $view = GeneralUtility::makeInstance(StandaloneView::class);
849 if ($templatePathAndFileName) {
850 $view->setTemplatePathAndFilename(GeneralUtility::getFileAbsFileName($templatePathAndFileName));
851 }
852 return $view;
853 }
854
855 /**
856 * Returns LanguageService
857 *
858 * @return \TYPO3\CMS\Lang\LanguageService
859 */
860 protected function getLanguageService()
861 {
862 return $GLOBALS['LANG'];
863 }
864
865 /**
866 * Returns the current BE user.
867 *
868 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
869 */
870 protected function getBackendUser()
871 {
872 return $GLOBALS['BE_USER'];
873 }
874
875 /**
876 * Returns an instance of DocumentTemplate
877 *
878 * @return \TYPO3\CMS\Backend\Template\DocumentTemplate
879 */
880 protected function getDocumentTemplate()
881 {
882 return $GLOBALS['TBE_TEMPLATE'];
883 }
884 }