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