[TASK] Redesign TYPO3 error page
[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\Fluid\View\StandaloneView;
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 * Additional header code for ExtJS. It will be included in document header and inserted in a Ext.onReady(function()
61 *
62 * @var string
63 */
64 public $extJScode = '';
65
66 /**
67 * Similar to $JScode but for use as array with associative keys to prevent double inclusion of JS code. a <script> tag is automatically wrapped around.
68 *
69 * @var array
70 */
71 public $JScodeArray = array('jumpToUrl' => '
72 function jumpToUrl(URL) {
73 window.location.href = URL;
74 return false;
75 }
76 ');
77
78 /**
79 * Additional 'page-end' code could be accumulated in this var. It will be outputted at the end of page before </body> and some other internal page-end code.
80 *
81 * @var string
82 */
83 public $postCode = '';
84
85 /**
86 * HTML template with markers for module
87 *
88 * @var string
89 */
90 public $moduleTemplate = '';
91
92 /**
93 * The base file (not overlaid by TBE_STYLES) for the current module, useful for hooks when finding out which modules is rendered currently
94 *
95 * @var string
96 */
97 protected $moduleTemplateFilename = '';
98
99 /**
100 * Script ID
101 *
102 * @var string
103 */
104 public $scriptID = '';
105
106 /**
107 * Id which can be set for the body tag. Default value is based on script ID
108 *
109 * @var string
110 */
111 public $bodyTagId = '';
112
113 /**
114 * You can add additional attributes to the body-tag through this variable.
115 *
116 * @var string
117 */
118 public $bodyTagAdditions = '';
119
120 /**
121 * Additional CSS styles which will be added to the <style> section in the header
122 * used as array with associative keys to prevent double inclusion of CSS code
123 *
124 * @var array
125 */
126 public $inDocStylesArray = array();
127
128 /**
129 * Compensation for large documents (used in \TYPO3\CMS\Backend\Form\FormEngine)
130 *
131 * @var float
132 */
133 public $form_largeComp = 1.33;
134
135 /**
136 * Filename of stylesheet (relative to PATH_typo3)
137 *
138 * @var string
139 */
140 public $styleSheetFile = '';
141
142 /**
143 * Filename of stylesheet #2 - linked to right after the $this->styleSheetFile script (relative to PATH_typo3)
144 *
145 * @var string
146 */
147 public $styleSheetFile2 = '';
148
149 /**
150 * Filename of a post-stylesheet - included right after all inline styles.
151 *
152 * @var string
153 */
154 public $styleSheetFile_post = '';
155
156 /**
157 * Whether to use the X-UA-Compatible meta tag
158 *
159 * @var bool
160 */
161 protected $useCompatibilityTag = true;
162
163 /**
164 * X-Ua-Compatible version output in meta tag
165 *
166 * @var string
167 */
168 protected $xUaCompatibilityVersion = 'IE=edge';
169
170 // Skinning
171 /**
172 * stylesheets from core
173 *
174 * @var array
175 */
176 protected $stylesheetsCore = array(
177 'generatedSprites' => '../typo3temp/sprites/'
178 );
179
180 /**
181 * Include these CSS directories from skins by default
182 *
183 * @var array
184 */
185 protected $stylesheetsSkins = array(
186 'structure' => 'Resources/Public/Css/structure/',
187 'visual' => 'Resources/Public/Css/visual/'
188 );
189
190 /**
191 * JavaScript files loaded for every page in the Backend
192 *
193 * @var array
194 */
195 protected $jsFiles = array();
196
197 /**
198 * JavaScript files loaded for every page in the Backend, but explicitly excluded from concatenation (useful for libraries etc.)
199 *
200 * @var array
201 */
202 protected $jsFilesNoConcatenation = array();
203
204 /**
205 * Indicates if a <div>-output section is open
206 *
207 * @var int
208 * @internal
209 */
210 public $sectionFlag = 0;
211
212 /**
213 * (Default) Class for wrapping <DIV>-tag of page. Is set in class extensions.
214 *
215 * @var string
216 */
217 public $divClass = '';
218
219 /**
220 * @var string
221 */
222 public $pageHeaderBlock = '';
223
224 /**
225 * @var string
226 */
227 public $endOfPageJsBlock = '';
228
229 /**
230 * @var bool
231 */
232 public $hasDocheader = true;
233
234 /**
235 * @var PageRenderer
236 */
237 protected $pageRenderer = null;
238
239 /**
240 * Alternative template file
241 *
242 * @var string
243 */
244 protected $pageHeaderFooterTemplateFile = '';
245
246 /**
247 * @var bool
248 */
249 protected $extDirectStateProvider = false;
250
251 /**
252 * Whether flashmessages should be rendered or not
253 *
254 * @var bool $showFlashMessages
255 */
256 public $showFlashMessages = true;
257
258 /**
259 * @var IconFactory
260 */
261 protected $iconFactory;
262
263 /**
264 * @var MarkerBasedTemplateService
265 */
266 protected $templateService;
267
268 const STATUS_ICON_ERROR = 3;
269 const STATUS_ICON_WARNING = 2;
270 const STATUS_ICON_NOTIFICATION = 1;
271 const STATUS_ICON_OK = -1;
272
273 /**
274 * Constructor
275 */
276 public function __construct()
277 {
278 // Initializes the page rendering object:
279 $this->initPageRenderer();
280
281 // load Legacy CSS Support
282 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyCssClasses');
283 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
284
285 // initialize Marker Support
286 $this->templateService = GeneralUtility::makeInstance(MarkerBasedTemplateService::class);
287
288 // Setting default scriptID:
289 if (($temp_M = (string)GeneralUtility::_GET('M')) && $GLOBALS['TBE_MODULES']['_PATHS'][$temp_M]) {
290 $this->scriptID = preg_replace('/^.*\\/(sysext|ext)\\//', 'ext/', $GLOBALS['TBE_MODULES']['_PATHS'][$temp_M] . 'index.php');
291 } else {
292 $this->scriptID = preg_replace('/^.*\\/(sysext|ext)\\//', 'ext/', \TYPO3\CMS\Core\Utility\PathUtility::stripPathSitePrefix(PATH_thisScript));
293 }
294 if (TYPO3_mainDir != 'typo3/' && substr($this->scriptID, 0, strlen(TYPO3_mainDir)) == TYPO3_mainDir) {
295 // This fixes if TYPO3_mainDir has been changed so the script ids are STILL "typo3/..."
296 $this->scriptID = 'typo3/' . substr($this->scriptID, strlen(TYPO3_mainDir));
297 }
298 $this->bodyTagId = preg_replace('/[^A-Za-z0-9-]/', '-', $this->scriptID);
299 // Individual configuration per script? If so, make a recursive merge of the arrays:
300 if (is_array($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID])) {
301 // Make copy
302 $ovr = $GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID];
303 // merge styles.
304 \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($GLOBALS['TBE_STYLES'], $ovr);
305 // Have to unset - otherwise the second instantiation will do it again!
306 unset($GLOBALS['TBE_STYLES']['scriptIDindex'][$this->scriptID]);
307 }
308 // Main Stylesheets:
309 if ($GLOBALS['TBE_STYLES']['stylesheet']) {
310 $this->styleSheetFile = $GLOBALS['TBE_STYLES']['stylesheet'];
311 }
312 if ($GLOBALS['TBE_STYLES']['stylesheet2']) {
313 $this->styleSheetFile2 = $GLOBALS['TBE_STYLES']['stylesheet2'];
314 }
315 if ($GLOBALS['TBE_STYLES']['styleSheetFile_post']) {
316 $this->styleSheetFile_post = $GLOBALS['TBE_STYLES']['styleSheetFile_post'];
317 }
318 if ($GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle']) {
319 $this->inDocStylesArray['TBEstyle'] = $GLOBALS['TBE_STYLES']['inDocStyles_TBEstyle'];
320 }
321 // include all stylesheets
322 foreach ($this->getSkinStylesheetDirectories() as $stylesheetDirectory) {
323 $this->addStylesheetDirectory($stylesheetDirectory);
324 }
325 // Background image
326 if ($GLOBALS['TBE_STYLES']['background']) {
327 GeneralUtility::deprecationLog('Usage of $TBE_STYLES["background"] is deprecated. Please use stylesheets directly.');
328 }
329 }
330
331 /**
332 * Initializes the page renderer object
333 */
334 protected function initPageRenderer()
335 {
336 if ($this->pageRenderer !== null) {
337 return;
338 }
339 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
340 $this->pageRenderer->setLanguage($GLOBALS['LANG']->lang);
341 $this->pageRenderer->enableConcatenateFiles();
342 $this->pageRenderer->enableCompressCss();
343 $this->pageRenderer->enableCompressJavascript();
344 // Add all JavaScript files defined in $this->jsFiles to the PageRenderer
345 foreach ($this->jsFilesNoConcatenation as $file) {
346 $this->pageRenderer->addJsFile(
347 $GLOBALS['BACK_PATH'] . $file,
348 'text/javascript',
349 true,
350 false,
351 '',
352 true
353 );
354 }
355 // Add all JavaScript files defined in $this->jsFiles to the PageRenderer
356 foreach ($this->jsFiles as $file) {
357 $this->pageRenderer->addJsFile($GLOBALS['BACK_PATH'] . $file);
358 }
359 if ((int)$GLOBALS['TYPO3_CONF_VARS']['BE']['debug'] === 1) {
360 $this->pageRenderer->enableDebugMode();
361 }
362 }
363
364 /**
365 * Sets inclusion of StateProvider
366 *
367 * @return void
368 */
369 public function setExtDirectStateProvider()
370 {
371 $this->extDirectStateProvider = true;
372 }
373
374 /*****************************************
375 *
376 * EVALUATION FUNCTIONS
377 * Various centralized processing
378 *
379 *****************************************/
380
381 /**
382 * Makes link to page $id in frontend (view page)
383 * Returns an icon which links to the frontend index.php document for viewing the page with id $id
384 * $id must be a page-uid
385 * If the BE_USER has access to Web>List then a link to that module is shown as well (with return-url)
386 *
387 * @param int $id The page id
388 * @return string HTML string with linked icon(s)
389 */
390 public function viewPageIcon($id)
391 {
392 // If access to Web>List for user, then link to that module.
393 $str = BackendUtility::getListViewLink(array(
394 'id' => $id,
395 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
396 ), $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.showList'));
397 // Make link to view page
398 $str .= '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick($id, '', BackendUtility::BEgetRootLine($id))) . '" title="' . $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.showPage', true) . '">' . $this->iconFactory->getIcon('actions-document-view', Icon::SIZE_SMALL)->render() . '</a>';
399 return $str;
400 }
401
402 /**
403 * Makes the header (icon+title) for a page (or other record). Used in most modules under Web>*
404 * $table and $row must be a tablename/record from that table
405 * $path will be shown as alt-text for the icon.
406 * The title will be truncated to 45 chars.
407 *
408 * @param string $table Table name
409 * @param array $row Record row
410 * @param string $path Alt text
411 * @param bool $noViewPageIcon Set $noViewPageIcon TRUE if you don't want a magnifier-icon for viewing the page in the frontend
412 * @param array $tWrap is an array with indexes 0 and 1 each representing HTML-tags (start/end) which will wrap the title
413 * @param bool $enableClickMenu If TRUE, render click menu code around icon image
414 * @return string HTML content
415 */
416 public function getHeader($table, $row, $path, $noViewPageIcon = false, $tWrap = array('', ''), $enableClickMenu = true)
417 {
418 $viewPage = '';
419 if (is_array($row) && $row['uid']) {
420 $iconImgTag = '<span title="' . htmlspecialchars($path) . '">' . $this->iconFactory->getIconForRecord($table, $row, Icon::SIZE_SMALL)->render() . '</span>';
421 $title = strip_tags(BackendUtility::getRecordTitle($table, $row));
422 $viewPage = $noViewPageIcon ? '' : $this->viewPageIcon($row['uid']);
423 } else {
424 $iconImgTag = '<span title="' . htmlspecialchars($path) . '">' . $this->iconFactory->getIcon('apps-pagetree-page-domain', Icon::SIZE_SMALL)->render() . '</span>';
425 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
426 }
427
428 if ($enableClickMenu) {
429 $iconImgTag = BackendUtility::wrapClickMenuOnIcon($iconImgTag, $table, $row['uid']);
430 }
431
432 return '<span class="typo3-moduleHeader">' . $iconImgTag . $viewPage . $tWrap[0] . htmlspecialchars(GeneralUtility::fixed_lgd_cs($title, 45)) . $tWrap[1] . '</span>';
433 }
434
435 /**
436 * Like ->getHeader() but for files and folders
437 * Returns the icon with the path of the file/folder set in the alt/title attribute. Shows the name after the icon.
438 *
439 * @param \TYPO3\CMS\Core\Resource\ResourceInterface $resource
440 * @param array $tWrap is an array with indexes 0 and 1 each representing HTML-tags (start/end) which will wrap the title
441 * @param bool $enableClickMenu If TRUE, render click menu code around icon image
442 * @return string
443 */
444 public function getResourceHeader(\TYPO3\CMS\Core\Resource\ResourceInterface $resource, $tWrap = array('', ''), $enableClickMenu = true)
445 {
446 try {
447 $path = $resource->getStorage()->getName() . $resource->getParentFolder()->getIdentifier();
448 $iconImgTag = '<span title="' . htmlspecialchars($path) . '">' . $this->iconFactory->getIconForResource($resource, Icon::SIZE_SMALL)->render() . '</span>';
449 } catch (\TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException $e) {
450 $iconImgTag = '';
451 }
452
453 if ($enableClickMenu && ($resource instanceof \TYPO3\CMS\Core\Resource\File)) {
454 $metaData = $resource->_getMetaData();
455 $iconImgTag = BackendUtility::wrapClickMenuOnIcon($iconImgTag, 'sys_file_metadata', $metaData['uid']);
456 }
457
458 return '<span class="typo3-moduleHeader">' . $iconImgTag . $tWrap[0] . htmlspecialchars(GeneralUtility::fixed_lgd_cs($resource->getName(), 45)) . $tWrap[1] . '</span>';
459 }
460
461 /**
462 * Returns a linked shortcut-icon which will call the shortcut frame and set a shortcut there back to the calling page/module
463 *
464 * @param string $gvList Is the list of GET variables to store (if any)
465 * @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
466 * @param string $modName Module name string
467 * @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.
468 * @param string $classes
469 * @return string HTML content
470 */
471 public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '', $classes = '')
472 {
473 $gvList = 'route,' . $gvList;
474 $storeUrl = $this->makeShortcutUrl($gvList, $setList);
475 $pathInfo = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
476 // Fallback for alt_mod. We still pass in the old xMOD... stuff, but TBE_MODULES only knows about "record_edit".
477 // We still need to pass the xMOD name to createShortcut below, since this is used for icons.
478 $moduleName = $modName === 'xMOD_alt_doc.php' ? 'record_edit' : $modName;
479 // Add the module identifier automatically if typo3/index.php is used:
480 if (GeneralUtility::_GET('M') !== null && isset($GLOBALS['TBE_MODULES']['_PATHS'][$moduleName])) {
481 $storeUrl = '&M=' . $moduleName . $storeUrl;
482 }
483 if ((int)$motherModName === 1) {
484 $motherModule = 'top.currentModuleLoaded';
485 } elseif ($motherModName) {
486 $motherModule = GeneralUtility::quoteJSvalue($motherModName);
487 } else {
488 $motherModule = '\'\'';
489 }
490 $confirmationText = GeneralUtility::quoteJSvalue($GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.makeBookmark'));
491
492 $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
493 $shortcutExist = BackendUtility::shortcutExists($shortcutUrl);
494
495 if ($shortcutExist) {
496 return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
497 $this->iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . '</a>';
498 }
499 $url = GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl));
500 $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility::quoteJSvalue(rawurlencode($modName)) .
501 ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this);return false;';
502
503 return '<a href="#" class="' . htmlspecialchars($classes) . '" onclick="' . htmlspecialchars($onClick) . '" title="' .
504 $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.makeBookmark', true) . '">' .
505 $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>';
506 }
507
508 /**
509 * MAKE url for storing
510 * Internal func
511 *
512 * @param string $gvList Is the list of GET variables to store (if any)
513 * @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
514 * @return string
515 * @access private
516 * @see makeShortcutIcon()
517 */
518 public function makeShortcutUrl($gvList, $setList)
519 {
520 $GET = GeneralUtility::_GET();
521 $storeArray = array_merge(GeneralUtility::compileSelectedGetVarsFromArray($gvList, $GET), array('SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)));
522 $storeUrl = GeneralUtility::implodeArrayForUrl('', $storeArray);
523 return $storeUrl;
524 }
525
526 /**
527 * Returns <input> attributes to set the width of an text-type input field.
528 * For client browsers with no CSS support the cols/size attribute is returned.
529 * For CSS compliant browsers (recommended) a ' style="width: ...px;"' is returned.
530 *
531 * @param int $size A relative number which multiplied with approx. 10 will lead to the width in pixels
532 * @param bool $textarea A flag you can set for textareas - DEPRECATED as there is no difference any more between the two
533 * @param string $styleOverride A string which will be returned as attribute-value for style="" instead of the calculated width (if CSS is enabled)
534 * @return string Tag attributes for an <input> tag (regarding width)
535 */
536 public function formWidth($size = 48, $textarea = false, $styleOverride = '')
537 {
538 return ' style="' . ($styleOverride ?: 'width:' . ceil($size * 9.58) . 'px;') . '"';
539 }
540
541 /**
542 * Returns JavaScript variables setting the returnUrl and thisScript location for use by JavaScript on the page.
543 * Used in fx. db_list.php (Web>List)
544 *
545 * @param string $thisLocation URL to "this location" / current script
546 * @return string Urls are returned as JavaScript variables T3_RETURN_URL and T3_THIS_LOCATION
547 * @see typo3/db_list.php
548 */
549 public function redirectUrls($thisLocation = '')
550 {
551 $thisLocation = $thisLocation ? $thisLocation : GeneralUtility::linkThisScript(array(
552 'CB' => '',
553 'SET' => '',
554 'cmd' => '',
555 'popViewId' => ''
556 ));
557 $out = '
558 var T3_RETURN_URL = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode(GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))))) . ';
559 var T3_THIS_LOCATION = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode($thisLocation))) . '
560 ';
561 return $out;
562 }
563
564 /**
565 * Defines whether to use the X-UA-Compatible meta tag.
566 *
567 * @param bool $useCompatibilityTag Whether to use the tag
568 * @return void
569 */
570 public function useCompatibilityTag($useCompatibilityTag = true)
571 {
572 $this->useCompatibilityTag = (bool)$useCompatibilityTag;
573 }
574
575 /*****************************************
576 *
577 * PAGE BUILDING FUNCTIONS.
578 * Use this to build the HTML of your backend modules
579 *
580 *****************************************/
581 /**
582 * Returns page start
583 * This includes the proper header with charset, title, meta tag and beginning body-tag.
584 *
585 * @param string $title HTML Page title for the header
586 * @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)
587 * @see endPage()
588 */
589 public function startPage($title)
590 {
591 // hook pre start page
592 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'])) {
593 $preStartPageHook = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preStartPageHook'];
594 if (is_array($preStartPageHook)) {
595 $hookParameters = array(
596 'title' => &$title
597 );
598 foreach ($preStartPageHook as $hookFunction) {
599 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
600 }
601 }
602 }
603 // alternative template for Header and Footer
604 if ($this->pageHeaderFooterTemplateFile) {
605 $file = GeneralUtility::getFileAbsFileName($this->pageHeaderFooterTemplateFile, true);
606 if ($file) {
607 $this->pageRenderer->setTemplateFile($file);
608 }
609 }
610
611 // Disable rendering of XHTML tags
612 $this->pageRenderer->setRenderXhtml(false);
613
614 $languageCode = $this->pageRenderer->getLanguage() === 'default' ? 'en' : $this->pageRenderer->getLanguage();
615 $this->pageRenderer->setHtmlTag('<html lang="' . $languageCode . '">');
616
617 $headerStart = '<!DOCTYPE html>';
618 $this->pageRenderer->setXmlPrologAndDocType($headerStart);
619 $this->pageRenderer->setHeadTag('<head>' . LF . '<!-- TYPO3 Script ID: ' . htmlspecialchars($this->scriptID) . ' -->');
620 header('Content-Type:text/html;charset=utf-8');
621 $this->pageRenderer->setCharSet('utf-8');
622 $this->pageRenderer->addMetaTag($this->generator());
623 $this->pageRenderer->addMetaTag('<meta name="robots" content="noindex,follow">');
624 $this->pageRenderer->addMetaTag('<meta charset="utf-8">');
625 $this->pageRenderer->addMetaTag('<meta name="viewport" content="width=device-width, initial-scale=1">');
626 $this->pageRenderer->setFavIcon($this->getBackendFavicon());
627 if ($this->useCompatibilityTag) {
628 $this->pageRenderer->addMetaTag($this->xUaCompatible($this->xUaCompatibilityVersion));
629 }
630 $this->pageRenderer->setTitle($title);
631 // add docstyles
632 $this->docStyle();
633 if ($this->extDirectStateProvider) {
634 $this->pageRenderer->addJsFile('sysext/backend/Resources/Public/JavaScript/ExtDirect.StateProvider.js');
635 }
636 $this->pageRenderer->addHeaderData($this->JScode);
637 foreach ($this->JScodeArray as $name => $code) {
638 $this->pageRenderer->addJsInlineCode($name, $code, false);
639 }
640 if (!empty($this->JScodeLibArray)) {
641 GeneralUtility::deprecationLog('DocumentTemplate->JScodeLibArray is deprecated since TYPO3 CMS 7. Use the functionality within pageRenderer directly');
642 foreach ($this->JScodeLibArray as $library) {
643 $this->pageRenderer->addHeaderData($library);
644 }
645 }
646 if ($this->extJScode) {
647 $this->pageRenderer->addExtOnReadyCode($this->extJScode);
648 }
649
650 // Load jquery and twbs JS libraries on every backend request
651 $this->pageRenderer->loadJquery();
652 // Note: please do not reference "bootstrap" outside of the TYPO3 Core (not in your own extensions)
653 // as this is preliminary as long as Twitter bootstrap does not support AMD modules
654 // this logic will be changed once Twitter bootstrap 4 is included
655 $this->pageRenderer->addJsFile('sysext/core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap.js');
656
657 // hook for additional headerData
658 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preHeaderRenderHook'])) {
659 $preHeaderRenderHook = &$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['preHeaderRenderHook'];
660 if (is_array($preHeaderRenderHook)) {
661 $hookParameters = array(
662 'pageRenderer' => &$this->pageRenderer
663 );
664 foreach ($preHeaderRenderHook as $hookFunction) {
665 GeneralUtility::callUserFunction($hookFunction, $hookParameters, $this);
666 }
667 }
668 }
669 // Construct page header.
670 $str = $this->pageRenderer->render(PageRenderer::PART_HEADER);
671 $this->JScode = ($this->extJScode = '');
672 $this->JScodeArray = array();
673 $this->endOfPageJsBlock = $this->pageRenderer->render(PageRenderer::PART_FOOTER);
674 $str .= $this->docBodyTagBegin() . ($this->divClass ? '
675
676 <!-- Wrapping DIV-section for whole page BEGIN -->
677 <div class="' . $this->divClass . '">
678 ' : '') . trim($this->form);
679 return $str;
680 }
681
682 /**
683 * Returns page end; This includes finishing form, div, body and html tags.
684 *
685 * @return string The HTML end of a page
686 * @see startPage()
687 */
688 public function endPage()
689 {
690 $str = $this->sectionEnd() . $this->postCode . $this->wrapScriptTags(BackendUtility::getUpdateSignalCode()) . ($this->form ? '
691 </form>' : '');
692 // If something is in buffer like debug, put it to end of page
693 if (ob_get_contents()) {
694 $str .= ob_get_clean();
695 if (!headers_sent()) {
696 header('Content-Encoding: None');
697 }
698 }
699 $str .= ($this->divClass ? '
700
701 <!-- Wrapping DIV-section for whole page END -->
702 </div>' : '') . $this->endOfPageJsBlock;
703
704 // Logging: Can't find better place to put it:
705 if (TYPO3_DLOG) {
706 GeneralUtility::devLog('END of BACKEND session', \TYPO3\CMS\Backend\Template\DocumentTemplate::class, 0, array('_FLUSH' => true));
707 }
708 return $str;
709 }
710
711 /**
712 * Shortcut for render the complete page of a module
713 *
714 * @param string $title page title
715 * @param string $content page content
716 * @return string complete page
717 */
718 public function render($title, $content)
719 {
720 $pageContent = $this->startPage($title);
721 $pageContent .= $content;
722 $pageContent .= $this->endPage();
723 return $this->insertStylesAndJS($pageContent);
724 }
725
726 /**
727 * Returns the header-bar in the top of most backend modules
728 * Closes section if open.
729 *
730 * @param string $text The text string for the header
731 * @return string HTML content
732 */
733 public function header($text)
734 {
735 $str = '
736
737 <!-- MAIN Header in page top -->
738 <h1 class="t3js-title-inlineedit">' . htmlspecialchars($text) . '</h1>
739 ';
740 return $this->sectionEnd() . $str;
741 }
742
743 /**
744 * Begins an output section and sets header and content
745 *
746 * @param string $label The header
747 * @param string $text The HTML-content
748 * @param bool $nostrtoupper A flag that will prevent the header from being converted to uppercase
749 * @param bool $sH Defines the type of header (if set, "<h3>" rather than the default "h4")
750 * @param int $type The number of an icon to show with the header (see the icon-function). -1,1,2,3
751 * @param bool $allowHTMLinHeader If set, HTML tags are allowed in $label (otherwise this value is by default htmlspecialchars()'ed)
752 * @return string HTML content
753 * @see icons(), sectionHeader()
754 */
755 public function section($label, $text, $nostrtoupper = false, $sH = false, $type = 0, $allowHTMLinHeader = false)
756 {
757 $str = '';
758 // Setting header
759 if ($label) {
760 if (!$allowHTMLinHeader) {
761 $label = htmlspecialchars($label);
762 }
763 $str .= $this->sectionHeader($this->icons($type) . $label, $sH, $nostrtoupper ? '' : ' class="uppercase"');
764 }
765 // Setting content
766 $str .= '
767
768 <!-- Section content -->
769 ' . $text;
770 return $this->sectionBegin() . $str;
771 }
772
773 /**
774 * Inserts a divider image
775 * Ends a section (if open) before inserting the image
776 *
777 * @param int $dist The margin-top/-bottom of the <hr> ruler.
778 * @return string HTML content
779 */
780 public function divider($dist)
781 {
782 $dist = (int)$dist;
783 $str = '
784
785 <!-- DIVIDER -->
786 <hr style="margin-top: ' . $dist . 'px; margin-bottom: ' . $dist . 'px;" />
787 ';
788 return $this->sectionEnd() . $str;
789 }
790
791 /**
792 * Make a section header.
793 * Begins a section if not already open.
794 *
795 * @param string $label The label between the <h3> or <h4> tags. (Allows HTML)
796 * @param bool $sH If set, <h3> is used, otherwise <h4>
797 * @param string $addAttrib Additional attributes to h-tag, eg. ' class=""'
798 * @return string HTML content
799 */
800 public function sectionHeader($label, $sH = false, $addAttrib = '')
801 {
802 $tag = $sH ? 'h2' : 'h3';
803 if ($addAttrib && $addAttrib[0] !== ' ') {
804 $addAttrib = ' ' . $addAttrib;
805 }
806 $str = '
807
808 <!-- Section header -->
809 <' . $tag . $addAttrib . '>' . $label . '</' . $tag . '>
810 ';
811 return $this->sectionBegin() . $str;
812 }
813
814 /**
815 * Begins an output section.
816 * Returns the <div>-begin tag AND sets the ->sectionFlag TRUE (if the ->sectionFlag is not already set!)
817 * You can call this function even if a section is already begun since the function will only return something if the sectionFlag is not already set!
818 *
819 * @return string HTML content
820 */
821 public function sectionBegin()
822 {
823 if (!$this->sectionFlag) {
824 $this->sectionFlag = 1;
825 $str = '
826
827 <!-- ***********************
828 Begin output section.
829 *********************** -->
830 <div>
831 ';
832 return $str;
833 } else {
834 return '';
835 }
836 }
837
838 /**
839 * Ends and output section
840 * Returns the </div>-end tag AND clears the ->sectionFlag (but does so only IF the sectionFlag is set - that is a section is 'open')
841 * See sectionBegin() also.
842 *
843 * @return string HTML content
844 */
845 public function sectionEnd()
846 {
847 if ($this->sectionFlag) {
848 $this->sectionFlag = 0;
849 return '
850 </div>
851 <!-- *********************
852 End output section.
853 ********************* -->
854 ';
855 } else {
856 return '';
857 }
858 }
859
860 /**
861 * Creates the bodyTag.
862 * You can add to the bodyTag by $this->bodyTagAdditions
863 *
864 * @return string HTML body tag
865 */
866 public function docBodyTagBegin()
867 {
868 return '<body ' . trim($this->bodyTagAdditions . ($this->bodyTagId ? ' id="' . $this->bodyTagId . '"' : '')) . '>';
869 }
870
871 /**
872 * Outputting document style
873 *
874 * @return string HTML style section/link tags
875 */
876 public function docStyle()
877 {
878 // Implode it all:
879 $inDocStyles = implode(LF, $this->inDocStylesArray);
880
881 // Reset styles so they won't be added again in insertStylesAndJS()
882 $this->inDocStylesArray = array();
883
884 if ($this->styleSheetFile) {
885 $this->pageRenderer->addCssFile($this->styleSheetFile);
886 }
887 if ($this->styleSheetFile2) {
888 $this->pageRenderer->addCssFile($this->styleSheetFile2);
889 }
890
891 if ($inDocStyles !== '') {
892 $this->pageRenderer->addCssInlineBlock('inDocStyles', $inDocStyles . LF . '/*###POSTCSSMARKER###*/');
893 }
894
895 if ($this->styleSheetFile_post) {
896 $this->pageRenderer->addCssFile($this->styleSheetFile_post);
897 }
898 }
899
900 /**
901 * Insert additional style sheet link
902 *
903 * @param string $key some key identifying the style sheet
904 * @param string $href uri to the style sheet file
905 * @param string $title value for the title attribute of the link element
906 * @param string $relation value for the rel attribute of the link element
907 * @return void
908 */
909 public function addStyleSheet($key, $href, $title = '', $relation = 'stylesheet')
910 {
911 $this->pageRenderer->addCssFile($href, $relation, 'screen', $title);
912 }
913
914 /**
915 * Add all *.css files of the directory $path to the stylesheets
916 *
917 * @param string $path directory to add
918 * @return void
919 */
920 public function addStyleSheetDirectory($path)
921 {
922 // Calculation needed, when TYPO3 source is used via a symlink
923 // absolute path to the stylesheets
924 $filePath = dirname(GeneralUtility::getIndpEnv('SCRIPT_FILENAME')) . '/' . $GLOBALS['BACK_PATH'] . $path;
925 // Clean the path
926 $resolvedPath = GeneralUtility::resolveBackPath($filePath);
927 // Read all files in directory and sort them alphabetically
928 $files = GeneralUtility::getFilesInDir($resolvedPath, 'css', false, 1);
929 foreach ($files as $file) {
930 $this->pageRenderer->addCssFile($GLOBALS['BACK_PATH'] . $path . $file, 'stylesheet', 'all');
931 }
932 }
933
934 /**
935 * Insert post rendering document style into already rendered content
936 * This is needed for extobjbase
937 *
938 * @param string $content style-content to insert.
939 * @return string content with inserted styles
940 */
941 public function insertStylesAndJS($content)
942 {
943 $styles = LF . implode(LF, $this->inDocStylesArray);
944 $content = str_replace('/*###POSTCSSMARKER###*/', $styles, $content);
945
946 // Insert accumulated JS
947 $jscode = $this->JScode . LF . $this->wrapScriptTags(implode(LF, $this->JScodeArray));
948 $content = str_replace('<!--###POSTJSMARKER###-->', $jscode, $content);
949 return $content;
950 }
951
952 /**
953 * Returns an array of all stylesheet directories belonging to core and skins
954 *
955 * @return array Stylesheet directories
956 */
957 public function getSkinStylesheetDirectories()
958 {
959 $stylesheetDirectories = array();
960 // Add default core stylesheets
961 foreach ($this->stylesheetsCore as $stylesheetDir) {
962 $stylesheetDirectories[] = $stylesheetDir;
963 }
964 // Stylesheets from skins
965 // merge default css directories ($this->stylesheetsSkin) with additional ones and include them
966 if (is_array($GLOBALS['TBE_STYLES']['skins'])) {
967 // loop over all registered skins
968 foreach ($GLOBALS['TBE_STYLES']['skins'] as $skinExtKey => $skin) {
969 $skinStylesheetDirs = $this->stylesheetsSkins;
970 // Skins can add custom stylesheetDirectories using
971 // $GLOBALS['TBE_STYLES']['skins'][$_EXTKEY]['stylesheetDirectories']
972 if (is_array($skin['stylesheetDirectories'])) {
973 $skinStylesheetDirs = array_merge($skinStylesheetDirs, $skin['stylesheetDirectories']);
974 }
975 // Add all registered directories
976 foreach ($skinStylesheetDirs as $stylesheetDir) {
977 // for EXT:myskin/stylesheets/ syntax
978 if (substr($stylesheetDir, 0, 4) === 'EXT:') {
979 list($extKey, $path) = explode('/', substr($stylesheetDir, 4), 2);
980 if (!empty($extKey) && ExtensionManagementUtility::isLoaded($extKey) && !empty($path)) {
981 $stylesheetDirectories[] = ExtensionManagementUtility::extRelPath($extKey) . $path;
982 }
983 } else {
984 // For relative paths
985 $stylesheetDirectories[] = ExtensionManagementUtility::extRelPath($skinExtKey) . $stylesheetDir;
986 }
987 }
988 }
989 }
990 return $stylesheetDirectories;
991 }
992
993 /**
994 * Returns generator meta tag
995 *
996 * @return string <meta> tag with name "generator
997 */
998 public function generator()
999 {
1000 $str = 'TYPO3 CMS, ' . TYPO3_URL_GENERAL . ', &#169; Kasper Sk&#229;rh&#248;j ' . TYPO3_copyright_year . ', extensions are copyright of their respective owners.';
1001 return '<meta name="generator" content="' . $str . '" />';
1002 }
1003
1004 /**
1005 * Returns X-UA-Compatible meta tag
1006 *
1007 * @param string $content Content of the compatible tag (default: IE-8)
1008 * @return string <meta http-equiv="X-UA-Compatible" content="???" />
1009 */
1010 public function xUaCompatible($content = 'IE=8')
1011 {
1012 return '<meta http-equiv="X-UA-Compatible" content="' . $content . '" />';
1013 }
1014
1015 /*****************************************
1016 *
1017 * OTHER ELEMENTS
1018 * Tables, buttons, formatting dimmed/red strings
1019 *
1020 ******************************************/
1021 /**
1022 * Returns an image-tag with an 18x16 icon of the following types:
1023 *
1024 * $type:
1025 * -1: OK icon (Check-mark)
1026 * 1: Notice (Speach-bubble)
1027 * 2: Warning (Yellow triangle)
1028 * 3: Fatal error (Red stop sign)
1029 *
1030 * @param int $type See description
1031 * @param string $styleAttribValue Value for style attribute
1032 * @return string HTML image tag (if applicable)
1033 */
1034 public function icons($type, $styleAttribValue = '')
1035 {
1036 switch ($type) {
1037 case self::STATUS_ICON_ERROR:
1038 $icon = 'status-dialog-error';
1039 break;
1040 case self::STATUS_ICON_WARNING:
1041 $icon = 'status-dialog-warning';
1042 break;
1043 case self::STATUS_ICON_NOTIFICATION:
1044 $icon = 'status-dialog-notification';
1045 break;
1046 case self::STATUS_ICON_OK:
1047 $icon = 'status-dialog-ok';
1048 break;
1049 default:
1050 // Do nothing
1051 }
1052 if ($icon) {
1053 return $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render();
1054 }
1055 }
1056
1057 /**
1058 * Returns an <input> button with the $onClick action and $label
1059 *
1060 * @param string $onClick The value of the onclick attribute of the input tag (submit type)
1061 * @param string $label The label for the button (which will be htmlspecialchar'ed)
1062 * @return string A <input> tag of the type "submit
1063 */
1064 public function t3Button($onClick, $label)
1065 {
1066 $button = '<input class="btn btn-default" type="submit" onclick="' . htmlspecialchars($onClick) . '; return false;" value="' . htmlspecialchars($label) . '" />';
1067 return $button;
1068 }
1069
1070 /**
1071 * Returns string wrapped in CDATA "tags" for XML / XHTML (wrap content of <script> and <style> sections in those!)
1072 *
1073 * @param string $string Input string
1074 * @return string Output string
1075 */
1076 public function wrapInCData($string)
1077 {
1078 $string = '/*<![CDATA[*/' . $string . '/*]]>*/';
1079 return $string;
1080 }
1081
1082 /**
1083 * Wraps the input string in script tags.
1084 * Automatic re-identing of the JS code is done by using the first line as ident reference.
1085 * This is nice for identing JS code with PHP code on the same level.
1086 *
1087 * @param string $string Input string
1088 * @param bool $linebreak Wrap script element in linebreaks? Default is TRUE.
1089 * @return string Output string
1090 */
1091 public function wrapScriptTags($string, $linebreak = true)
1092 {
1093 if (trim($string)) {
1094 // <script wrapped in nl?
1095 $cr = $linebreak ? LF : '';
1096 // Remove nl from the beginning
1097 $string = ltrim($string, LF);
1098 // Re-ident to one tab using the first line as reference
1099 if ($string[0] === TAB) {
1100 $string = TAB . ltrim($string, TAB);
1101 }
1102 $string = $cr . '<script type="text/javascript">
1103 /*<![CDATA[*/
1104 ' . $string . '
1105 /*]]>*/
1106 </script>' . $cr;
1107 }
1108 return trim($string);
1109 }
1110
1111 // These vars defines the layout for the table produced by the table() function.
1112 // You can override these values from outside if you like.
1113 public $tableLayout = array(
1114 'defRow' => array(
1115 'defCol' => array('<td valign="top">', '</td>')
1116 )
1117 );
1118
1119 public $table_TR = '<tr>';
1120
1121 public $table_TABLE = '<table border="0" cellspacing="0" cellpadding="0" class="typo3-dblist" id="typo3-tmpltable">';
1122
1123 /**
1124 * Returns a one-row/two-celled table with $content and $menu side by side.
1125 * The table is a 100% width table and each cell is aligned left / right
1126 *
1127 * @param string $content Content cell content (left)
1128 * @param string $menu Menu cell content (right)
1129 * @return string HTML output
1130 */
1131 public function funcMenu($content, $menu)
1132 {
1133 return '
1134 <table border="0" cellpadding="0" cellspacing="0" width="100%" id="typo3-funcmenu">
1135 <tr>
1136 <td valign="top" nowrap="nowrap">' . $content . '</td>
1137 <td valign="top" align="right" nowrap="nowrap">' . $menu . '</td>
1138 </tr>
1139 </table>';
1140 }
1141
1142 /**
1143 * Includes a javascript library that exists in the core /typo3/ directory
1144 *
1145 * @param string $lib: Library name. Call it with the full path like "sysext/core/Resources/Public/JavaScript/QueryGenerator.js" to load it
1146 * @return void
1147 */
1148 public function loadJavascriptLib($lib)
1149 {
1150 // @todo: maybe we can remove this one as well
1151 $this->pageRenderer->addJsFile($lib);
1152 }
1153
1154 /**
1155 * Includes the necessary Javascript function for the clickmenu (context sensitive menus) in the document
1156 *
1157 * @return void
1158 */
1159 public function getContextMenuCode()
1160 {
1161 $this->pageRenderer->loadJquery();
1162 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ClickMenu');
1163 }
1164
1165 /**
1166 * Includes the necessary javascript file for use on pages which have the
1167 * drag and drop functionality (legacy folder tree)
1168 *
1169 * @param string $table indicator of which table the drag and drop function should work on (pages or folders)
1170 * @param string $additionalJavaScriptCode adds more code to the additional javascript code
1171 * @return void
1172 */
1173 public function getDragDropCode($table, $additionalJavaScriptCode = '')
1174 {
1175 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/LegacyTree', 'function() {
1176 DragDrop.table = "' . $table . '";
1177 ' . $additionalJavaScriptCode . '
1178 }');
1179 }
1180
1181 /**
1182 * Creates a tab menu from an array definition
1183 *
1184 * Returns a tab menu for a module
1185 * Requires the JS function jumpToUrl() to be available
1186 *
1187 * @param mixed $mainParams is the "&id=" parameter value to be sent to the module, but it can be also a parameter array which will be passed instead of the &id=...
1188 * @param string $elementName it the form elements name, probably something like "SET[...]
1189 * @param string $currentValue is the value to be selected currently.
1190 * @param array $menuItems is an array with the menu items for the selector box
1191 * @param string $script is the script to send the &id to, if empty it's automatically found
1192 * @param string $addparams is additional parameters to pass to the script.
1193 * @return string HTML code for tab menu
1194 */
1195 public function getTabMenu($mainParams, $elementName, $currentValue, $menuItems, $script = '', $addparams = '')
1196 {
1197 $content = '';
1198 if (is_array($menuItems)) {
1199 if (!is_array($mainParams)) {
1200 $mainParams = array('id' => $mainParams);
1201 }
1202 $mainParams = GeneralUtility::implodeArrayForUrl('', $mainParams);
1203 if (!$script) {
1204 $script = basename(PATH_thisScript);
1205 }
1206 $menuDef = array();
1207 foreach ($menuItems as $value => $label) {
1208 $menuDef[$value]['isActive'] = (string)$currentValue === (string)$value;
1209 $menuDef[$value]['label'] = htmlspecialchars($label, ENT_COMPAT, 'UTF-8', false);
1210 $menuDef[$value]['url'] = $script . '?' . $mainParams . $addparams . '&' . $elementName . '=' . $value;
1211 }
1212 $content = $this->getTabMenuRaw($menuDef);
1213 }
1214 return $content;
1215 }
1216
1217 /**
1218 * Creates the HTML content for the tab menu
1219 *
1220 * @param array $menuItems Menu items for tabs
1221 * @return string Table HTML
1222 * @access private
1223 */
1224 public function getTabMenuRaw($menuItems)
1225 {
1226 if (!is_array($menuItems)) {
1227 return '';
1228 }
1229
1230 $options = '';
1231 foreach ($menuItems as $id => $def) {
1232 $class = $def['isActive'] ? 'active' : '';
1233 $label = $def['label'];
1234 $url = htmlspecialchars($def['url']);
1235 $params = $def['addParams'];
1236
1237 $options .= '<li class="' . $class . '">' .
1238 '<a href="' . $url . '" ' . $params . '>' . $label . '</a>' .
1239 '</li>';
1240 }
1241
1242 return '<ul class="nav nav-tabs" role="tablist">' .
1243 $options .
1244 '</ul>';
1245 }
1246
1247 /**
1248 * Creates the version selector for the page id inputted.
1249 * Requires the core version management extension, "version" to be loaded.
1250 *
1251 * @param int $id Page id to create selector for.
1252 * @param bool $noAction If set, there will be no button for swapping page.
1253 * @return string
1254 */
1255 public function getVersionSelector($id, $noAction = false)
1256 {
1257 if (
1258 ExtensionManagementUtility::isLoaded('version') &&
1259 !ExtensionManagementUtility::isLoaded('workspaces')
1260 ) {
1261 $versionGuiObj = GeneralUtility::makeInstance(\TYPO3\CMS\Version\View\VersionView::class);
1262 return $versionGuiObj->getVersionSelector($id, $noAction);
1263 }
1264 }
1265
1266 /**
1267 * Function to load a HTML template file with markers.
1268 * When calling from own extension, use syntax getHtmlTemplate('EXT:extkey/template.html')
1269 *
1270 * @param string $filename tmpl name, usually in the typo3/template/ directory
1271 * @return string HTML of template
1272 */
1273 public function getHtmlTemplate($filename)
1274 {
1275 // setting the name of the original HTML template
1276 $this->moduleTemplateFilename = $filename;
1277 if ($GLOBALS['TBE_STYLES']['htmlTemplates'][$filename]) {
1278 $filename = $GLOBALS['TBE_STYLES']['htmlTemplates'][$filename];
1279 }
1280 if (GeneralUtility::isFirstPartOfStr($filename, 'EXT:')) {
1281 $filename = GeneralUtility::getFileAbsFileName($filename, true, true);
1282 } elseif (!GeneralUtility::isAbsPath($filename)) {
1283 $filename = GeneralUtility::resolveBackPath($filename);
1284 } elseif (!GeneralUtility::isAllowedAbsPath($filename)) {
1285 $filename = '';
1286 }
1287 $htmlTemplate = '';
1288 if ($filename !== '') {
1289 $htmlTemplate = GeneralUtility::getUrl($filename);
1290 }
1291 return $htmlTemplate;
1292 }
1293
1294 /**
1295 * Define the template for the module
1296 *
1297 * @param string $filename filename
1298 * @return void
1299 */
1300 public function setModuleTemplate($filename)
1301 {
1302 $this->moduleTemplate = $this->getHtmlTemplate($filename);
1303 }
1304
1305 /**
1306 * Put together the various elements for the module <body> using a static HTML
1307 * template
1308 *
1309 * @param array $pageRecord Record of the current page, used for page path and info
1310 * @param array $buttons HTML for all buttons
1311 * @param array $markerArray HTML for all other markers
1312 * @param array $subpartArray HTML for the subparts
1313 * @return string Composite HTML
1314 */
1315 public function moduleBody($pageRecord = array(), $buttons = array(), $markerArray = array(), $subpartArray = array())
1316 {
1317 // Get the HTML template for the module
1318 $moduleBody = $this->templateService->getSubpart($this->moduleTemplate, '###FULLDOC###');
1319 // Add CSS
1320 $this->inDocStylesArray[] = 'html { overflow: hidden; }';
1321 // Get the page path for the docheader
1322 $markerArray['PAGEPATH'] = $this->getPagePath($pageRecord);
1323 // Get the page info for the docheader
1324 $markerArray['PAGEINFO'] = $this->getPageInfo($pageRecord);
1325 // Get all the buttons for the docheader
1326 $docHeaderButtons = $this->getDocHeaderButtons($buttons);
1327 // Merge docheader buttons with the marker array
1328 $markerArray = array_merge($markerArray, $docHeaderButtons);
1329 // replacing subparts
1330 foreach ($subpartArray as $marker => $content) {
1331 $moduleBody = $this->templateService->substituteSubpart($moduleBody, $marker, $content);
1332 }
1333 // adding flash messages
1334 if ($this->showFlashMessages) {
1335 $flashMessages = $this->getFlashMessages();
1336 if (!empty($flashMessages)) {
1337 $markerArray['FLASHMESSAGES'] = $flashMessages;
1338 // If there is no dedicated marker for the messages present
1339 // then force them to appear before the content
1340 if (strpos($moduleBody, '###FLASHMESSAGES###') === false) {
1341 $moduleBody = str_replace('###CONTENT###', '###FLASHMESSAGES######CONTENT###', $moduleBody);
1342 }
1343 }
1344 }
1345 // Hook for adding more markers/content to the page, like the version selector
1346 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['moduleBodyPostProcess'])) {
1347 $params = array(
1348 'moduleTemplateFilename' => &$this->moduleTemplateFilename,
1349 'moduleTemplate' => &$this->moduleTemplate,
1350 'moduleBody' => &$moduleBody,
1351 'markers' => &$markerArray,
1352 'parentObject' => &$this
1353 );
1354 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['moduleBodyPostProcess'] as $funcRef) {
1355 GeneralUtility::callUserFunction($funcRef, $params, $this);
1356 }
1357 }
1358 // Replacing all markers with the finished markers and return the HTML content
1359 return $this->templateService->substituteMarkerArray($moduleBody, $markerArray, '###|###');
1360 }
1361
1362 /**
1363 * Get the default rendered FlashMessages from queue
1364 *
1365 * @return string
1366 */
1367 public function getFlashMessages()
1368 {
1369 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1370 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
1371 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
1372 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1373 return $defaultFlashMessageQueue->renderFlashMessages();
1374 }
1375
1376 /**
1377 * Renders the FlashMessages from queue and returns them as AJAX.
1378 *
1379 * @param ServerRequestInterface $request
1380 * @param ResponseInterface $response
1381 * @return ResponseInterface
1382 */
1383 public function renderQueuedFlashMessages(ServerRequestInterface $request, ResponseInterface $response)
1384 {
1385 /** @var $flashMessageService \TYPO3\CMS\Core\Messaging\FlashMessageService */
1386 $flashMessageService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Messaging\FlashMessageService::class);
1387 /** @var $defaultFlashMessageQueue \TYPO3\CMS\Core\Messaging\FlashMessageQueue */
1388 $defaultFlashMessageQueue = $flashMessageService->getMessageQueueByIdentifier();
1389 $flashMessages = $defaultFlashMessageQueue->getAllMessagesAndFlush();
1390
1391 $messages = [];
1392 foreach ($flashMessages as $flashMessage) {
1393 $messages[] = [
1394 'title' => $flashMessage->getTitle(),
1395 'message' => $flashMessage->getMessage(),
1396 'severity' => $flashMessage->getSeverity()
1397 ];
1398 }
1399
1400 $response->getBody()->write(json_encode($messages));
1401 return $response;
1402 }
1403
1404 /**
1405 * Fill the button lists with the defined HTML
1406 *
1407 * @param array $buttons HTML for all buttons
1408 * @return array Containing HTML for both buttonlists
1409 */
1410 protected function getDocHeaderButtons($buttons)
1411 {
1412 $markers = array();
1413 // Fill buttons for left and right float
1414 $floats = array('left', 'right');
1415 foreach ($floats as $key) {
1416 // Get the template for each float
1417 $buttonTemplate = $this->templateService->getSubpart($this->moduleTemplate, '###BUTTON_GROUPS_' . strtoupper($key) . '###');
1418 // Fill the button markers in this float
1419 $buttonTemplate = $this->templateService->substituteMarkerArray($buttonTemplate, $buttons, '###|###', true);
1420 // getting the wrap for each group
1421 $buttonWrap = $this->templateService->getSubpart($this->moduleTemplate, '###BUTTON_GROUP_WRAP###');
1422 // looping through the groups (max 6) and remove the empty groups
1423 for ($groupNumber = 1; $groupNumber < 6; $groupNumber++) {
1424 $buttonMarker = '###BUTTON_GROUP' . $groupNumber . '###';
1425 $buttonGroup = $this->templateService->getSubpart($buttonTemplate, $buttonMarker);
1426 if (trim($buttonGroup)) {
1427 if ($buttonWrap) {
1428 $buttonGroup = $this->templateService->substituteMarker($buttonWrap, '###BUTTONS###', $buttonGroup);
1429 }
1430 $buttonTemplate = $this->templateService->substituteSubpart($buttonTemplate, $buttonMarker, trim($buttonGroup));
1431 }
1432 }
1433 // Replace the marker with the template and remove all line breaks (for IE compat)
1434 $markers['BUTTONLIST_' . strtoupper($key)] = str_replace(LF, '', $buttonTemplate);
1435 }
1436 // Hook for manipulating docHeaderButtons
1437 if (isset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['docHeaderButtonsHook'])) {
1438 $params = array(
1439 'buttons' => $buttons,
1440 'markers' => &$markers,
1441 'pObj' => &$this
1442 );
1443 foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['typo3/template.php']['docHeaderButtonsHook'] as $funcRef) {
1444 GeneralUtility::callUserFunction($funcRef, $params, $this);
1445 }
1446 }
1447 return $markers;
1448 }
1449
1450 /**
1451 * Generate the page path for docheader
1452 *
1453 * @param array $pageRecord Current page
1454 * @return string Page path
1455 */
1456 protected function getPagePath($pageRecord)
1457 {
1458 // Is this a real page
1459 if (is_array($pageRecord) && $pageRecord['uid']) {
1460 $title = substr($pageRecord['_thePathFull'], 0, -1);
1461 // Remove current page title
1462 $pos = strrpos($title, $pageRecord['title']);
1463 if ($pos !== false) {
1464 $title = substr($title, 0, $pos);
1465 }
1466 } else {
1467 $title = '';
1468 }
1469 // Setting the path of the page
1470 $pagePath = $GLOBALS['LANG']->sL('LLL:EXT:lang/locallang_core.xlf:labels.path', true) . ': <span class="typo3-docheader-pagePath">';
1471 // crop the title to title limit (or 50, if not defined)
1472 $cropLength = empty($GLOBALS['BE_USER']->uc['titleLen']) ? 50 : $GLOBALS['BE_USER']->uc['titleLen'];
1473 $croppedTitle = GeneralUtility::fixed_lgd_cs($title, -$cropLength);
1474 if ($croppedTitle !== $title) {
1475 $pagePath .= '<abbr title="' . htmlspecialchars($title) . '">' . htmlspecialchars($croppedTitle) . '</abbr>';
1476 } else {
1477 $pagePath .= htmlspecialchars($title);
1478 }
1479 $pagePath .= '</span>';
1480 return $pagePath;
1481 }
1482
1483 /**
1484 * Setting page icon with clickmenu + uid for docheader
1485 *
1486 * @param array $pageRecord Current page
1487 * @return string Page info
1488 */
1489 protected function getPageInfo($pageRecord)
1490 {
1491 // Add icon with clickmenu, etc:
1492 // If there IS a real page
1493 if (is_array($pageRecord) && $pageRecord['uid']) {
1494 $alttext = BackendUtility::getRecordIconAltText($pageRecord, 'pages');
1495 $iconImg = '<span title="' . htmlspecialchars($alttext) . '">' . $this->iconFactory->getIconForRecord('pages', $pageRecord, Icon::SIZE_SMALL)->render() . '</span>';
1496 // Make Icon:
1497 $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, 'pages', $pageRecord['uid']);
1498 $uid = $pageRecord['uid'];
1499 $title = BackendUtility::getRecordTitle('pages', $pageRecord);
1500 } else {
1501 // On root-level of page tree
1502 // Make Icon
1503 $iconImg = '<span title="' . htmlspecialchars($GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename']) . '">' . $this->iconFactory->getIcon('apps-pagetree-root', Icon::SIZE_SMALL)->render() . '</span>';
1504 if ($GLOBALS['BE_USER']->user['admin']) {
1505 $theIcon = BackendUtility::wrapClickMenuOnIcon($iconImg, 'pages', 0);
1506 } else {
1507 $theIcon = $iconImg;
1508 }
1509 $uid = '0';
1510 $title = $GLOBALS['TYPO3_CONF_VARS']['SYS']['sitename'];
1511 }
1512 // Setting icon with clickmenu + uid
1513 $pageInfo = $theIcon . '<strong>' . htmlspecialchars($title) . '&nbsp;[' . $uid . ']</strong>';
1514 return $pageInfo;
1515 }
1516
1517 /**
1518 * Retrieves configured favicon for backend (with fallback)
1519 *
1520 * @return string
1521 */
1522 protected function getBackendFavicon()
1523 {
1524 return $GLOBALS['TBE_STYLES']['favicon'] ?: 'sysext/backend/Resources/Public/Icons/favicon.ico';
1525 }
1526 }