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