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