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