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