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