2 namespace TYPO3\CMS\Backend\Template
;
5 * This file is part of the TYPO3 CMS project.
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.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use Psr\Log\LoggerAwareInterface
;
18 use Psr\Log\LoggerAwareTrait
;
19 use TYPO3\CMS\Backend\Utility\BackendUtility
;
20 use TYPO3\CMS\Core\Imaging\Icon
;
21 use TYPO3\CMS\Core\Imaging\IconFactory
;
22 use TYPO3\CMS\Core\Page\PageRenderer
;
23 use TYPO3\CMS\Core\Service\MarkerBasedTemplateService
;
24 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility
;
25 use TYPO3\CMS\Core\Utility\GeneralUtility
;
26 use TYPO3\CMS\Core\Utility\PathUtility
;
29 * TYPO3 Backend Template Class
31 * This class contains functions for starting and ending the HTML of backend modules
32 * It also contains methods for outputting sections of content.
33 * Further there are functions for making icons, links, setting form-field widths etc.
34 * Color scheme and stylesheet definitions are also available here.
35 * Finally this file includes the language class for TYPO3's backend.
37 * After this file $LANG and $TBE_TEMPLATE are global variables / instances of their respective classes.
39 * Please refer to Inside TYPO3 for a discussion of how to use this API.
41 class DocumentTemplate
implements LoggerAwareInterface
45 // Vars you typically might want to/should set from outside after making instance of this class:
47 * This can be set to the HTML-code for a formtag.
48 * Useful when you need a form to span the whole page; Inserted exactly after the body-tag.
55 * Additional header code (eg. a JavaScript section) could be accommulated in this var. It will be directly outputted in the header.
62 * 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.
66 public $JScodeArray = ['jumpToUrl' => '
67 function jumpToUrl(URL) {
68 window.location.href = URL;
74 * 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.
78 public $postCode = '';
81 * HTML template with markers for module
85 public $moduleTemplate = '';
88 * The base file (not overlaid by TBE_STYLES) for the current module, useful for hooks when finding out which modules is rendered currently
92 protected $moduleTemplateFilename = '';
99 public $scriptID = '';
102 * Id which can be set for the body tag. Default value is based on script ID
106 public $bodyTagId = '';
109 * You can add additional attributes to the body-tag through this variable.
113 public $bodyTagAdditions = '';
116 * Additional CSS styles which will be added to the <style> section in the header
117 * used as array with associative keys to prevent double inclusion of CSS code
121 public $inDocStylesArray = [];
124 * Filename of stylesheet
128 public $styleSheetFile = '';
131 * Filename of stylesheet #2 - linked to right after the $this->styleSheetFile script
135 public $styleSheetFile2 = '';
138 * Filename of a post-stylesheet - included right after all inline styles.
142 public $styleSheetFile_post = '';
145 * Whether to use the X-UA-Compatible meta tag
149 protected $useCompatibilityTag = true;
152 * X-Ua-Compatible version output in meta tag
156 protected $xUaCompatibilityVersion = 'IE=edge';
160 * Include these CSS directories from skins by default
164 protected $stylesheetsSkins = [
165 'structure' => 'Resources/Public/Css/structure/',
166 'visual' => 'Resources/Public/Css/visual/'
170 * JavaScript files loaded for every page in the Backend
174 protected $jsFiles = [];
177 * JavaScript files loaded for every page in the Backend, but explicitly excluded from concatenation (useful for libraries etc.)
181 protected $jsFilesNoConcatenation = [];
184 * Indicates if a <div>-output section is open
187 * @internal will be removed in TYPO3 v9
189 public $sectionFlag = 0;
192 * (Default) Class for wrapping <DIV>-tag of page. Is set in class extensions.
196 public $divClass = '';
201 public $pageHeaderBlock = '';
206 public $endOfPageJsBlock = '';
211 public $hasDocheader = true;
216 protected $pageRenderer = null;
219 * Alternative template file
223 protected $pageHeaderFooterTemplateFile = '';
226 * Whether flashmessages should be rendered or not
228 * @var bool $showFlashMessages
230 public $showFlashMessages = true;
235 protected $iconFactory;
238 * @var MarkerBasedTemplateService
240 protected $templateService;
245 public function __construct()
247 // Initializes the page rendering object:
248 $this->initPageRenderer();
250 $this->iconFactory
= GeneralUtility
::makeInstance(IconFactory
::class);
252 // initialize Marker Support
253 $this->templateService
= GeneralUtility
::makeInstance(MarkerBasedTemplateService
::class);
255 // Setting default scriptID, trim forward slash from route
256 $this->scriptID
= ltrim(GeneralUtility
::_GET('route'), '/');
257 $this->bodyTagId
= preg_replace('/[^A-Za-z0-9-]/', '-', $this->scriptID
);
258 // Individual configuration per script? If so, make a recursive merge of the arrays:
259 if (is_array($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID
] ??
false)) {
261 $ovr = $GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID
];
263 \TYPO3\CMS\Core\Utility\ArrayUtility
::mergeRecursiveWithOverrule($GLOBALS['TBE_STYLES'], $ovr);
264 // Have to unset - otherwise the second instantiation will do it again!
265 unset($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID
]);
268 if (!empty($GLOBALS['TBE_STYLES']['stylesheet'])) {
269 $this->styleSheetFile
= $GLOBALS['TBE_STYLES']['stylesheet'];
271 if (!empty($GLOBALS['TBE_STYLES']['stylesheet2'])) {
272 $this->styleSheetFile2
= $GLOBALS['TBE_STYLES']['stylesheet2'];
274 if (!empty($GLOBALS['TBE_STYLES']['styleSheetFile_post'])) {
275 $this->styleSheetFile_post
= $GLOBALS['TBE_STYLES']['styleSheetFile_post'];
277 if (!empty($GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle'])) {
278 $this->inDocStylesArray
['TBEstyle'] = $GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle'];
280 // include all stylesheets
281 foreach ($this->getSkinStylesheetDirectories() as $stylesheetDirectory) {
282 $this->addStyleSheetDirectory($stylesheetDirectory);
287 * Initializes the page renderer object
289 protected function initPageRenderer()
291 if ($this->pageRenderer
!== null) {
294 $this->pageRenderer
= GeneralUtility
::makeInstance(PageRenderer
::class);
295 $this->pageRenderer
->setLanguage($GLOBALS['LANG']->lang
);
296 $this->pageRenderer
->enableConcatenateFiles();
297 $this->pageRenderer
->enableCompressCss();
298 $this->pageRenderer
->enableCompressJavascript();
299 // Add all JavaScript files defined in $this->jsFiles to the PageRenderer
300 foreach ($this->jsFilesNoConcatenation
as $file) {
301 $this->pageRenderer
->addJsFile(
310 // Add all JavaScript files defined in $this->jsFiles to the PageRenderer
311 foreach ($this->jsFiles
as $file) {
312 $this->pageRenderer
->addJsFile($file);
314 if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] === 1) {
315 $this->pageRenderer
->enableDebugMode();
319 /*****************************************
321 * EVALUATION FUNCTIONS
322 * Various centralized processing
324 *****************************************/
327 * Returns a linked shortcut-icon which will call the shortcut frame and set a shortcut there back to the calling page/module
329 * @param string $gvList Is the list of GET variables to store (if any)
330 * @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
331 * @param string $modName Module name string
332 * @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.
333 * @param string $classes
334 * @return string HTML content
336 public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '', $classes = '')
338 $gvList = 'route,' . $gvList;
339 $storeUrl = $this->makeShortcutUrl($gvList, $setList);
340 $pathInfo = parse_url(GeneralUtility
::getIndpEnv('REQUEST_URI'));
341 // Fallback for alt_mod. We still pass in the old xMOD... stuff, but TBE_MODULES only knows about "record_edit".
342 // We still need to pass the xMOD name to createShortcut below, since this is used for icons.
343 $moduleName = $modName === 'xMOD_alt_doc.php' ?
'record_edit' : $modName;
344 // Add the module identifier automatically if typo3/index.php is used:
345 if (GeneralUtility
::_GET('M') !== null) {
346 $storeUrl = '&M=' . $moduleName . $storeUrl;
348 if ((int)$motherModName === 1) {
349 $motherModule = 'top.currentModuleLoaded';
350 } elseif ($motherModName) {
351 $motherModule = GeneralUtility
::quoteJSvalue($motherModName);
353 $motherModule = '\'\'';
355 $confirmationText = GeneralUtility
::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark'));
357 $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
358 $shortcutExist = BackendUtility
::shortcutExists($shortcutUrl);
360 if ($shortcutExist) {
361 return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
362 $this->iconFactory
->getIcon('actions-system-shortcut-active', Icon
::SIZE_SMALL
)->render() . '</a>';
364 $url = GeneralUtility
::quoteJSvalue(rawurlencode($shortcutUrl));
365 $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility
::quoteJSvalue(rawurlencode($modName)) .
366 ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this);return false;';
368 return '<a href="#" class="' . htmlspecialchars($classes) . '" onclick="' . htmlspecialchars($onClick) . '" title="' .
369 htmlspecialchars($GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')) . '">' .
370 $this->iconFactory
->getIcon('actions-system-shortcut-new', Icon
::SIZE_SMALL
)->render() . '</a>';
374 * MAKE url for storing
377 * @param string $gvList Is the list of GET variables to store (if any)
378 * @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
381 * @see makeShortcutIcon()
383 public function makeShortcutUrl($gvList, $setList)
385 $GET = GeneralUtility
::_GET();
386 $storeArray = array_merge(GeneralUtility
::compileSelectedGetVarsFromArray($gvList, $GET), ['SET' => GeneralUtility
::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS
)]);
387 $storeUrl = GeneralUtility
::implodeArrayForUrl('', $storeArray);
392 * Returns <input> attributes to set the width of an text-type input field.
393 * For client browsers with no CSS support the cols/size attribute is returned.
394 * For CSS compliant browsers (recommended) a ' style="width: ...px;"' is returned.
396 * @param int $size A relative number which multiplied with approx. 10 will lead to the width in pixels
397 * @param bool $textarea A flag you can set for textareas - DEPRECATED as there is no difference any more between the two
398 * @param string $styleOverride A string which will be returned as attribute-value for style="" instead of the calculated width (if CSS is enabled)
399 * @return string Tag attributes for an <input> tag (regarding width)
402 public function formWidth($size = 48, $textarea = false, $styleOverride = '')
404 trigger_error('This method will be removed in TYPO3 10 - use responsive code or direct inline styles to format your input fields instead.', E_USER_DEPRECATED
);
405 return ' style="' . ($styleOverride ?
: 'width:' . ceil($size * 9.58) . 'px;') . '"';
409 * Returns JavaScript variables setting the returnUrl and thisScript location for use by JavaScript on the page.
410 * Used in fx. db_list.php (Web>List)
412 * @param string $thisLocation URL to "this location" / current script
413 * @return string Urls are returned as JavaScript variables T3_RETURN_URL and T3_THIS_LOCATION
414 * @see typo3/db_list.php
416 public function redirectUrls($thisLocation = '')
418 $thisLocation = $thisLocation ?
$thisLocation : GeneralUtility
::linkThisScript([
425 var T3_RETURN_URL = ' . GeneralUtility
::quoteJSvalue(str_replace('%20', '', rawurlencode(GeneralUtility
::sanitizeLocalUrl(GeneralUtility
::_GP('returnUrl'))))) . ';
426 var T3_THIS_LOCATION = ' . GeneralUtility
::quoteJSvalue(str_replace('%20', '', rawurlencode($thisLocation))) . '
432 * Defines whether to use the X-UA-Compatible meta tag.
434 * @param bool $useCompatibilityTag Whether to use the tag
436 public function useCompatibilityTag($useCompatibilityTag = true)
438 $this->useCompatibilityTag
= (bool)$useCompatibilityTag;
441 /*****************************************
443 * PAGE BUILDING FUNCTIONS.
444 * Use this to build the HTML of your backend modules
446 *****************************************/
449 * This includes the proper header with charset, title, meta tag and beginning body-tag.
451 * @param string $title HTML Page title for the header
452 * @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)
455 public function startPage($title)
457 // hook pre start page
458 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'])) {
459 $preStartPageHook = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'];
460 if (is_array($preStartPageHook)) {
464 foreach ($preStartPageHook as $hookFunction) {
465 GeneralUtility
::callUserFunction($hookFunction, $hookParameters, $this);
469 // alternative template for Header and Footer
470 if ($this->pageHeaderFooterTemplateFile
) {
471 $file = GeneralUtility
::getFileAbsFileName($this->pageHeaderFooterTemplateFile
);
473 $this->pageRenderer
->setTemplateFile($file);
477 // Disable rendering of XHTML tags
478 $this->pageRenderer
->setRenderXhtml(false);
480 $languageCode = $this->pageRenderer
->getLanguage() === 'default' ?
'en' : $this->pageRenderer
->getLanguage();
481 $this->pageRenderer
->setHtmlTag('<html lang="' . $languageCode . '">');
483 $headerStart = '<!DOCTYPE html>';
484 $this->pageRenderer
->setXmlPrologAndDocType($headerStart);
485 $this->pageRenderer
->setHeadTag('<head>' . LF
. '<!-- TYPO3 Script ID: ' . htmlspecialchars($this->scriptID
) . ' -->');
486 header('Content-Type:text/html;charset=utf-8');
487 $this->pageRenderer
->setCharSet('utf-8');
488 $this->pageRenderer
->setMetaTag('name', 'generator', $this->generator());
489 $this->pageRenderer
->setMetaTag('name', 'robots', 'noindex,follow');
490 $this->pageRenderer
->setMetaTag('name', 'viewport', 'width=device-width, initial-scale=1');
491 $this->pageRenderer
->setFavIcon($this->getBackendFavicon());
492 if ($this->useCompatibilityTag
) {
493 $this->pageRenderer
->setMetaTag('http-equiv', 'X-UA-Compatible', $this->xUaCompatibilityVersion
);
495 $this->pageRenderer
->setTitle($title);
498 $this->pageRenderer
->addHeaderData($this->JScode
);
499 foreach ($this->JScodeArray
as $name => $code) {
500 $this->pageRenderer
->addJsInlineCode($name, $code, false);
503 // Load jquery and twbs JS libraries on every backend request
504 $this->pageRenderer
->loadJquery();
505 // Note: please do not reference "bootstrap" outside of the TYPO3 Core (not in your own extensions)
506 // as this is preliminary as long as Twitter bootstrap does not support AMD modules
507 // this logic will be changed once Twitter bootstrap 4 is included
508 $this->pageRenderer
->addJsFile('EXT:core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap.js');
510 // hook for additional headerData
511 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preHeaderRenderHook'])) {
512 $preHeaderRenderHook = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preHeaderRenderHook'];
513 if (is_array($preHeaderRenderHook)) {
515 'pageRenderer' => &$this->pageRenderer
517 foreach ($preHeaderRenderHook as $hookFunction) {
518 GeneralUtility
::callUserFunction($hookFunction, $hookParameters, $this);
522 // Construct page header.
523 $str = $this->pageRenderer
->render(PageRenderer
::PART_HEADER
);
525 $this->JScodeArray
= [];
526 $this->endOfPageJsBlock
= $this->pageRenderer
->render(PageRenderer
::PART_FOOTER
);
527 $str .= $this->docBodyTagBegin() . ($this->divClass ?
'
529 <!-- Wrapping DIV-section for whole page BEGIN -->
530 <div class="' . $this->divClass
. '">
531 ' : '') . trim($this->form
);
536 * Returns page end; This includes finishing form, div, body and html tags.
538 * @return string The HTML end of a page
541 public function endPage()
543 $str = $this->postCode
. GeneralUtility
::wrapJS(BackendUtility
::getUpdateSignalCode()) . ($this->form ?
'
545 // If something is in buffer like debug, put it to end of page
546 if (ob_get_contents()) {
547 $str .= ob_get_clean();
548 if (!headers_sent()) {
549 header('Content-Encoding: None');
552 $str .= ($this->divClass ?
'
554 <!-- Wrapping DIV-section for whole page END -->
555 </div>' : '') . $this->endOfPageJsBlock
;
557 // Logging: Can't find better place to put it:
558 $this->logger
->debug('END of BACKEND session', ['_FLUSH' => true]);
563 * Shortcut for render the complete page of a module
565 * @param string $title page title
566 * @param string $content page content
567 * @return string complete page
569 public function render($title, $content)
571 $pageContent = $this->startPage($title);
572 $pageContent .= $content;
573 $pageContent .= $this->endPage();
574 return $this->insertStylesAndJS($pageContent);
578 * Creates the bodyTag.
579 * You can add to the bodyTag by $this->bodyTagAdditions
581 * @return string HTML body tag
583 public function docBodyTagBegin()
585 return '<body ' . trim($this->bodyTagAdditions
. ($this->bodyTagId ?
' id="' . $this->bodyTagId
. '"' : '')) . '>';
589 * Outputting document style
591 * @return string HTML style section/link tags
593 public function docStyle()
596 $inDocStyles = implode(LF
, $this->inDocStylesArray
);
598 // Reset styles so they won't be added again in insertStylesAndJS()
599 $this->inDocStylesArray
= [];
601 if ($this->styleSheetFile
) {
602 $this->pageRenderer
->addCssFile($this->styleSheetFile
);
604 if ($this->styleSheetFile2
) {
605 $this->pageRenderer
->addCssFile($this->styleSheetFile2
);
608 if ($inDocStyles !== '') {
609 $this->pageRenderer
->addCssInlineBlock('inDocStyles', $inDocStyles . LF
. '/*###POSTCSSMARKER###*/');
612 if ($this->styleSheetFile_post
) {
613 $this->pageRenderer
->addCssFile($this->styleSheetFile_post
);
618 * Insert additional style sheet link
620 * @param string $key some key identifying the style sheet
621 * @param string $href uri to the style sheet file
622 * @param string $title value for the title attribute of the link element
623 * @param string $relation value for the rel attribute of the link element
625 public function addStyleSheet($key, $href, $title = '', $relation = 'stylesheet')
627 $this->pageRenderer
->addCssFile($href, $relation, 'screen', $title);
631 * Add all *.css files of the directory $path to the stylesheets
633 * @param string $path directory to add
635 public function addStyleSheetDirectory($path)
637 $path = GeneralUtility
::getFileAbsFileName($path);
638 // Read all files in directory and sort them alphabetically
639 $cssFiles = GeneralUtility
::getFilesInDir($path, 'css');
640 foreach ($cssFiles as $cssFile) {
641 $this->pageRenderer
->addCssFile(PathUtility
::getRelativePathTo($path) . $cssFile);
646 * Insert post rendering document style into already rendered content
647 * This is needed for extobjbase
649 * @param string $content style-content to insert.
650 * @return string content with inserted styles
652 public function insertStylesAndJS($content)
654 $styles = LF
. implode(LF
, $this->inDocStylesArray
);
655 $content = str_replace('/*###POSTCSSMARKER###*/', $styles, $content);
657 // Insert accumulated JS
658 $jscode = $this->JScode
. LF
. GeneralUtility
::wrapJS(implode(LF
, $this->JScodeArray
));
659 $content = str_replace('<!--###POSTJSMARKER###-->', $jscode, $content);
664 * Returns an array of all stylesheet directories belonging to core and skins
666 * @return array Stylesheet directories
668 public function getSkinStylesheetDirectories()
670 $stylesheetDirectories = [];
671 // Stylesheets from skins
672 // merge default css directories ($this->stylesheetsSkin) with additional ones and include them
673 if (is_array($GLOBALS['TBE_STYLES']['skins'])) {
674 // loop over all registered skins
675 foreach ($GLOBALS['TBE_STYLES']['skins'] as $skinExtKey => $skin) {
676 $skinStylesheetDirs = $this->stylesheetsSkins
;
677 // Skins can add custom stylesheetDirectories using
678 // $GLOBALS['TBE_STYLES']['skins'][$_EXTKEY]['stylesheetDirectories']
679 if (is_array($skin['stylesheetDirectories'])) {
680 $skinStylesheetDirs = array_merge($skinStylesheetDirs, $skin['stylesheetDirectories']);
682 // Add all registered directories
683 foreach ($skinStylesheetDirs as $stylesheetDir) {
684 // for EXT:myskin/stylesheets/ syntax
685 if (strpos($stylesheetDir, 'EXT:') === 0) {
686 list($extKey, $path) = explode('/', substr($stylesheetDir, 4), 2);
687 if (!empty($extKey) && ExtensionManagementUtility
::isLoaded($extKey) && !empty($path)) {
688 $stylesheetDirectories[] = ExtensionManagementUtility
::extPath($extKey) . $path;
691 // For relative paths
692 $stylesheetDirectories[] = ExtensionManagementUtility
::extPath($skinExtKey) . $stylesheetDir;
697 return $stylesheetDirectories;
701 * Returns generator meta tag
703 * @return string <meta> tag with name "generator
705 public function generator()
707 return 'TYPO3 CMS, ' . TYPO3_URL_GENERAL
. ', © Kasper Skårhøj ' . TYPO3_copyright_year
. ', extensions are copyright of their respective owners.';
711 * Returns X-UA-Compatible meta tag
714 * @param string $content Content of the compatible tag (default: IE-8)
715 * @return string <meta http-equiv="X-UA-Compatible" content="???" />
717 public function xUaCompatible($content = 'IE=8')
719 trigger_error('Method DocumentTemplate->xUaCompatible is deprecated and will be removed with v10. Use pageRenderer->setMetaTag instead.', E_USER_DEPRECATED
);
720 return '<meta http-equiv="X-UA-Compatible" content="' . $content . '" />';
723 /*****************************************
726 * Tables, buttons, formatting dimmed/red strings
728 ******************************************/
731 * Function to load a HTML template file with markers.
732 * When calling from own extension, use syntax getHtmlTemplate('EXT:extkey/template.html')
734 * @param string $filename tmpl name, usually in the typo3/template/ directory
735 * @return string HTML of template
737 public function getHtmlTemplate($filename)
739 // setting the name of the original HTML template
740 $this->moduleTemplateFilename
= $filename;
741 if ($GLOBALS['TBE_STYLES']['htmlTemplates'][$filename]) {
742 $filename = $GLOBALS['TBE_STYLES']['htmlTemplates'][$filename];
744 if (GeneralUtility
::isFirstPartOfStr($filename, 'EXT:')) {
745 $filename = GeneralUtility
::getFileAbsFileName($filename);
746 } elseif (!GeneralUtility
::isAbsPath($filename)) {
747 $filename = GeneralUtility
::resolveBackPath($filename);
748 } elseif (!GeneralUtility
::isAllowedAbsPath($filename)) {
752 if ($filename !== '') {
753 $htmlTemplate = file_get_contents($filename);
755 return $htmlTemplate;
759 * Define the template for the module
761 * @param string $filename filename
763 public function setModuleTemplate($filename)
765 $this->moduleTemplate
= $this->getHtmlTemplate($filename);
769 * Put together the various elements for the module <body> using a static HTML
772 * @param array $pageRecord Record of the current page, used for page path and info
773 * @param array $buttons HTML for all buttons
774 * @param array $markerArray HTML for all other markers
775 * @param array $subpartArray HTML for the subparts
776 * @return string Composite HTML
778 public function moduleBody($pageRecord = [], $buttons = [], $markerArray = [], $subpartArray = [])
780 // Get the HTML template for the module
781 $moduleBody = $this->templateService
->getSubpart($this->moduleTemplate
, '###FULLDOC###');
783 $this->inDocStylesArray
[] = 'html { overflow: hidden; }';
784 // Get the page path for the docheader
785 $markerArray['PAGEPATH'] = $this->getPagePath($pageRecord);
786 // Get the page info for the docheader
787 $markerArray['PAGEINFO'] = $this->getPageInfo($pageRecord);
788 // Get all the buttons for the docheader
789 $docHeaderButtons = $this->getDocHeaderButtons($buttons);
790 // Merge docheader buttons with the marker array
791 $markerArray = array_merge($markerArray, $docHeaderButtons);
792 // replacing subparts
793 foreach ($subpartArray as $marker => $content) {
794 $moduleBody = $this->templateService
->substituteSubpart($moduleBody, $marker, $content);
796 // adding flash messages
797 if ($this->showFlashMessages
) {
798 $flashMessages = $this->getFlashMessages();
799 if (!empty($flashMessages)) {
800 $markerArray['FLASHMESSAGES'] = $flashMessages;
801 // If there is no dedicated marker for the messages present
802 // then force them to appear before the content
803 if (strpos($moduleBody, '###FLASHMESSAGES###') === false) {
804 $moduleBody = str_replace('###CONTENT###', '###FLASHMESSAGES######CONTENT###', $moduleBody);
808 // Hook for adding more markers/content to the page, like the version selector
809 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['moduleBodyPostProcess'])) {
811 'moduleTemplateFilename' => &$this->moduleTemplateFilename
,
812 'moduleTemplate' => &$this->moduleTemplate
,
813 'moduleBody' => &$moduleBody,
814 'markers' => &$markerArray,
815 'parentObject' => &$this
817 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['moduleBodyPostProcess'] as $funcRef) {
818 GeneralUtility
::callUserFunction($funcRef, $params, $this);
821 // Replacing all markers with the finished markers and return the HTML content
822 return $this->templateService
->substituteMarkerArray($moduleBody, $markerArray, '###|###');
826 * Get the default rendered FlashMessages from queue
830 public function getFlashMessages()
832 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
833 $flashMessageService = GeneralUtility
::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService
::class);
834 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
835 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
836 return $defaultFlashMessageQueue->renderFlashMessages();
840 * Fill the button lists with the defined HTML
842 * @param array $buttons HTML for all buttons
843 * @return array Containing HTML for both buttonlists
845 protected function getDocHeaderButtons($buttons)
848 // Fill buttons for left and right float
849 $floats = ['left', 'right'];
850 foreach ($floats as $key) {
851 // Get the template for each float
852 $buttonTemplate = $this->templateService
->getSubpart($this->moduleTemplate
, '###BUTTON_GROUPS_' . strtoupper($key) . '###');
853 // Fill the button markers in this float
854 $buttonTemplate = $this->templateService
->substituteMarkerArray($buttonTemplate, $buttons, '###|###', true);
855 // getting the wrap for each group
856 $buttonWrap = $this->templateService
->getSubpart($this->moduleTemplate
, '###BUTTON_GROUP_WRAP###');
857 // looping through the groups (max 6) and remove the empty groups
858 for ($groupNumber = 1; $groupNumber < 6; $groupNumber++
) {
859 $buttonMarker = '###BUTTON_GROUP' . $groupNumber . '###';
860 $buttonGroup = $this->templateService
->getSubpart($buttonTemplate, $buttonMarker);
861 if (trim($buttonGroup)) {
863 $buttonGroup = $this->templateService
->substituteMarker($buttonWrap, '###BUTTONS###', $buttonGroup);
865 $buttonTemplate = $this->templateService
->substituteSubpart($buttonTemplate, $buttonMarker, trim($buttonGroup));
868 // Replace the marker with the template and remove all line breaks (for IE compat)
869 $markers['BUTTONLIST_' . strtoupper($key)] = str_replace(LF
, '', $buttonTemplate);
871 // Hook for manipulating docHeaderButtons
872 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['docHeaderButtonsHook'])) {
874 'buttons' => $buttons,
875 'markers' => &$markers,
878 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['docHeaderButtonsHook'] as $funcRef) {
879 GeneralUtility
::callUserFunction($funcRef, $params, $this);
886 * Generate the page path for docheader
888 * @param array $pageRecord Current page
889 * @return string Page path
891 protected function getPagePath($pageRecord)
893 // Is this a real page
894 if (is_array($pageRecord) && $pageRecord['uid']) {
895 $title = substr($pageRecord['_thePathFull'], 0, -1);
896 // Remove current page title
897 $pos = strrpos($title, $pageRecord['title']);
898 if ($pos !== false) {
899 $title = substr($title, 0, $pos);
904 // Setting the path of the page
905 $pagePath = htmlspecialchars($GLOBALS['LANG']->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.path')) . ': <span class="typo3-docheader-pagePath">';
906 // crop the title to title limit (or 50, if not defined)
907 $cropLength = empty($GLOBALS['BE_USER']->uc
['titleLen']) ?
50 : $GLOBALS['BE_USER']->uc
['titleLen'];
908 $croppedTitle = GeneralUtility
::fixed_lgd_cs($title, -$cropLength);
909 if ($croppedTitle !== $title) {
910 $pagePath .= '<abbr title="' . htmlspecialchars($title) . '">' . htmlspecialchars($croppedTitle) . '</abbr>';
912 $pagePath .= htmlspecialchars($title);
914 $pagePath .= '</span>';
919 * Setting page icon with context menu + uid for docheader
921 * @param array $pageRecord Current page
922 * @return string Page info
924 protected function getPageInfo($pageRecord)
926 // Add icon with context menu, etc:
927 // If there IS a real page
928 if (is_array($pageRecord) && $pageRecord['uid']) {
929 $alttext = BackendUtility
::getRecordIconAltText($pageRecord, 'pages');
930 $iconImg = '<span title="' . htmlspecialchars($alttext) . '">' . $this->iconFactory
->getIconForRecord('pages', $pageRecord, Icon
::SIZE_SMALL
)->render() . '</span>';
932 $theIcon = BackendUtility
::wrapClickMenuOnIcon($iconImg, 'pages', $pageRecord['uid']);
933 $uid = $pageRecord['uid'];
934 $title = BackendUtility
::getRecordTitle('pages', $pageRecord);
936 // On root-level of page tree
938 $iconImg = '<span title="' . htmlspecialchars($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) . '">' . $this->iconFactory
->getIcon('apps-pagetree-root', Icon
::SIZE_SMALL
)->render() . '</span>';
939 if ($GLOBALS['BE_USER']->isAdmin()) {
940 $theIcon = BackendUtility
::wrapClickMenuOnIcon($iconImg, 'pages');
945 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
947 // Setting icon with context menu + uid
948 $pageInfo = $theIcon . '<strong>' . htmlspecialchars($title) . ' [' . $uid . ']</strong>';
953 * Retrieves configured favicon for backend (with fallback)
957 protected function getBackendFavicon()
959 $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf']['backend'], ['allowed_classes' => false]);
961 if (!empty($extConf['backendFavicon'])) {
962 $path = $this->getUriForFileName($extConf['backendFavicon']);
964 $path = ExtensionManagementUtility
::extPath('backend') . 'Resources/Public/Icons/favicon.ico';
966 return PathUtility
::getAbsoluteWebPath($path);
970 * Returns the uri of a relative reference, resolves the "EXT:" prefix
971 * (way of referring to files inside extensions) and checks that the file is inside
972 * the PATH_site of the TYPO3 installation
974 * @param string $filename The input filename/filepath to evaluate
975 * @return string Returns the filename of $filename if valid, otherwise blank string.
977 protected function getUriForFileName($filename)
979 if (strpos($filename, '://')) {
983 if (strpos($filename, 'EXT:') === 0) {
984 $absoluteFilename = GeneralUtility
::getFileAbsFileName($filename);
986 if ($absoluteFilename !== '') {
987 $filename = PathUtility
::getAbsoluteWebPath($absoluteFilename);
989 } elseif (strpos($filename, '/') !== 0) {
990 $urlPrefix = GeneralUtility
::getIndpEnv('TYPO3_SITE_PATH');
992 return $urlPrefix . $filename;