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