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