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