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