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