358c56b560976b5c19b103dc2def002ca68aa4d7
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Template / ModuleTemplate.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 TYPO3\CMS\Backend\Template\Components\DocHeaderComponent;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
20 use TYPO3\CMS\Core\Imaging\Icon;
21 use TYPO3\CMS\Core\Imaging\IconFactory;
22 use TYPO3\CMS\Core\Messaging\AbstractMessage;
23 use TYPO3\CMS\Core\Messaging\FlashMessageService;
24 use TYPO3\CMS\Core\Page\PageRenderer;
25 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException;
28 use TYPO3\CMS\Fluid\View\StandaloneView;
29 use TYPO3\CMS\Lang\LanguageService;
30
31 /**
32 * A class taking care of the "outer" HTML of a module, especially
33 * the doc header and other related parts.
34 *
35 * @internal This API is not yet carved in stone and may be adapted later.
36 */
37 class ModuleTemplate
38 {
39 /**
40 * Error Icon Constant
41 *
42 * @internal
43 */
44 const STATUS_ICON_ERROR = 3;
45
46 /**
47 * Warning Icon Constant
48 *
49 * @internal
50 */
51 const STATUS_ICON_WARNING = 2;
52
53 /**
54 * Notification Icon Constant
55 *
56 * @internal
57 */
58 const STATUS_ICON_NOTIFICATION = 1;
59
60 /**
61 * OK Icon Constant
62 *
63 * @internal
64 */
65 const STATUS_ICON_OK = -1;
66
67 /**
68 * DocHeaderComponent
69 *
70 * @var DocHeaderComponent
71 */
72 protected $docHeaderComponent;
73
74 /**
75 * Javascript Code Array
76 * Used for inline JS
77 *
78 * @var array
79 */
80 protected $javascriptCodeArray = [];
81
82 /**
83 * Expose the pageRenderer
84 *
85 * @var PageRenderer
86 */
87 protected $pageRenderer;
88
89 /**
90 * TemplateRootPath
91 *
92 * @var string[]
93 */
94 protected $templateRootPaths = ['EXT:backend/Resources/Private/Templates'];
95
96 /**
97 * PartialRootPath
98 *
99 * @var string[]
100 */
101 protected $partialRootPaths = ['EXT:backend/Resources/Private/Partials'];
102
103 /**
104 * LayoutRootPath
105 *
106 * @var string[]
107 */
108 protected $layoutRootPaths = ['EXT:backend/Resources/Private/Layouts'];
109
110 /**
111 * Template name
112 *
113 * @var string
114 */
115 protected $templateFile = 'Module.html';
116
117 /**
118 * Fluid Standalone View
119 *
120 * @var StandaloneView
121 */
122 protected $view;
123
124 /**
125 * Content String
126 *
127 * @var string
128 */
129 protected $content = '';
130
131 /**
132 * IconFactory Member
133 *
134 * @var IconFactory
135 */
136 protected $iconFactory;
137
138 /**
139 * Module ID
140 *
141 * @var string
142 */
143 protected $moduleId = '';
144
145 /**
146 * Module Name
147 *
148 * @var string
149 */
150 protected $moduleName = '';
151
152 /**
153 * Title Tag
154 *
155 * @var string
156 */
157 protected $title = '';
158
159 /**
160 * Body Tag
161 *
162 * @var string
163 */
164 protected $bodyTag = '<body>';
165
166 /**
167 * Flash message queue
168 *
169 * @var \TYPO3\CMS\Core\Messaging\FlashMessageQueue
170 */
171 protected $flashMessageQueue;
172
173 /**
174 * Returns the current body tag
175 *
176 * @return string
177 */
178 public function getBodyTag()
179 {
180 return $this->bodyTag;
181 }
182
183 /**
184 * Sets the body tag
185 *
186 * @param string $bodyTag
187 */
188 public function setBodyTag($bodyTag)
189 {
190 $this->bodyTag = $bodyTag;
191 }
192
193 /**
194 * Gets the standalone view.
195 *
196 * @return StandaloneView
197 */
198 public function getView()
199 {
200 return $this->view;
201 }
202
203 /**
204 * Set content
205 *
206 * @param string $content Content of the module
207 */
208 public function setContent($content)
209 {
210 $this->view->assign('content', $content);
211 }
212
213 /**
214 * Set title tag
215 *
216 * @param string $title
217 */
218 public function setTitle($title)
219 {
220 $this->title = $title;
221 }
222
223 /**
224 * Returns the IconFactory
225 *
226 * @return IconFactory
227 */
228 public function getIconFactory()
229 {
230 return $this->iconFactory;
231 }
232
233 /**
234 * Class constructor
235 * Sets up view and property objects
236 *
237 * @throws InvalidTemplateResourceException In case a template is invalid
238 */
239 public function __construct()
240 {
241 $this->view = GeneralUtility::makeInstance(StandaloneView::class);
242 $this->view->setPartialRootPaths($this->partialRootPaths);
243 $this->view->setTemplateRootPaths($this->templateRootPaths);
244 $this->view->setLayoutRootPaths($this->layoutRootPaths);
245 $this->view->setTemplate($this->templateFile);
246 $this->pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
247 $this->docHeaderComponent = GeneralUtility::makeInstance(DocHeaderComponent::class);
248 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
249 }
250
251 /**
252 * Loads all necessary Javascript Files
253 */
254 protected function loadJavaScripts()
255 {
256 $this->pageRenderer->loadJquery();
257 $this->pageRenderer->loadRequireJsModule('bootstrap');
258 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/ContextHelp');
259 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/DocumentHeader');
260 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/SplitButtons');
261 }
262
263 /**
264 * Loads all necessary stylesheets
265 */
266 protected function loadStylesheets()
267 {
268 if ($GLOBALS['TBE_STYLES']['stylesheet']) {
269 $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheet']);
270 }
271 if ($GLOBALS['TBE_STYLES']['stylesheet2']) {
272 $this->pageRenderer->addCssFile($GLOBALS['TBE_STYLES']['stylesheet2']);
273 }
274 }
275
276 /**
277 * Sets mandatory parameters for the view (pageRenderer)
278 */
279 protected function setupPage()
280 {
281 // Yes, hardcoded on purpose
282 $this->pageRenderer->setXmlPrologAndDocType('<!DOCTYPE html>');
283 $this->pageRenderer->setCharSet('utf-8');
284 $this->pageRenderer->setLanguage($GLOBALS['LANG']->lang);
285 $this->pageRenderer->addMetaTag('<meta name="viewport" content="width=device-width, initial-scale=1">');
286 }
287
288 /**
289 * Wrapper function for adding JS inline blocks
290 */
291 protected function setJavaScriptCodeArray()
292 {
293 foreach ($this->javascriptCodeArray as $name => $code) {
294 $this->pageRenderer->addJsInlineCode($name, $code, false);
295 }
296 }
297
298 /**
299 * Adds JS inline blocks of code to the internal registry
300 *
301 * @param string $name Javascript code block name
302 * @param string $code Inline Javascript
303 */
304 public function addJavaScriptCode($name = '', $code = '')
305 {
306 $this->javascriptCodeArray[$name] = $code;
307 }
308
309 /**
310 * Get the DocHeader
311 *
312 * @return DocHeaderComponent
313 */
314 public function getDocHeaderComponent()
315 {
316 return $this->docHeaderComponent;
317 }
318
319 /**
320 * Returns the fully rendered view
321 *
322 * @return string
323 */
324 public function renderContent()
325 {
326 $this->setupPage();
327 $this->pageRenderer->setTitle($this->title);
328 $this->loadJavaScripts();
329 $this->setJavaScriptCodeArray();
330 $this->loadStylesheets();
331
332 $this->view->assign('docHeader', $this->docHeaderComponent->docHeaderContent());
333 if ($this->moduleId) {
334 $this->view->assign('moduleId', $this->moduleId);
335 }
336 if ($this->moduleName) {
337 $this->view->assign('moduleName', $this->moduleName);
338 }
339 $this->view->assign('flashMessageQueueIdentifier', $this->getFlashMessageQueue()->getIdentifier());
340 $renderedPage = $this->pageRenderer->render(PageRenderer::PART_HEADER);
341 $renderedPage .= $this->bodyTag;
342 $renderedPage .= $this->view->render();
343 $this->pageRenderer->addJsFooterInlineCode('updateSignals', BackendUtility::getUpdateSignalCode());
344 $renderedPage .= $this->pageRenderer->render(PageRenderer::PART_FOOTER);
345
346 return $renderedPage;
347 }
348
349 /**
350 * Get PageRenderer
351 *
352 * @return PageRenderer
353 */
354 public function getPageRenderer()
355 {
356 return $this->pageRenderer;
357 }
358
359 /**
360 * Set form tag
361 *
362 * @param string $formTag Form tag to add
363 */
364 public function setForm($formTag = '')
365 {
366 $this->view->assign('formTag', $formTag);
367 }
368
369 /**
370 * Sets the ModuleId
371 *
372 * @param string $moduleId ID of the module
373 */
374 public function setModuleId($moduleId)
375 {
376 $this->moduleId = $moduleId;
377 $this->registerModuleMenu($moduleId);
378 }
379
380 /**
381 * Sets the ModuleName
382 *
383 * @param string $moduleName Name of the module
384 */
385 public function setModuleName($moduleName)
386 {
387 $this->moduleName = $moduleName;
388 }
389
390 /**
391 * Generates the Menu for things like Web->Info
392 *
393 * @param $moduleMenuIdentifier
394 */
395 public function registerModuleMenu($moduleMenuIdentifier)
396 {
397 if (isset($GLOBALS['TBE_MODULES_EXT'][$moduleMenuIdentifier])) {
398 $menuEntries =
399 $GLOBALS['TBE_MODULES_EXT'][$moduleMenuIdentifier]['MOD_MENU']['function'];
400 $menu = $this->getDocHeaderComponent()->getMenuRegistry()->makeMenu()->setIdentifier('MOD_FUNC');
401 foreach ($menuEntries as $menuEntry) {
402 $menuItem = $menu->makeMenuItem()
403 ->setTitle($menuEntry['title'])
404 ->setHref('#');
405 $menu->addMenuItem($menuItem);
406 }
407 $this->docHeaderComponent->getMenuRegistry()->addMenu($menu);
408 }
409 }
410
411 /**
412 * Creates a tab menu where the tabs or collapsible are rendered with bootstrap markup
413 *
414 * @param array $menuItems Tab elements, each element is an array with "label" and "content"
415 * @param string $domId DOM id attribute, will be appended with an iteration number per tab.
416 * @param int $defaultTabIndex Default tab to open (for toggle <=0). Value corresponds to integer-array index + 1
417 * (index zero is "1", index "1" is 2 etc.). A value of zero (or something non-existing
418 * will result in no default tab open.
419 * @param bool $collapsible If set, the tabs are rendered as headers instead over each sheet. Effectively this means
420 * there is no tab menu, but rather a foldout/fold-in menu.
421 * @param bool $wrapContent If set, the content is wrapped in div structure which provides a padding and border
422 * style. Set this FALSE to get unstyled content pane with fullsize content area.
423 * @param bool $storeLastActiveTab If set, the last open tab is stored in local storage and will be re-open again.
424 * If you don't need this feature, e.g. for wizards like import/export you can
425 * disable this behaviour.
426 * @return string
427 */
428 public function getDynamicTabMenu(array $menuItems, $domId, $defaultTabIndex = 1, $collapsible = false, $wrapContent = true, $storeLastActiveTab = true)
429 {
430 $this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Backend/Tabs');
431 $templatePath = ExtensionManagementUtility::extPath('backend')
432 . 'Resources/Private/Templates/DocumentTemplate/';
433 $view = GeneralUtility::makeInstance(StandaloneView::class);
434 $view->setTemplatePathAndFilename($templatePath . ($collapsible ? 'Collapse.html' : 'Tabs.html'));
435 $view->setPartialRootPaths([$templatePath . 'Partials']);
436 $view->assignMultiple([
437 'id' => 'DTM-' . GeneralUtility::shortMD5($domId),
438 'items' => $menuItems,
439 'defaultTabIndex' => $defaultTabIndex,
440 'wrapContent' => $wrapContent,
441 'storeLastActiveTab' => $storeLastActiveTab,
442 ]);
443 return $view->render();
444 }
445
446 /*******************************************
447 * THE FOLLOWING METHODS ARE SUBJECT TO BE DEPRECATED / DROPPED!
448 *
449 * These methods have been copied over from DocumentTemplate and enables
450 * core modules to drop the dependency to DocumentTemplate altogether without
451 * rewriting these modules now.
452 * The methods below are marked as internal and will be removed
453 * one-by-one with further refactoring of modules.
454 *
455 * Do not use these methods within own extensions if possible or
456 * be prepared to change this later again.
457 *******************************************/
458
459 /**
460 * Includes a javascript library that exists in the core /typo3/ directory
461 *
462 * @param string $lib Library name. Call it with the full path like
463 * "sysext/core/Resources/Public/JavaScript/QueryGenerator.js" to load it
464 *
465 * @internal
466 */
467 public function loadJavascriptLib($lib)
468 {
469 // @todo: maybe we can remove this one as well
470 $this->pageRenderer->addJsFile($lib);
471 }
472
473 /**
474 * Returns a linked shortcut-icon which will call the shortcut frame and set a
475 * shortcut there back to the calling page/module
476 *
477 * @param string $gvList Is the list of GET variables to store (if any)
478 * @param string $setList Is the list of SET[] variables to store
479 * (if any) - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS
480 * for backend modules
481 * @param string $modName Module name string
482 * @param string|int $motherModName Is used to enter the "parent module
483 * name" if the module is a submodule under eg. Web>* or File>*. You
484 * can also set this value to 1 in which case the currentLoadedModule
485 * is sent to the shortcut script (so - not a fixed value!) - that is used
486 * in file_edit and wizard_rte modules where those are really running as
487 * a part of another module.
488 * @param string $displayName When given this name is used instead of the
489 * module name.
490 * @param string $classes Additional CSS classes for the link around the icon
491 *
492 * @return string HTML content
493 * @todo Make this thing return a button object
494 * @internal
495 */
496 public function makeShortcutIcon($gvList, $setList, $modName, $motherModName = '', $displayName = '', $classes = 'btn btn-default btn-sm')
497 {
498 $gvList = 'route,' . $gvList;
499 $storeUrl = $this->makeShortcutUrl($gvList, $setList);
500 $pathInfo = parse_url(GeneralUtility::getIndpEnv('REQUEST_URI'));
501 // Fallback for alt_mod. We still pass in the old xMOD... stuff,
502 // but TBE_MODULES only knows about "record_edit".
503 // We still need to pass the xMOD name to createShortcut below,
504 // since this is used for icons.
505 $moduleName = $modName === 'xMOD_alt_doc.php' ? 'record_edit' : $modName;
506 // Add the module identifier automatically if typo3/index.php is used:
507 if (GeneralUtility::_GET('M') !== null) {
508 $storeUrl = '&M=' . $moduleName . $storeUrl;
509 }
510 if ((int)$motherModName === 1) {
511 $motherModule = 'top.currentModuleLoaded';
512 } elseif ($motherModName) {
513 $motherModule = GeneralUtility::quoteJSvalue($motherModName);
514 } else {
515 $motherModule = '\'\'';
516 }
517 $confirmationText = GeneralUtility::quoteJSvalue(
518 $this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')
519 );
520
521 $shortcutUrl = $pathInfo['path'] . '?' . $storeUrl;
522 $shortcutExist = BackendUtility::shortcutExists($shortcutUrl);
523
524 if ($shortcutExist) {
525 return '<a class="active ' . htmlspecialchars($classes) . '" title="">' .
526 $this->iconFactory->getIcon('actions-system-shortcut-active', Icon::SIZE_SMALL)->render() . '</a>';
527 }
528
529 $url = GeneralUtility::quoteJSvalue(rawurlencode($shortcutUrl));
530 $onClick = 'top.TYPO3.ShortcutMenu.createShortcut(' . GeneralUtility::quoteJSvalue(rawurlencode($modName)) .
531 ', ' . $url . ', ' . $confirmationText . ', ' . $motherModule . ', this, ' . GeneralUtility::quoteJSvalue($displayName) . ');return false;';
532
533 return '<a href="#" class="' . htmlspecialchars($classes) . '" onclick="' . htmlspecialchars($onClick) . '" title="' .
534 htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.makeBookmark')) . '">' .
535 $this->iconFactory->getIcon('actions-system-shortcut-new', Icon::SIZE_SMALL)->render() . '</a>';
536 }
537
538 /**
539 * MAKE url for storing
540 * Internal func
541 *
542 * @param string $gvList Is the list of GET variables to store (if any)
543 * @param string $setList Is the list of SET[] variables to store (if any)
544 * - SET[] variables a stored in $GLOBALS["SOBE"]->MOD_SETTINGS for backend
545 * modules
546 *
547 * @return string
548 * @internal
549 */
550 public function makeShortcutUrl($gvList, $setList)
551 {
552 $getParams = GeneralUtility::_GET();
553 $storeArray = array_merge(
554 GeneralUtility::compileSelectedGetVarsFromArray($gvList, $getParams),
555 ['SET' => GeneralUtility::compileSelectedGetVarsFromArray($setList, (array)$GLOBALS['SOBE']->MOD_SETTINGS)]
556 );
557 return GeneralUtility::implodeArrayForUrl('', $storeArray);
558 }
559
560 /**
561 * Returns the BE USER Object
562 *
563 * @return BackendUserAuthentication
564 */
565 protected function getBackendUserAuthentication()
566 {
567 return $GLOBALS['BE_USER'];
568 }
569
570 /**
571 * Returns the LanguageService
572 *
573 * @return LanguageService
574 */
575 protected function getLanguageService()
576 {
577 return $GLOBALS['LANG'];
578 }
579
580 /**
581 * Returns an image-tag with an 18x16 icon of the following types:
582 *
583 * $type:
584 * -1:» OK icon (Check-mark)
585 * 1:» Notice (Speach-bubble)
586 * 2:» Warning (Yellow triangle)
587 * 3:» Fatal error (Red stop sign)
588 *
589 * @param int $type See description
590 *
591 * @return string HTML image tag (if applicable)
592 * @internal
593 */
594 public function icons($type)
595 {
596 $icon = '';
597 switch ($type) {
598 case self::STATUS_ICON_ERROR:
599 $icon = 'status-dialog-error';
600 break;
601 case self::STATUS_ICON_WARNING:
602 $icon = 'status-dialog-warning';
603 break;
604 case self::STATUS_ICON_NOTIFICATION:
605 $icon = 'status-dialog-notification';
606 break;
607 case self::STATUS_ICON_OK:
608 $icon = 'status-dialog-ok';
609 break;
610 default:
611 // Do nothing
612 }
613 if ($icon != '') {
614 return $this->iconFactory->getIcon($icon, Icon::SIZE_SMALL)->render();
615 }
616 return '';
617 }
618
619 /**
620 * Returns JavaScript variables setting the returnUrl and thisScript location for use by JavaScript on the page.
621 * Used in fx. db_list.php (Web>List)
622 *
623 * @param string $thisLocation URL to "this location" / current script
624 * @return string Urls are returned as JavaScript variables T3_RETURN_URL and T3_THIS_LOCATION
625 * @see typo3/db_list.php
626 * @internal
627 */
628 public function redirectUrls($thisLocation = '')
629 {
630 $thisLocation = $thisLocation ? $thisLocation : GeneralUtility::linkThisScript([
631 'CB' => '',
632 'SET' => '',
633 'cmd' => '',
634 'popViewId' => ''
635 ]);
636 $out = '
637 var T3_RETURN_URL = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode(GeneralUtility::sanitizeLocalUrl(GeneralUtility::_GP('returnUrl'))))) . ';
638 var T3_THIS_LOCATION = ' . GeneralUtility::quoteJSvalue(str_replace('%20', '', rawurlencode($thisLocation))) . '
639 ';
640 return $out;
641 }
642
643 /**
644 * Returns the header-bar in the top of most backend modules
645 * Closes section if open.
646 *
647 * @param string $text The text string for the header
648 * @return string HTML content
649 * @internal
650 */
651 public function header($text)
652 {
653 return '
654
655 <!-- MAIN Header in page top -->
656 <h1 class="t3js-title-inlineedit">' . htmlspecialchars($text) . '</h1>
657 ';
658 }
659
660 /**
661 * Creates a Message object and adds it to the FlashMessageQueue.
662 *
663 * @param string $messageBody The message
664 * @param string $messageTitle Optional message title
665 * @param int $severity Optional severity, must be one of \TYPO3\CMS\Core\Messaging\FlashMessage constants
666 * @param bool $storeInSession Optional, defines whether the message should be stored in the session (default)
667 * @throws \InvalidArgumentException if the message body is no string
668 */
669 public function addFlashMessage($messageBody, $messageTitle = '', $severity = AbstractMessage::OK, $storeInSession = true)
670 {
671 if (!is_string($messageBody)) {
672 throw new \InvalidArgumentException('The message body must be of type string, "' . gettype($messageBody) . '" given.', 1446483133);
673 }
674 /* @var \TYPO3\CMS\Core\Messaging\FlashMessage $flashMessage */
675 $flashMessage = GeneralUtility::makeInstance(
676 \TYPO3\CMS\Core\Messaging\FlashMessage::class,
677 $messageBody,
678 $messageTitle,
679 $severity,
680 $storeInSession
681 );
682 $this->getFlashMessageQueue()->enqueue($flashMessage);
683 }
684
685 /**
686 * @param \TYPO3\CMS\Core\Messaging\FlashMessageQueue $flashMessageQueue
687 */
688 public function setFlashMessageQueue($flashMessageQueue)
689 {
690 $this->flashMessageQueue = $flashMessageQueue;
691 }
692
693 /**
694 * @return \TYPO3\CMS\Core\Messaging\FlashMessageQueue
695 */
696 protected function getFlashMessageQueue()
697 {
698 if (!isset($this->flashMessageQueue)) {
699 /** @var FlashMessageService $service */
700 $service = GeneralUtility::makeInstance(FlashMessageService::class);
701 $this->flashMessageQueue = $service->getMessageQueueByIdentifier();
702 }
703 return $this->flashMessageQueue;
704 }
705 }