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