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