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