[BUGFIX] Render FlashMessages at ClearCache
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Template / DocumentTemplate.php
1 <?php
2 namespace TYPO3\CMS\Backend\Template;
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\Log\LoggerAwareInterface;
18 use Psr\Log\LoggerAwareTrait;
19 use TYPO3\CMS\Backend\Backend\Shortcut\ShortcutRepository;
20 use TYPO3\CMS\Backend\Routing\UriBuilder;
21 use TYPO3\CMS\Backend\Utility\BackendUtility;
22 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
23 use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
24 use TYPO3\CMS\Core\Imaging\Icon;
25 use TYPO3\CMS\Core\Imaging\IconFactory;
26 use TYPO3\CMS\Core\Localization\LanguageService;
27 use TYPO3\CMS\Core\Page\PageRenderer;
28 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService;
29 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
30 use TYPO3\CMS\Core\Utility\GeneralUtility;
31 use TYPO3\CMS\Core\Utility\HttpUtility;
32 use TYPO3\CMS\Core\Utility\PathUtility;
33
34 /**
35 * TYPO3 Backend Template Class
36 *
37 * This class contains functions for starting and ending the HTML of backend modules
38 * It also contains methods for outputting sections of content.
39 * Further there are functions for making icons, links, setting form-field widths etc.
40 * Color scheme and stylesheet definitions are also available here.
41 * Finally this file includes the language class for TYPO3's backend.
42 *
43 * After this file $LANG and $TBE_TEMPLATE are global variables / instances of their respective classes.
44 *
45 * Please refer to Inside TYPO3 for a discussion of how to use this API.
46 */
47 class DocumentTemplate implements LoggerAwareInterface
48 {
49 use LoggerAwareTrait;
50
51 // Vars you typically might want to/should set from outside after making instance of this class:
52 /**
53 * This can be set to the HTML-code for a formtag.
54 * Useful when you need a form to span the whole page; Inserted exactly after the body-tag.
55 *
56 * @var string
57 */
58 public $form = '';
59
60 /**
61 * Additional header code (eg. a JavaScript section) could be accumulated in this var. It will be directly outputted in the header.
62 *
63 * @var string
64 */
65 public $JScode = '';
66
67 /**
68 * Similar to $JScode but for use as array with associative keys to prevent double inclusion of JS code. a <script> tag is automatically wrapped around.
69 *
70 * @var array
71 */
72 public $JScodeArray = ['jumpToUrl' => '
73 function jumpToUrl(URL) {
74 console.warn(\'jumpToUrl() has been marked as deprecated.\');
75 window.location.href = URL;
76 return false;
77 }
78 '];
79
80 /**
81 * Additional 'page-end' code could be accumulated in this var. It will be outputted at the end of page before </body> and some other internal page-end code.
82 *
83 * @var string
84 */
85 public $postCode = '';
86
87 /**
88 * HTML template with markers for module
89 *
90 * @var string
91 */
92 public $moduleTemplate = '';
93
94 /**
95 * The base file (not overlaid by TBE_STYLES) for the current module, useful for hooks when finding out which modules is rendered currently
96 *
97 * @var string
98 */
99 protected $moduleTemplateFilename = '';
100
101 /**
102 * Script ID
103 *
104 * @var string
105 */
106 public $scriptID = '';
107
108 /**
109 * Id which can be set for the body tag. Default value is based on script ID
110 *
111 * @var string
112 */
113 public $bodyTagId = '';
114
115 /**
116 * You can add additional attributes to the body-tag through this variable.
117 *
118 * @var string
119 */
120 public $bodyTagAdditions = '';
121
122 /**
123 * Additional CSS styles which will be added to the <style> section in the header
124 * used as array with associative keys to prevent double inclusion of CSS code
125 *
126 * @var array
127 */
128 public $inDocStylesArray = [];
129
130 /**
131 * Filename of stylesheet
132 *
133 * @var string
134 */
135 public $styleSheetFile = '';
136
137 /**
138 * Filename of stylesheet #2 - linked to right after the $this->styleSheetFile script
139 *
140 * @var string
141 */
142 public $styleSheetFile2 = '';
143
144 /**
145 * Filename of a post-stylesheet - included right after all inline styles.
146 *
147 * @var string
148 */
149 public $styleSheetFile_post = '';
150
151 /**
152 * Whether to use the X-UA-Compatible meta tag
153 *
154 * @var bool
155 */
156 protected $useCompatibilityTag = false;
157
158 /**
159 * X-Ua-Compatible version output in meta tag
160 *
161 * @var string
162 */
163 protected $xUaCompatibilityVersion = 'IE=edge';
164
165 // Skinning
166 /**
167 * Include these CSS directories from skins by default
168 *
169 * @var array
170 */
171 protected $stylesheetsSkins = [
172 'structure' => 'Resources/Public/Css/structure/',
173 'visual' => 'Resources/Public/Css/visual/'
174 ];
175
176 /**
177 * (Default) Class for wrapping <DIV>-tag of page. Is set in class extensions.
178 *
179 * @var string
180 */
181 public $divClass = '';
182
183 /**
184 * @var string
185 */
186 public $pageHeaderBlock = '';
187
188 /**
189 * @var string
190 */
191 public $endOfPageJsBlock = '';
192
193 /**
194 * @var PageRenderer
195 */
196 protected $pageRenderer;
197
198 /**
199 * Alternative template file
200 *
201 * @var string
202 */
203 protected $pageHeaderFooterTemplateFile = '';
204
205 /**
206 * Whether flashmessages should be rendered or not
207 *
208 * @var bool $showFlashMessages
209 */
210 public $showFlashMessages = true;
211
212 /**
213 * @var IconFactory
214 */
215 protected $iconFactory;
216
217 /**
218 * @var MarkerBasedTemplateService
219 */
220 protected $templateService;
221
222 /**
223 * Constructor
224 */
225 public function __construct()
226 {
227 $this->initPageRenderer();
228
229 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
230
231 // initialize Marker Support
232 $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
233
234 // Setting default scriptID, trim forward slash from route
235 $this->scriptID = ltrim(GeneralUtility::_GET('route'), '/');
236 $this->bodyTagId = preg_replace('/[^A-Za-z0-9-]/', '-', $this->scriptID);
237 // Individual configuration per script? If so, make a recursive merge of the arrays:
238 if (is_array($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID] ?? false)) {
239 // Make copy
240 $ovr = $GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID];
241 // merge styles.
242 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TBE_STYLES'], $ovr);
243 // Have to unset - otherwise the second instantiation will do it again!
244 unset($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID]);
245 }
246 // Main Stylesheets:
247 if (!empty($GLOBALS['TBE_STYLES']['stylesheet'])) {
248 $this->styleSheetFile = $GLOBALS['TBE_STYLES']['stylesheet'];
249 }
250 if (!empty($GLOBALS['TBE_STYLES']['stylesheet2'])) {
251 $this->styleSheetFile2 = $GLOBALS['TBE_STYLES']['stylesheet2'];
252 }
253 if (!empty($GLOBALS['TBE_STYLES']['styleSheetFile_post'])) {
254 $this->styleSheetFile_post = $GLOBALS['TBE_STYLES']['styleSheetFile_post'];
255 }
256 if (!empty($GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle'])) {
257 $this->inDocStylesArray['TBEstyle'] = $GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle'];
258 }
259 // include all stylesheets
260 foreach ($this->getSkinStylesheetDirectories() as $stylesheetDirectory) {
261 $this->addStyleSheetDirectory($stylesheetDirectory);
262 }
263 }
264
265 /**
266 * Initializes the page renderer object
267 */
268 protected function initPageRenderer()
269 {
270 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
271 $this->pageRenderer->setLanguage($this->getLanguageService()->lang);
272 $this->pageRenderer->enableConcatenateCss();
273 $this->pageRenderer->enableConcatenateJavascript();
274 $this->pageRenderer->enableCompressCss();
275 $this->pageRenderer->enableCompressJavascript();
276 if ($GLOBALS['TYPO3_CONF_VARS']['BE']['debug']) {
277 $this->pageRenderer->enableDebugMode();
278 }
279 }
280
281 /*****************************************
282 *
283 * EVALUATION FUNCTIONS
284 * Various centralized processing
285 *
286 *****************************************/
287
288 /**
289 * Returns a linked shortcut-icon which will call the shortcut frame and set a shortcut there back to the calling page/module
290 *
291 * @param string $gvList Is the list of GET variables to store (if any)
292 * @param string $setList Is the list of SET[] variables to store (if any) - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend modules
293 * @param string $modName Module name string
294 * @param string|int $motherModName Is used to enter the "parent module name" if the module is a submodule under eg. Web>* or File>*. You can also set this value to 1 in which case the currentLoadedModule is sent to the shortcut script (so - not a fixed value!) - that is used in file_edit and wizard_rte modules where those are really running as a part of another module.
295 * @param string $classes
296 * @return string HTML content
297 */
298 public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '', $classes = '')
299 {
300 $gvList = 'route,' . $gvList;
301 $storeUrl = $this->makeShortcutUrl($gvList, $setList);
302 $pathInfo = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
303 if ((int)$motherModName === 1) {
304 $motherModule = 'top.currentModuleLoaded';
305 } elseif ($motherModName) {
306 $motherModule = GeneralUtility::quoteJSvalue($motherModName);
307 } else {
308 $motherModule = '\'\'';
309 }
310 $confirmationText = GeneralUtility::quoteJSvalue($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'));
311
312 $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
313 $shortcutRepository = GeneralUtility::makeInstance(ShortcutRepository::class);
314 $shortcutExist = $shortcutRepository->shortcutExists($shortcutUrl);
315
316 if ($shortcutExist) {
317 return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
318 $this->iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . '</a>';
319 }
320 $url = GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl));
321 $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility::quoteJSvalue(rawurlencode($modName)) .
322 ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this);return false;';
323
324 return '<a href="#" class="' . htmlspecialchars($classes) . '" onclick="' . htmlspecialchars($onClick) . '" title="' .
325 htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')) . '">' .
326 $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>';
327 }
328
329 /**
330 * MAKE url for storing
331 * Internal func
332 *
333 * @param string $gvList Is the list of GET variables to store (if any)
334 * @param string $setList Is the list of SET[] variables to store (if any) - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend modules
335 * @return string
336 * @internal
337 * @see makeShortcutIcon()
338 */
339 public function makeShortcutUrl($gvList, $setList)
340 {
341 $GET = GeneralUtility::_GET();
342 $storeArray = array_merge(
343 GeneralUtility::compileSelectedGetVarsFromArray($gvList, $GET),
344 ['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)]
345 );
346 return HttpUtility::buildQueryString($storeArray, '&');
347 }
348
349 /**
350 * Returns JavaScript variables setting the returnUrl and thisScript location for use by JavaScript on the page.
351 * Used in fx. db_list.php (Web>List)
352 *
353 * @param string $thisLocation URL to "this location" / current script
354 * @return string Urls are returned as JavaScript variables T3_RETURN_URL and T3_THIS_LOCATION
355 */
356 public function redirectUrls($thisLocation = '')
357 {
358 $thisLocation = $thisLocation ? $thisLocation : GeneralUtility::linkThisScript([
359 'CB' => '',
360 'SET' => '',
361 'cmd' => '',
362 'popViewId' => ''
363 ]);
364 $out = '
365 // @deprecated
366 var T3_RETURN_URL = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode(GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))))) . ';
367 // @deprecated
368 var T3_THIS_LOCATION = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode($thisLocation))) . '
369 ';
370 return $out;
371 }
372
373 /**
374 * Defines whether to use the X-UA-Compatible meta tag.
375 *
376 * @param bool $useCompatibilityTag Whether to use the tag
377 */
378 public function useCompatibilityTag($useCompatibilityTag = true)
379 {
380 $this->useCompatibilityTag = (bool)$useCompatibilityTag;
381 }
382
383 /*****************************************
384 *
385 * PAGE BUILDING FUNCTIONS.
386 * Use this to build the HTML of your backend modules
387 *
388 *****************************************/
389 /**
390 * Returns page start
391 * This includes the proper header with charset, title, meta tag and beginning body-tag.
392 *
393 * @param string $title HTML Page title for the header
394 * @return string Returns the whole header section of a HTML-document based on settings in internal variables (like styles, javascript code, charset, generator and docType)
395 * @see endPage()
396 */
397 public function startPage($title)
398 {
399 // hook pre start page
400 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'] ?? [] as $hookFunction) {
401 $hookParameters = [
402 'title' => &$title
403 ];
404 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
405 }
406 // alternative template for Header and Footer
407 if ($this->pageHeaderFooterTemplateFile) {
408 $file = GeneralUtility::getFileAbsFileName($this->pageHeaderFooterTemplateFile);
409 if ($file) {
410 $this->pageRenderer->setTemplateFile($file);
411 }
412 }
413
414 // Disable rendering of XHTML tags
415 $this->pageRenderer->setRenderXhtml(false);
416
417 $languageCode = $this->pageRenderer->getLanguage() === 'default' ? 'en' : $this->pageRenderer->getLanguage();
418 $this->pageRenderer->setHtmlTag('<html lang="' . $languageCode . '">');
419
420 $headerStart = '<!DOCTYPE html>';
421 $this->pageRenderer->setXmlPrologAndDocType($headerStart);
422 $this->pageRenderer->setCharSet('utf-8');
423 $this->pageRenderer->setMetaTag('name', 'generator', $this->generator());
424 $this->pageRenderer->setMetaTag('name', 'robots', 'noindex,follow');
425 $this->pageRenderer->setMetaTag('name', 'viewport', 'width=device-width, initial-scale=1');
426 $this->pageRenderer->setFavIcon($this->getBackendFavicon());
427 if ($this->useCompatibilityTag) {
428 $this->pageRenderer->setMetaTag('http-equiv', 'X-UA-Compatible', $this->xUaCompatibilityVersion);
429 }
430 $this->pageRenderer->setTitle($title);
431 // add docstyles
432 $this->docStyle();
433 $this->pageRenderer->addHeaderData($this->JScode);
434 foreach ($this->JScodeArray as $name => $code) {
435 $this->pageRenderer->addJsInlineCode($name, $code, false);
436 }
437
438 // Note: please do not reference "bootstrap" outside of the TYPO3 Core (not in your own extensions)
439 // as this is preliminary as long as Twitter bootstrap does not support AMD modules
440 // this logic will be changed once Twitter bootstrap 4 is included
441 // @todo
442 $this->pageRenderer->addJsFile('EXT:core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap.js');
443
444 // csh manual require js module & moduleUrl
445 if (TYPO3_MODE === 'BE' && $this->getBackendUser() && !empty($this->getBackendUser()->user)) {
446 $uriBuilder = GeneralUtility::makeInstance(UriBuilder::class);
447 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextHelp');
448 $this->pageRenderer->addInlineSetting(
449 'ContextHelp',
450 'moduleUrl',
451 (string)$uriBuilder->buildUriFromRoute('help_cshmanual', ['action' => 'detail'])
452 );
453 }
454
455 // hook for additional headerData
456 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preHeaderRenderHook'] ?? [] as $hookFunction) {
457 $hookParameters = [
458 'pageRenderer' => &$this->pageRenderer
459 ];
460 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
461 }
462 // Construct page header.
463 $str = $this->pageRenderer->render(PageRenderer::PART_HEADER);
464 $this->JScode = '';
465 $this->JScodeArray = [];
466 $this->endOfPageJsBlock = $this->pageRenderer->render(PageRenderer::PART_FOOTER);
467 $str .= $this->docBodyTagBegin() . ($this->divClass ? '
468
469 <!-- Wrapping DIV-section for whole page BEGIN -->
470 <div class="' . $this->divClass . '">
471 ' : '') . trim($this->form);
472 return $str;
473 }
474
475 /**
476 * Returns page end; This includes finishing form, div, body and html tags.
477 *
478 * @return string The HTML end of a page
479 * @see startPage()
480 */
481 public function endPage()
482 {
483 $str = $this->postCode . GeneralUtility::wrapJS(BackendUtility::getUpdateSignalCode()) . ($this->form ? '
484 </form>' : '');
485 // If something is in buffer like debug, put it to end of page
486 if (ob_get_contents()) {
487 $str .= ob_get_clean();
488 if (!headers_sent()) {
489 header('Content-Encoding: None');
490 }
491 }
492 $str .= ($this->divClass ? '
493
494 <!-- Wrapping DIV-section for whole page END -->
495 </div>' : '') . $this->endOfPageJsBlock;
496
497 // Logging: Can't find better place to put it:
498 $this->logger->debug('END of BACKEND session', ['_FLUSH' => true]);
499 return $str;
500 }
501
502 /**
503 * Shortcut for render the complete page of a module
504 *
505 * @param string $title page title
506 * @param string $content page content
507 * @return string complete page
508 */
509 public function render($title, $content)
510 {
511 $pageContent = $this->startPage($title);
512 $pageContent .= $content;
513 $pageContent .= $this->endPage();
514 return $this->insertStylesAndJS($pageContent);
515 }
516
517 /**
518 * Creates the bodyTag.
519 * You can add to the bodyTag by $this->bodyTagAdditions
520 *
521 * @return string HTML body tag
522 */
523 public function docBodyTagBegin()
524 {
525 return '<body ' . trim($this->bodyTagAdditions . ($this->bodyTagId ? ' id="' . $this->bodyTagId . '"' : '')) . '>';
526 }
527
528 /**
529 * Outputting document style
530 */
531 public function docStyle()
532 {
533 // Implode it all:
534 $inDocStyles = implode(LF, $this->inDocStylesArray);
535
536 // Reset styles so they won't be added again in insertStylesAndJS()
537 $this->inDocStylesArray = [];
538
539 if ($this->styleSheetFile) {
540 $this->pageRenderer->addCssFile($this->styleSheetFile);
541 }
542 if ($this->styleSheetFile2) {
543 $this->pageRenderer->addCssFile($this->styleSheetFile2);
544 }
545
546 if ($inDocStyles !== '') {
547 $this->pageRenderer->addCssInlineBlock('inDocStyles', $inDocStyles . LF . '/*###POSTCSSMARKER###*/');
548 }
549
550 if ($this->styleSheetFile_post) {
551 $this->pageRenderer->addCssFile($this->styleSheetFile_post);
552 }
553 }
554
555 /**
556 * Add all *.css files of the directory $path to the stylesheets
557 *
558 * @param string $path directory to add
559 */
560 public function addStyleSheetDirectory($path)
561 {
562 $path = GeneralUtility::getFileAbsFileName($path);
563 // Read all files in directory and sort them alphabetically
564 foreach (GeneralUtility::getFilesInDir($path, 'css', true) as $cssFile) {
565 $this->pageRenderer->addCssFile($cssFile);
566 }
567 }
568
569 /**
570 * Insert post rendering document style into already rendered content
571 *
572 * @todo: Review this, it may be obsolete or could be done differently?
573 * @todo: Previous comment was: "This is needed for extobjbase" (AbstractFunctionModule)
574 *
575 * @param string $content style-content to insert.
576 * @return string content with inserted styles
577 */
578 public function insertStylesAndJS($content)
579 {
580 $styles = LF . implode(LF, $this->inDocStylesArray);
581 $content = str_replace('/*###POSTCSSMARKER###*/', $styles, $content);
582
583 // Insert accumulated JS
584 $jscode = $this->JScode . LF . GeneralUtility::wrapJS(implode(LF, $this->JScodeArray));
585 $content = str_replace('<!--###POSTJSMARKER###-->', $jscode, $content);
586 return $content;
587 }
588
589 /**
590 * Returns an array of all stylesheet directories belonging to core and skins
591 *
592 * @return array Stylesheet directories
593 */
594 public function getSkinStylesheetDirectories()
595 {
596 $stylesheetDirectories = [];
597 // Stylesheets from skins
598 // merge default css directories ($this->stylesheetsSkin) with additional ones and include them
599 if (is_array($GLOBALS['TBE_STYLES']['skins'])) {
600 // loop over all registered skins
601 foreach ($GLOBALS['TBE_STYLES']['skins'] as $skinExtKey => $skin) {
602 $skinStylesheetDirs = $this->stylesheetsSkins;
603 // Skins can add custom stylesheetDirectories using
604 // $GLOBALS['TBE_STYLES']['skins']['your_extension_key']['stylesheetDirectories']
605 if (is_array($skin['stylesheetDirectories'])) {
606 $skinStylesheetDirs = array_merge($skinStylesheetDirs, $skin['stylesheetDirectories']);
607 }
608 // Add all registered directories
609 foreach ($skinStylesheetDirs as $stylesheetDir) {
610 // for EXT:myskin/stylesheets/ syntax
611 if (strpos($stylesheetDir, 'EXT:') === 0) {
612 list($extKey, $path) = explode('/', substr($stylesheetDir, 4), 2);
613 if (!empty($extKey) && ExtensionManagementUtility::isLoaded($extKey) && !empty($path)) {
614 $stylesheetDirectories[] = ExtensionManagementUtility::extPath($extKey) . $path;
615 }
616 } else {
617 // For relative paths
618 $stylesheetDirectories[] = ExtensionManagementUtility::extPath($skinExtKey) . $stylesheetDir;
619 }
620 }
621 }
622 }
623 return $stylesheetDirectories;
624 }
625
626 /**
627 * Returns generator meta tag
628 *
629 * @return string <meta> tag with name "generator
630 */
631 public function generator()
632 {
633 return 'TYPO3 CMS, ' . TYPO3_URL_GENERAL . ', &#169; Kasper Sk&#229;rh&#248;j ' . TYPO3_copyright_year . ', extensions are copyright of their respective owners.';
634 }
635
636 /*****************************************
637 *
638 * OTHER ELEMENTS
639 * Tables, buttons, formatting dimmed/red strings
640 *
641 ******************************************/
642
643 /**
644 * Function to load a HTML template file with markers.
645 * When calling from own extension, use syntax getHtmlTemplate('EXT:extkey/template.html')
646 *
647 * @param string $filename tmpl name, usually in the typo3/template/ directory
648 * @return string HTML of template
649 */
650 public function getHtmlTemplate($filename)
651 {
652 // setting the name of the original HTML template
653 $this->moduleTemplateFilename = $filename;
654 if ($GLOBALS['TBE_STYLES']['htmlTemplates'][$filename]) {
655 $filename = $GLOBALS['TBE_STYLES']['htmlTemplates'][$filename];
656 }
657 $filename = GeneralUtility::getFileAbsFileName($filename);
658 return $filename !== '' ? file_get_contents($filename) : '';
659 }
660
661 /**
662 * Define the template for the module
663 *
664 * @param string $filename filename
665 */
666 public function setModuleTemplate($filename)
667 {
668 $this->moduleTemplate = $this->getHtmlTemplate($filename);
669 }
670
671 /**
672 * Put together the various elements for the module <body> using a static HTML
673 * template
674 *
675 * @param array $pageRecord Record of the current page, used for page path and info
676 * @param array $buttons HTML for all buttons
677 * @param array $markerArray HTML for all other markers
678 * @param array $subpartArray HTML for the subparts
679 * @return string Composite HTML
680 */
681 public function moduleBody($pageRecord = [], $buttons = [], $markerArray = [], $subpartArray = [])
682 {
683 // Get the HTML template for the module
684 $moduleBody = $this->templateService->getSubpart($this->moduleTemplate, '###FULLDOC###');
685 // Add CSS
686 $this->inDocStylesArray[] = 'html { overflow: hidden; }';
687 // Get the page path for the docheader
688 $markerArray['PAGEPATH'] = $this->getPagePath($pageRecord);
689 // Get the page info for the docheader
690 $markerArray['PAGEINFO'] = $this->getPageInfo($pageRecord);
691 // Get all the buttons for the docheader
692 $docHeaderButtons = $this->getDocHeaderButtons($buttons);
693 // Merge docheader buttons with the marker array
694 $markerArray = array_merge($markerArray, $docHeaderButtons);
695 // replacing subparts
696 foreach ($subpartArray as $marker => $content) {
697 $moduleBody = $this->templateService->substituteSubpart($moduleBody, $marker, $content);
698 }
699 // adding flash messages
700 if ($this->showFlashMessages) {
701 $flashMessages = $this->getFlashMessages();
702 if (!empty($flashMessages)) {
703 $markerArray['FLASHMESSAGES'] = $flashMessages;
704 // If there is no dedicated marker for the messages present
705 // then force them to appear before the content
706 if (strpos($moduleBody, '###FLASHMESSAGES###') === false) {
707 $moduleBody = str_replace('###CONTENT###', '###FLASHMESSAGES######CONTENT###', $moduleBody);
708 }
709 }
710 }
711 // Hook for adding more markers/content to the page, like the version selector
712 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['moduleBodyPostProcess'] ?? [] as $funcRef) {
713 $params = [
714 'moduleTemplateFilename' => &$this->moduleTemplateFilename,
715 'moduleTemplate' => &$this->moduleTemplate,
716 'moduleBody' => &$moduleBody,
717 'markers' => &$markerArray,
718 'parentObject' => &$this
719 ];
720 GeneralUtility::callUserFunction($funcRef, $params, $this);
721 }
722 // Replacing all markers with the finished markers and return the HTML content
723 return $this->templateService->substituteMarkerArray($moduleBody, $markerArray, '###|###');
724 }
725
726 /**
727 * Get the default rendered FlashMessages from queue
728 *
729 * @return string
730 */
731 public function getFlashMessages()
732 {
733 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageService $flashMessageService */
734 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
735 /** @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue $defaultFlashMessageQueue */
736 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
737 return $defaultFlashMessageQueue->renderFlashMessages();
738 }
739
740 /**
741 * Fill the button lists with the defined HTML
742 *
743 * @param array $buttons HTML for all buttons
744 * @return array Containing HTML for both buttonlists
745 */
746 protected function getDocHeaderButtons($buttons)
747 {
748 $markers = [];
749 // Fill buttons for left and right float
750 $floats = ['left', 'right'];
751 foreach ($floats as $key) {
752 // Get the template for each float
753 $buttonTemplate = $this->templateService->getSubpart($this->moduleTemplate, '###BUTTON_GROUPS_' . strtoupper($key) . '###');
754 // Fill the button markers in this float
755 $buttonTemplate = $this->templateService->substituteMarkerArray($buttonTemplate, $buttons, '###|###', true);
756 // getting the wrap for each group
757 $buttonWrap = $this->templateService->getSubpart($this->moduleTemplate, '###BUTTON_GROUP_WRAP###');
758 // looping through the groups (max 6) and remove the empty groups
759 for ($groupNumber = 1; $groupNumber < 6; $groupNumber++) {
760 $buttonMarker = '###BUTTON_GROUP' . $groupNumber . '###';
761 $buttonGroup = $this->templateService->getSubpart($buttonTemplate, $buttonMarker);
762 if (trim($buttonGroup)) {
763 if ($buttonWrap) {
764 $buttonGroup = $this->templateService->substituteMarker($buttonWrap, '###BUTTONS###', $buttonGroup);
765 }
766 $buttonTemplate = $this->templateService->substituteSubpart($buttonTemplate, $buttonMarker, trim($buttonGroup));
767 }
768 }
769 // Replace the marker with the template and remove all line breaks (for IE compat)
770 $markers['BUTTONLIST_' . strtoupper($key)] = str_replace(LF, '', $buttonTemplate);
771 }
772 // Hook for manipulating docHeaderButtons
773 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['docHeaderButtonsHook'] ?? [] as $funcRef) {
774 $params = [
775 'buttons' => $buttons,
776 'markers' => &$markers,
777 'pObj' => &$this
778 ];
779 GeneralUtility::callUserFunction($funcRef, $params, $this);
780 }
781 return $markers;
782 }
783
784 /**
785 * Generate the page path for docheader
786 *
787 * @param array $pageRecord Current page
788 * @return string Page path
789 */
790 protected function getPagePath($pageRecord)
791 {
792 // Is this a real page
793 if (is_array($pageRecord) && $pageRecord['uid']) {
794 $title = substr($pageRecord['_thePathFull'], 0, -1);
795 // Remove current page title
796 $pos = strrpos($title, $pageRecord['title']);
797 if ($pos !== false) {
798 $title = substr($title, 0, $pos);
799 }
800 } else {
801 $title = '';
802 }
803 // Setting the path of the page
804 $pagePath = htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:core/Resources/Private/Language/locallang_core.xlf:labels.path')) . ': <span class="typo3-docheader-pagePath">';
805 // crop the title to title limit (or 50, if not defined)
806 $cropLength = empty($GLOBALS['BE_USER']->uc['titleLen']) ? 50 : $GLOBALS['BE_USER']->uc['titleLen'];
807 $croppedTitle = GeneralUtility::fixed_lgd_cs($title, -$cropLength);
808 if ($croppedTitle !== $title) {
809 $pagePath .= '<abbr title="' . htmlspecialchars($title) . '">' . htmlspecialchars($croppedTitle) . '</abbr>';
810 } else {
811 $pagePath .= htmlspecialchars($title);
812 }
813 $pagePath .= '</span>';
814 return $pagePath;
815 }
816
817 /**
818 * Setting page icon with context menu + uid for docheader
819 *
820 * @param array $pageRecord Current page
821 * @return string Page info
822 */
823 protected function getPageInfo($pageRecord)
824 {
825 // Add icon with context menu, etc:
826 // If there IS a real page
827 if (is_array($pageRecord) && $pageRecord['uid']) {
828 $alttext = BackendUtility::getRecordIconAltText($pageRecord, 'pages');
829 $iconImg = '<span title="' . htmlspecialchars($alttext) . '">' . $this->iconFactory->getIconForRecord('pages', $pageRecord, Icon::SIZE_SMALL)->render() . '</span>';
830 // Make Icon:
831 $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, 'pages', $pageRecord['uid']);
832 $uid = $pageRecord['uid'];
833 $title = BackendUtility::getRecordTitle('pages', $pageRecord);
834 } else {
835 // On root-level of page tree
836 // Make Icon
837 $iconImg = '<span title="' . htmlspecialchars($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) . '">' . $this->iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render() . '</span>';
838 if ($GLOBALS['BE_USER']->isAdmin()) {
839 $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, 'pages');
840 } else {
841 $theIcon = $iconImg;
842 }
843 $uid = '0';
844 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
845 }
846 // Setting icon with context menu + uid
847 $pageInfo = $theIcon . '<strong>' . htmlspecialchars($title) . '&nbsp;[' . $uid . ']</strong>';
848 return $pageInfo;
849 }
850
851 /**
852 * Retrieves configured favicon for backend (with fallback)
853 *
854 * @return string
855 */
856 protected function getBackendFavicon()
857 {
858 $backendFavicon = GeneralUtility::makeInstance(ExtensionConfiguration::class)->get('backend', 'backendFavicon');
859 if (!empty($backendFavicon)) {
860 $path = $this->getUriForFileName($backendFavicon);
861 } else {
862 $path = ExtensionManagementUtility::extPath('backend') . 'Resources/Public/Icons/favicon.ico';
863 }
864 return PathUtility::getAbsoluteWebPath($path);
865 }
866
867 /**
868 * Returns the uri of a relative reference, resolves the "EXT:" prefix
869 * (way of referring to files inside extensions) and checks that the file is inside
870 * the project root of the TYPO3 installation
871 *
872 * @param string $filename The input filename/filepath to evaluate
873 * @return string Returns the filename of $filename if valid, otherwise blank string.
874 */
875 protected function getUriForFileName($filename)
876 {
877 if (strpos($filename, '://')) {
878 return $filename;
879 }
880 $urlPrefix = '';
881 if (strpos($filename, 'EXT:') === 0) {
882 $absoluteFilename = GeneralUtility::getFileAbsFileName($filename);
883 $filename = '';
884 if ($absoluteFilename !== '') {
885 $filename = PathUtility::getAbsoluteWebPath($absoluteFilename);
886 }
887 } elseif (strpos($filename, '/') !== 0) {
888 $urlPrefix = GeneralUtility::getIndpEnv('TYPO3_SITE_PATH');
889 }
890 return $urlPrefix . $filename;
891 }
892
893 /**
894 * @return BackendUserAuthentication|null
895 */
896 protected function getBackendUser()
897 {
898 return $GLOBALS['BE_USER'] ?? null;
899 }
900
901 /**
902 * @return LanguageService|null
903 */
904 protected function getLanguageService(): ?LanguageService
905 {
906 return $GLOBALS['LANG'] ?? null;
907 }
908 }