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